def run_moments(*, n_sims, wcs_g1, wcs_g2): """Run the moments fitter and check g1, g2. The resulting values are printed to STDOUT. Parameters ---------- n_sims : int The number of objects to simulated. wcs_g1 : float The shear on the 1-axis of the WCS Jacobian. wcs_g2 : float The shear on the 2-axis of the WCS Jacobian. """ jc = galsim.ShearWCS(0.263, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() jacobian_dict = { 'dudx': jc.dudx, 'dudy': jc.dudy, 'dvdx': jc.dvdx, 'dvdy': jc.dvdy } res = _run_moments(n_sims=n_sims, rng=np.random.RandomState(seed=10), **jacobian_dict) g1 = np.array([r['g'][0] for r in res]) g2 = np.array([r['g'][1] for r in res]) g1m, g1_err, g2m, g2_err = _jack_est(g1, g2) print("""\ # of sims: {n_sims} wcs_g1 : {wcs_g1:f} wcs_g2 : {wcs_g2:f} dudx : {dudx:f} dudy : {dudy:f} dvdx : {dvdx:f} dvdy : {dvdy:f} g1 [1e-3] : {g1m:g} +/- {g1_err:g} g2 [1e-3] : {g2m:g} +/- {g2_err:g}""".format(n_sims=len(g1), wcs_g1=wcs_g1, wcs_g2=wcs_g2, **jacobian_dict, g1m=g1m / 1e-3, g1_err=g1_err / 1e-3, g2m=g2m / 1e-3, g2_err=g2_err / 1e-3), flush=True)
def test_pixels_smoke(x, y, wcs_g1, wcs_g2, ignore_zero_weight): gs_wcs = galsim.ShearWCS(0.25, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() jac = Jacobian(y=y, x=x, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy) dims = (13, 15) rng = np.random.RandomState(seed=11) image = rng.normal(size=dims) weight = np.exp(rng.normal(size=dims)) weight[10, 9] = 0 weight[8, 7] = 0 pixels = make_pixels(image, weight, jac, ignore_zero_weight=ignore_zero_weight) assert np.allclose(pixels['area'], jac.area) found_zero = 0 for i in range(len(pixels)): y, x = jac.get_rowcol(pixels['v'][i], pixels['u'][i]) assert np.allclose(x, int(x + 0.5)) assert np.allclose(y, int(y + 0.5)) x = int(x + 0.5) y = int(y + 0.5) assert pixels['val'][i] == image[y, x] assert np.allclose(pixels['ierr'][i], np.sqrt(weight[y, x])) if x == 9 and y == 10: found_zero += 1 if y == 8 and x == 7: found_zero += 1 if ignore_zero_weight: assert found_zero == 0 else: assert found_zero == 2
def test_coords_smoke(x, y, wcs_g1, wcs_g2): gs_wcs = galsim.ShearWCS(0.25, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() jac = Jacobian(y=y, x=x, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy) dims = (13, 15) coords = make_coords(dims, jac) loc = 0 for y in range(dims[0]): for x in range(dims[1]): v, u = jac(y, x) assert u == coords['u'][loc] assert v == coords['v'][loc] loc += 1
def main(argv): """ Getting reasonably close to including all the principle features of an image from a ground-based telescope: - Use a bulge plus disk model for the galaxy - Both galaxy components are Sersic profiles (n=3.5 and n=1.5 respectively) - Let the PSF have both atmospheric and optical components. - The atmospheric component is a Kolmogorov spectrum. - The optical component has some defocus, coma, and astigmatism. - Add both Poisson noise to the image and Gaussian read noise. - Let the pixels be slightly distorted relative to the sky. """ # We do some fancier logging for demo3, just to demonstrate that we can: # - we log to both stdout and to a log file # - the log file has a lot more (mostly redundant) information logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) if not os.path.isdir('output'): os.mkdir('output') logFile = logging.FileHandler(os.path.join("output", "script3.log")) logFile.setFormatter(logging.Formatter("%(name)s[%(levelname)s] %(asctime)s: %(message)s")) logging.getLogger("demo3").addHandler(logFile) logger = logging.getLogger("demo3") gal_flux = 1.e6 # ADU ("Analog-to-digital units", the units of the numbers on a CCD) bulge_n = 3.5 # bulge_re = 2.3 # arcsec disk_n = 1.5 # disk_r0 = 0.85 # arcsec (corresponds to half_light_radius of ~3.7 arcsec) bulge_frac = 0.3 # gal_q = 0.73 # (axis ratio 0 < q < 1) gal_beta = 23 # degrees (position angle on the sky) atmos_fwhm=2.1 # arcsec atmos_e = 0.13 # atmos_beta = 0.81 # radians opt_defocus=0.53 # wavelengths opt_a1=-0.29 # wavelengths opt_a2=0.12 # wavelengths opt_c1=0.64 # wavelengths opt_c2=-0.33 # wavelengths opt_obscuration=0.3 # linear scale size of secondary mirror obscuration lam = 800 # nm NB: don't use lambda - that's a reserved word. tel_diam = 4. # meters pixel_scale = 0.23 # arcsec / pixel image_size = 64 # n x n pixels wcs_g1 = -0.02 # wcs_g2 = 0.01 # sky_level = 2.5e4 # ADU / arcsec^2 gain = 1.7 # e- / ADU # Note: here we assume 1 photon -> 1 e-, ignoring QE. If you wanted, # you could include the QE factor as part of the gain. read_noise = 0.3 # e- / pixel random_seed = 1314662 logger.info('Starting demo script 3 using:') logger.info(' - Galaxy is bulge plus disk, flux = %.1e',gal_flux) logger.info(' - Bulge is Sersic (n = %.1f, re = %.2f), frac = %.1f', bulge_n,bulge_re,bulge_frac) logger.info(' - Disk is Sersic (n = %.1f, r0 = %.2f), frac = %.1f', disk_n,disk_r0,1-bulge_frac) logger.info(' - Shape is q,beta (%.2f,%.2f deg)', gal_q, gal_beta) logger.info(' - Atmospheric PSF is Kolmogorov with fwhm = %.2f',atmos_fwhm) logger.info(' - Shape is e,beta (%.2f,%.2f rad)', atmos_e, atmos_beta) logger.info(' - Optical PSF has defocus = %.2f, astigmatism = (%.2f,%.2f),', opt_defocus, opt_a1, opt_a2) logger.info(' coma = (%.2f,%.2f), lambda = %.0f nm, D = %.1f m', opt_c1, opt_c2, lam, tel_diam) logger.info(' obscuration linear size = %.1f',opt_obscuration) logger.info(' - pixel scale = %.2f,',pixel_scale) logger.info(' - WCS distortion = (%.2f,%.2f),',wcs_g1,wcs_g2) logger.info(' - Poisson noise (sky level = %.1e, gain = %.1f).',sky_level, gain) logger.info(' - Gaussian read noise (sigma = %.2f).',read_noise) # Initialize the (pseudo-)random number generator that we will be using below. rng = galsim.BaseDeviate(random_seed+1) # Define the galaxy profile. # Normally Sersic profiles are specified by half-light radius, the radius that # encloses half of the total flux. However, for some purposes, it can be # preferable to instead specify the scale radius, where the surface brightness # drops to 1/e of the central peak value. bulge = galsim.Sersic(bulge_n, half_light_radius=bulge_re) disk = galsim.Sersic(disk_n, scale_radius=disk_r0) # Objects may be multiplied by a scalar (which means scaling the flux) and also # added to each other. gal = bulge_frac * bulge + (1-bulge_frac) * disk # Could also have written the following, which does the same thing: # gal = galsim.Add([ bulge.withFlux(bulge_frac) , disk.withFlux(1-bulge_frac) ]) # Both syntaxes work with more than two summands as well. # Set the overall flux of the combined object. gal = gal.withFlux(gal_flux) # Since the total flux of the components was 1, we could also have written: # gal *= gal_flux # The withFlux method will always set the flux to the given value, while `gal *= flux` # will multiply whatever the current flux is by the given factor. # Set the shape of the galaxy according to axis ratio and position angle # Note: All angles in GalSim must have explicit units. Options are: # galsim.radians # galsim.degrees # galsim.arcmin # galsim.arcsec # galsim.hours gal_shape = galsim.Shear(q=gal_q, beta=gal_beta*galsim.degrees) gal = gal.shear(gal_shape) logger.debug('Made galaxy profile') # Define the atmospheric part of the PSF. # Note: the flux here is the default flux=1. atmos = galsim.Kolmogorov(fwhm=atmos_fwhm) # For the PSF shape here, we use ellipticity rather than axis ratio. # And the position angle can be either degrees or radians. Here we chose radians. atmos = atmos.shear(e=atmos_e, beta=atmos_beta*galsim.radians) logger.debug('Made atmospheric PSF profile') # Define the optical part of the PSF: # The first argument of OpticalPSF below is lambda/diam (wavelength of light / telescope # diameter), which needs to be in the same units used to specify the image scale. We are using # arcsec for that, so we have to self-consistently use arcsec here, using the following # calculation: lam_over_diam = lam * 1.e-9 / tel_diam # radians lam_over_diam *= 206265 # arcsec # Note that we could also have made GalSim do the conversion for us if we did not know the right # factor: # lam_over_diam = lam * 1.e-9 / tel_diam * galsim.radians # lam_over_diam = lam_over_diam / galsim.arcsec logger.debug('Calculated lambda over diam = %f arcsec', lam_over_diam) # The rest of the values should be given in units of the wavelength of the incident light. optics = galsim.OpticalPSF(lam_over_diam, defocus = opt_defocus, coma1 = opt_c1, coma2 = opt_c2, astig1 = opt_a1, astig2 = opt_a2, obscuration = opt_obscuration) logger.debug('Made optical PSF profile') # So far, our coordinate transformation between image and sky coordinates has been just a # scaling of the units between pixels and arcsec, which we have defined as the "pixel scale". # This is fine for many purposes, so we have made it easy to treat the coordinate systems # this way via the `scale` parameter to commands like drawImage. However, in general, the # transformation between the two coordinate systems can be more complicated than that, # including distortions, rotations, variation in pixel size, and so forth. GalSim can # model a number of different "World Coordinate System" (WCS) transformations. See the # docstring for BaseWCS for more information. # In this case, we use a WCS that includes a distortion (specified as g1,g2 in this case), # which we call a ShearWCS. wcs = galsim.ShearWCS(scale=pixel_scale, shear=galsim.Shear(g1=wcs_g1, g2=wcs_g2)) logger.debug('Made the WCS') # Next we will convolve the components in world coordinates. psf = galsim.Convolve([atmos, optics]) final = galsim.Convolve([psf, gal]) logger.debug('Convolved components into final profile') # This time we specify a particular size for the image rather than let GalSim # choose the size automatically. GalSim has several kinds of images that it can use: # ImageF uses 32-bit floats (like a C float, aka numpy.float32) # ImageD uses 64-bit floats (like a C double, aka numpy.float64) # ImageS uses 16-bit integers (usually like a C short, aka numpy.int16) # ImageI uses 32-bit integers (usually like a C int, aka numpy.int32) # If you let the GalSim drawImage command create the image for you, it will create an ImageF. # However, you can make a different type if you prefer. In this case, we still use # ImageF, since 32-bit floats are fine. We just want to set the size explicitly. image = galsim.ImageF(image_size, image_size) # Draw the image with the given WCS. Note that we use wcs rather than scale when the # WCS is more complicated than just a pixel scale. final.drawImage(image=image, wcs=wcs) # Also draw the effective PSF by itself and the optical PSF component alone. image_epsf = galsim.ImageF(image_size, image_size) psf.drawImage(image_epsf, wcs=wcs) # We also draw the optical part of the PSF at its own Nyquist-sampled pixel size # in order to better see the features of the (highly structured) profile. # In this case, we draw a "surface brightness image" using method='sb'. Rather than # integrate the flux over the area of each pixel, this method just samples the surface # brightness value at the locations of the pixel centers. We will encounter a few other # drawing methods as we go through this sequence of demos. cf. demos 7, 8, 10, and 11. image_opticalpsf = optics.drawImage(method='sb') logger.debug('Made image of the profile') # Add a constant sky level to the image. image += sky_level * pixel_scale**2 # This time, we use CCDNoise to model the real noise in a CCD image. It takes a sky level, # gain, and read noise, so it can be a bit more realistic than the simpler GaussianNoise # or PoissonNoise that we used in demos 1 and 2. # # The gain is in units of e-/ADU. Technically, one should also account for quantum efficiency # (QE) of the detector. An ideal CCD has one electron per incident photon, but real CCDs have # QE less than 1, so not every photon triggers an electron. We are essentially folding # the quantum efficiency (and filter transmission and anything else like that) into the gain. # The read_noise value is given as e-/pixel. This is modeled as a pure Gaussian noise # added to the image after applying the pure Poisson noise. image.addNoise(galsim.CCDNoise(rng, gain=gain, read_noise=read_noise)) # Subtract off the sky. image -= sky_level * pixel_scale**2 logger.debug('Added Gaussian and Poisson noise') # Write the images to files. file_name = os.path.join('output', 'demo3.fits') file_name_epsf = os.path.join('output','demo3_epsf.fits') file_name_opticalpsf = os.path.join('output','demo3_opticalpsf.fits') image.write(file_name) image_epsf.write(file_name_epsf) image_opticalpsf.write(file_name_opticalpsf) logger.info('Wrote image to %r', file_name) logger.info('Wrote effective PSF image to %r', file_name_epsf) logger.info('Wrote optics-only PSF image (Nyquist sampled) to %r', file_name_opticalpsf) # Check that the HSM package, which is bundled with GalSim, finds a good estimate # of the shear. results = galsim.hsm.EstimateShear(image, image_epsf) logger.info('HSM reports that the image has observed shape and size:') logger.info(' e1 = %.3f, e2 = %.3f, sigma = %.3f (pixels)', results.observed_shape.e1, results.observed_shape.e2, results.moments_sigma) logger.info('When carrying out Regaussianization PSF correction, HSM reports') logger.info(' e1, e2 = %.3f, %.3f', results.corrected_e1, results.corrected_e2) logger.info('Expected values in the limit that noise and non-Gaussianity are negligible:') # Convention for shear addition is to apply the second term initially followed by the first. # So this needs to be the WCS shear + the galaxy shape in that order. total_shape = galsim.Shear(g1=wcs_g1, g2=wcs_g2) + gal_shape logger.info(' e1, e2 = %.3f, %.3f', total_shape.e1, total_shape.e2)
def test_coadd_image_correct(crazy_wcs, crazy_obj): rng = np.random.RandomState(seed=42) n_coadd = 10 psf_dim = 51 coadd_dim = 53 coadd_cen = (coadd_dim + 1) / 2 se_dim = int(np.ceil(coadd_dim * np.sqrt(2))) if se_dim % 2 == 0: se_dim += 1 se_cen = (se_dim + 1) / 2 scale = 0.2 noise_std = 0.1 world_origin = galsim.CelestialCoord(0 * galsim.degrees, 0 * galsim.degrees) aff = galsim.PixelScale(scale).affine() aff = aff.withOrigin(galsim.PositionD(coadd_cen, coadd_cen), galsim.PositionD(0, 0)) coadd_wcs = galsim.TanWCS( aff, world_origin, ) def _gen_psf_func(wcs, fwhm): def _psf_func(*args, **kargs): return galsim.Gaussian(fwhm=fwhm).drawImage( nx=101, ny=101, wcs=wcs.local(world_pos=world_origin)), galsim.PositionD(0, 0) return _psf_func wgts = [] objs = [] psf_objs = [] exps = [] for _ in range(n_coadd): if crazy_obj: _fwhm = 2.9 * (1.0 + rng.normal() * 0.1) _g1 = rng.normal() * 0.3 _g2 = rng.normal() * 0.3 obj = galsim.Gaussian(fwhm=_fwhm).shear(g1=_g1, g2=_g2) else: obj = galsim.Gaussian(fwhm=2.9).shear(g1=-0.1, g2=0.3) objs.append(obj) if crazy_wcs: shear = galsim.Shear(g1=rng.normal() * 0.01, g2=rng.normal() * 0.01) aff = galsim.ShearWCS(scale, shear).affine() aff = aff.withOrigin(galsim.PositionD(se_cen, se_cen), galsim.PositionD(0, 0)) wcs = galsim.TanWCS( aff, world_origin, ) else: aff = galsim.PixelScale(scale).affine() aff = aff.withOrigin(galsim.PositionD(se_cen, se_cen), galsim.PositionD(0, 0)) wcs = galsim.TanWCS( aff, world_origin, ) _noise = noise_std * (1 + (rng.uniform() - 0.5) * 2 * 0.05) wgts.append(1.0 / _noise**2) bmsk = galsim.ImageI(np.zeros((se_dim, se_dim))) img = obj.drawImage( nx=se_dim, ny=se_dim, wcs=wcs.local(world_pos=world_origin), ) if crazy_obj: _psf_fwhm = 1.0 * (1.0 + rng.normal() * 0.1) else: _psf_fwhm = 1.0 psf = galsim.Gaussian(fwhm=_psf_fwhm) psf_objs.append(psf) exp = make_exp( gsimage=img, bmask=bmsk, noise=_noise, galsim_wcs=wcs, galsim_psf=psf, psf_dim=psf_dim, ) exps.append(exp) coadd_bbox = geom.Box2I( geom.IntervalI(min=0, max=coadd_dim - 1), geom.IntervalI(min=0, max=coadd_dim - 1), ) coadd, exp_info = make_coadd_obs( exps=exps, coadd_wcs=make_dm_wcs(coadd_wcs), coadd_bbox=coadd_bbox, psf_dims=(psf_dim, ) * 2, rng=rng, remove_poisson=False, ) coadd_img = coadd.image coadd_psf = coadd.psf.image wgts = np.array(wgts) / np.sum(wgts) true_coadd_img = galsim.Sum([ obj.withFlux(wgt) for obj, wgt in zip(objs, wgts) ]).drawImage(nx=coadd_dim, ny=coadd_dim, wcs=coadd_wcs.local(world_pos=world_origin)).array true_coadd_psf = galsim.Sum([ obj.withFlux(wgt) for obj, wgt in zip(psf_objs, wgts) ]).drawImage(nx=psf_dim, ny=psf_dim, wcs=coadd_wcs.local(world_pos=world_origin)).array if not crazy_wcs: rtol = 0 atol = 5e-7 else: rtol = 0 atol = 5e-5 coadd_img_err = np.max(np.abs(coadd_img - true_coadd_img)) coadd_psf_err = np.max(np.abs(coadd_psf - true_coadd_psf)) print("image max abs error:", coadd_img_err) print("psf max abs error:", coadd_psf_err) if not np.allclose(coadd_img, true_coadd_img, rtol=rtol, atol=atol): _plot_cmp(coadd_img, true_coadd_img, rtol, atol, crazy_obj, crazy_wcs, "img") if not np.allclose(coadd_psf, true_coadd_psf, rtol=rtol, atol=atol): _plot_cmp(coadd_psf, true_coadd_psf, rtol, atol, crazy_obj, crazy_wcs, "psf") assert np.allclose(coadd_img, true_coadd_img, rtol=rtol, atol=atol) assert np.allclose(coadd_psf, true_coadd_psf, rtol=rtol, atol=atol) assert np.all(np.isfinite(coadd.noise))
import galsim import numpy as np jc = galsim.ShearWCS( 0.263, galsim.Shear(g1=np.random.uniform(low=-0.01, high=0.01), g2=np.random.uniform(low=-0.01, high=0.01))).jacobian() jacobian_dict = { 'dudx': jc.dudx, 'dudy': jc.dudy, 'dvdx': jc.dvdx, 'dvdy': jc.dvdy } gauss_psf = True n_sims = 100
def test_admom_smoke(g1_true, g2_true, wcs_g1, wcs_g2): rng = np.random.RandomState(seed=100) fwhm = 0.9 image_size = 107 cen = (image_size - 1) / 2 gs_wcs = galsim.ShearWCS(0.125, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() obj = galsim.Gaussian(fwhm=fwhm).shear(g1=g1_true, g2=g2_true).withFlux(400) im = obj.drawImage(nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel').array noise = np.sqrt(np.sum(im**2)) / 1e18 wgt = np.ones_like(im) / noise**2 scale = np.sqrt(gs_wcs.pixelArea()) g1arr = [] g2arr = [] Tarr = [] for _ in range(50): shift = rng.uniform(low=-scale / 2, high=scale / 2, size=2) xy = gs_wcs.toImage(galsim.PositionD(shift)) im = obj.shift(dx=shift[0], dy=shift[1]).drawImage( nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel', dtype=np.float64, ).array jac = Jacobian(y=cen + xy.y, x=cen + xy.x, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy) _im = im + (rng.normal(size=im.shape) * noise) obs = Observation(image=_im, weight=wgt, jacobian=jac) Tguess = fwhm_to_T(fwhm) + rng.normal() * 0.01 res = run_admom(obs=obs, guess=Tguess) if res['flags'] == 0: gm = res.get_gmix() _g1, _g2, _T = gm.get_g1g2T() g1arr.append(_g1) g2arr.append(_g2) Tarr.append(_T) fim = res.make_image() assert fim.shape == im.shape res['flags'] = 5 with pytest.raises(RuntimeError): res.make_image() with pytest.raises(RuntimeError): res.get_gmix() g1 = np.mean(g1arr) g2 = np.mean(g2arr) gtol = 1.5e-6 assert np.abs(g1 - g1_true) < gtol, (g1, np.std(g1arr) / np.sqrt(len(g1arr))) assert np.abs(g2 - g2_true) < gtol, (g2, np.std(g2arr) / np.sqrt(len(g2arr))) if g1_true == 0 and g2_true == 0: T = np.mean(Tarr) assert np.abs(T - fwhm_to_T(fwhm)) < 1e-6 with pytest.raises(ValueError): _ = run_admom(None, None) # cover some branches tres = copy.deepcopy(res) tres['flags'] = 0 tres['sums_cov'][:, :] = np.nan tres = ngmix.admom.admom.get_result(tres) assert tres['e1err'] == 9999.0 tres = copy.deepcopy(res) tres['flags'] = 0 tres['pars'][4] = -1 tres = ngmix.admom.admom.get_result(tres) assert tres['flags'] == 0x8
def test_admom_smoke(g1_true, g2_true, wcs_g1, wcs_g2): rng = np.random.RandomState(seed=100) fwhm = 0.9 image_size = 107 cen = (image_size - 1) / 2 gs_wcs = galsim.ShearWCS(0.125, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() obj = galsim.Gaussian(fwhm=fwhm).shear(g1=g1_true, g2=g2_true).withFlux(400) im = obj.drawImage(nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel').array noise = np.sqrt(np.sum(im**2)) / 1e18 wgt = np.ones_like(im) / noise**2 scale = np.sqrt(gs_wcs.pixelArea()) g1arr = [] g2arr = [] Tarr = [] for _ in range(50): shift = rng.uniform(low=-scale / 2, high=scale / 2, size=2) xy = gs_wcs.toImage(galsim.PositionD(shift)) im = obj.shift(dx=shift[0], dy=shift[1]).drawImage(nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel', dtype=np.float64).array jac = Jacobian(y=cen + xy.y, x=cen + xy.x, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy) _im = im + (rng.normal(size=im.shape) * noise) obs = Observation(image=_im, weight=wgt, jacobian=jac) fitter = Admom(obs, rng=rng) try: fitter.go(fwhm_to_T(fwhm) + rng.normal() * 0.01) res = fitter.get_result() if res['flags'] == 0: gm = fitter.get_gmix() _g1, _g2, _T = gm.get_g1g2T() g1arr.append(_g1) g2arr.append(_g2) Tarr.append(_T) except GMixRangeError: pass g1 = np.mean(g1arr) g2 = np.mean(g2arr) gtol = 1e-6 assert np.abs(g1 - g1_true) < gtol, (g1, np.std(g1arr) / np.sqrt(len(g1arr))) assert np.abs(g2 - g2_true) < gtol, (g2, np.std(g2arr) / np.sqrt(len(g2arr))) if g1_true == 0 and g2_true == 0: T = np.mean(Tarr) assert np.abs(T - fwhm_to_T(fwhm)) < 1e-6
def test_ml_fitting_exp_obj_gauss_psf_smoke(g1_true, g2_true, wcs_g1, wcs_g2): rng = np.random.RandomState(seed=10) image_size = 33 cen = (image_size - 1) / 2 gs_wcs = galsim.ShearWCS(0.25, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() scale = np.sqrt(gs_wcs.pixelArea()) g_prior = ngmix.priors.GPriorBA(0.1) cen_prior = ngmix.priors.CenPrior(0, 0, scale, scale) T_prior = ngmix.priors.FlatPrior(0.01, 2) F_prior = ngmix.priors.FlatPrior(1e-4, 1e9) prior = ngmix.joint_prior.PriorSimpleSep(cen_prior, g_prior, T_prior, F_prior) gal = galsim.Exponential(half_light_radius=0.5).shear( g1=g1_true, g2=g2_true).withFlux(400) obj = galsim.Convolve([gal, galsim.Gaussian(fwhm=0.5)]) psf_im = galsim.Gaussian(fwhm=0.5).drawImage(nx=33, ny=33, wcs=gs_wcs, method='no_pixel').array psf_gmix = ngmix.gmix.make_gmix_model( [0, 0, 0, 0, fwhm_to_T(0.5), 1], "gauss") psf_obs = Observation(image=psf_im, gmix=psf_gmix) im = obj.drawImage(nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel').array noise = np.sqrt(np.sum(im**2)) / 1e16 wgt = np.ones_like(im) / noise**2 guess = np.ones(6) * 0.1 guess[0] = 0 guess[1] = 0 guess[2] = g1_true guess[3] = g2_true guess[4] = fwhm_to_T(0.5) guess[5] = 400 g1arr = [] g2arr = [] farr = [] xarr = [] yarr = [] for _ in range(50): shift = rng.uniform(low=-scale / 2, high=scale / 2, size=2) xy = gs_wcs.toImage(galsim.PositionD(shift)) im = obj.shift(dx=shift[0], dy=shift[1]).drawImage(nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel', dtype=np.float64).array jac = Jacobian(y=cen + xy.y, x=cen + xy.x, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy) _im = im + (rng.normal(size=im.shape) * noise) obs = Observation(image=_im, weight=wgt, jacobian=jac, psf=psf_obs) fitter = LMSimple(obs, 'exp', prior=prior) fitter.go(guess + rng.normal(size=6) * 0.01) res = fitter.get_result() if res['flags'] == 0: _g1, _g2, _ = res['g'][0], res['g'][1], res['pars'][4] g1arr.append(_g1) g2arr.append(_g2) farr.append(res['pars'][5]) xarr.append(res['pars'][1]) yarr.append(res['pars'][0]) g1 = np.mean(g1arr) g2 = np.mean(g2arr) gtol = 1e-5 assert np.abs(g1 - g1_true) < gtol assert np.abs(g2 - g2_true) < gtol xerr = np.std(xarr) / np.sqrt(len(xarr)) assert np.abs(np.mean(xarr)) < xerr * 5 yerr = np.std(yarr) / np.sqrt(len(yarr)) assert np.abs(np.mean(yarr)) < yerr * 5
def test_admom_smoke(g1_true, g2_true, wcs_g1, wcs_g2, weight_fac): rng = np.random.RandomState(seed=100) fwhm = 0.9 image_size = 107 cen = (image_size - 1) / 2 gs_wcs = galsim.ShearWCS(0.125, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() obj = galsim.Gaussian(fwhm=fwhm).shear(g1=g1_true, g2=g2_true).withFlux(400) im = obj.drawImage(nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel').array noise = np.sqrt(np.sum(im**2)) / 1e18 wgt = np.ones_like(im) / noise**2 scale = np.sqrt(gs_wcs.pixelArea()) g1arr = [] g2arr = [] Tarr = [] for _ in range(50): shift = rng.uniform(low=-scale / 2, high=scale / 2, size=2) xy = gs_wcs.toImage(galsim.PositionD(shift)) im = obj.shift(dx=shift[0], dy=shift[1]).drawImage(nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel', dtype=np.float64).array jac = Jacobian(y=cen + xy.y, x=cen + xy.x, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy) _im = im + (rng.normal(size=im.shape) * noise) obs = Observation(image=_im, weight=wgt, jacobian=jac) # use a huge weight so that we get the raw moments back out fitter = GaussMom(obs, fwhm * weight_fac, rng=rng) try: fitter.go() res = fitter.get_result() if res['flags'] == 0: if weight_fac > 1: _g1, _g2 = e1e2_to_g1g2(res['e'][0], res['e'][1]) else: _g1, _g2 = res['e'][0], res['e'][1] g1arr.append(_g1) g2arr.append(_g2) Tarr.append(res['pars'][4]) except GMixRangeError: pass g1 = np.mean(g1arr) g2 = np.mean(g2arr) gtol = 1e-9 assert np.abs(g1 - g1_true) < gtol, (g1, np.std(g1arr) / np.sqrt(len(g1arr))) assert np.abs(g2 - g2_true) < gtol, (g2, np.std(g2arr) / np.sqrt(len(g2arr))) # T test should only pass when the weight function is constant so # weight_fac needs to be rally big if g1_true == 0 and g2_true == 0 and weight_fac > 1: T = np.mean(Tarr) assert np.abs(T - fwhm_to_T(fwhm)) < 1e-6
def test_ml_fitting_exp_obj_gauss_psf_smoke( g1_true, g2_true, wcs_g1, wcs_g2, fit_model): rng = np.random.RandomState(seed=10) image_size = 33 cen = (image_size - 1)/2 gs_wcs = galsim.ShearWCS( 0.25, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() scale = np.sqrt(gs_wcs.pixelArea()) gal = galsim.Exponential( half_light_radius=0.5 ).shear( g1=g1_true, g2=g2_true ).withFlux( 400, ) obj = galsim.Convolve([gal, galsim.Gaussian(fwhm=0.5)]) psf_im = galsim.Gaussian(fwhm=0.5).drawImage( nx=33, ny=33, wcs=gs_wcs, method='no_pixel').array psf_gmix = ngmix.gmix.make_gmix_model( [0, 0, 0, 0, fwhm_to_T(0.5), 1], "gauss") psf_obs = Observation( image=psf_im, gmix=psf_gmix ) im = obj.drawImage( nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel', ).array noise = np.sqrt(np.sum(im**2)) / 1e16 wgt = np.ones_like(im) / noise**2 prior = get_prior(fit_model=fit_model, rng=rng, scale=scale) guess = prior.sample() guess[0] = 0 guess[1] = 0 guess[2] = g1_true guess[3] = g2_true guess[4] = fwhm_to_T(0.5) if fit_model == 'bd': guess[5] = 1.0 guess[6] = 0.5 guess[7] = 400 elif fit_model == 'bdf': guess[6] = 400 else: guess[5] = 400 g1arr = [] g2arr = [] farr = [] xarr = [] yarr = [] fitter = Fitter(model=fit_model, prior=prior) for _ in range(50): shift = rng.uniform(low=-scale/2, high=scale/2, size=2) xy = gs_wcs.toImage(galsim.PositionD(shift)) im = obj.shift( dx=shift[0], dy=shift[1] ).drawImage( nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel', dtype=np.float64).array jac = Jacobian( y=cen + xy.y, x=cen + xy.x, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy) _im = im + (rng.normal(size=im.shape) * noise) obs = Observation( image=_im, weight=wgt, jacobian=jac, psf=psf_obs) res = fitter.go( obs=obs, guess=guess + rng.normal(size=guess.size) * 0.01, ) if res['flags'] == 0: _g1, _g2, _ = res['g'][0], res['g'][1], res['pars'][4] g1arr.append(_g1) g2arr.append(_g2) farr.append(res['pars'][5]) xarr.append(res['pars'][1]) yarr.append(res['pars'][0]) g1 = np.mean(g1arr) g2 = np.mean(g2arr) if fit_model == 'bd': # bd fitting is highly degenerate, we don't recover the # ellipticity with as much accuracy gtol = 0.002 else: gtol = 1.0e-5 assert np.abs(g1 - g1_true) < gtol assert np.abs(g2 - g2_true) < gtol xerr = np.std(xarr) / np.sqrt(len(xarr)) assert np.abs(np.mean(xarr)) < xerr * 5 yerr = np.std(yarr) / np.sqrt(len(yarr)) assert np.abs(np.mean(yarr)) < yerr * 5
def test_ml_fitting_galsim_spergel_smoke(): rng = np.random.RandomState(seed=2312) scale = 0.263 prior = get_prior_galsimfit(model='spergel', rng=rng, scale=scale) psf_fwhm = 0.9 psf_image_size = 33 image_size = 51 noise = 0.001 hlr = 0.1 flux = 400 gs_wcs = galsim.ShearWCS( scale, galsim.Shear(g1=0.01, g2=-0.01), ).jacobian() psf_im = galsim.Gaussian(fwhm=psf_fwhm).drawImage( nx=psf_image_size, ny=psf_image_size, wcs=gs_wcs, ).array psf_cen = (psf_image_size - 1.0) / 2.0 psf_jac = ngmix.Jacobian( y=psf_cen, x=psf_cen, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy, ) psf_obs = ngmix.Observation( image=psf_im, jacobian=psf_jac, ) guess = prior.sample() fitter = ngmix.fitting.GalsimSpergelFitter(prior=prior) shift = rng.uniform(low=-scale / 2, high=scale / 2, size=2) xy = gs_wcs.toImage(galsim.PositionD(shift)) g1_true, g2_true = prior.g_prior.sample2d() gal = galsim.Exponential(half_light_radius=hlr, ).shear( g1=g1_true, g2=g2_true).withFlux(flux, ) obj = galsim.Convolve([gal, galsim.Gaussian(fwhm=psf_fwhm)]) im = obj.shift(dx=shift[0], dy=shift[1]).drawImage( nx=image_size, ny=image_size, wcs=gs_wcs, dtype=np.float64, ).array wgt = np.ones_like(im) / noise**2 cen = (np.array(im.shape) - 1) / 2 jac = ngmix.Jacobian( y=cen[0] + xy.y, x=cen[1] + xy.x, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy, ) _im = im + (rng.normal(size=im.shape) * noise) obs = ngmix.Observation( image=_im, weight=wgt, jacobian=jac, psf=psf_obs, ) guess[0] = rng.uniform(low=-0.1, high=0.1) guess[1] = rng.uniform(low=-0.1, high=0.1) guess[2] = rng.uniform(low=-0.1, high=0.1) guess[3] = rng.uniform(low=-0.1, high=0.1) guess[4] = hlr * rng.uniform(low=0.9, high=1.1) guess[5] = rng.uniform(low=0, high=2) guess[6] = flux * rng.uniform(low=0.9, high=1.1) res = fitter.go(obs=obs, guess=guess + rng.normal(size=guess.size) * 0.01) assert res['flags'] == 0
def test_ml_fitting_galsim(wcs_g1, wcs_g2, model, use_prior): rng = np.random.RandomState(seed=2312) scale = 0.263 prior = get_prior_galsimfit(model=model, rng=rng, scale=scale) psf_fwhm = 0.9 psf_image_size = 33 image_size = 51 noise = 0.001 hlr = 0.1 flux = 400 gs_wcs = galsim.ShearWCS( scale, galsim.Shear(g1=wcs_g1, g2=wcs_g2), ).jacobian() psf_im = galsim.Gaussian(fwhm=psf_fwhm).drawImage( nx=psf_image_size, ny=psf_image_size, wcs=gs_wcs, ).array psf_cen = (psf_image_size - 1.0) / 2.0 psf_jac = ngmix.Jacobian( y=psf_cen, x=psf_cen, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy, ) psf_obs = ngmix.Observation( image=psf_im, jacobian=psf_jac, ) guess = prior.sample() g1arr = [] g2arr = [] farr = [] xarr = [] yarr = [] if use_prior: send_prior = prior else: send_prior = None fitter = ngmix.fitting.GalsimFitter(model=model, prior=send_prior) if model == 'exp' and use_prior: ntrial = 50 else: ntrial = 1 for _ in range(ntrial): shift = rng.uniform(low=-scale / 2, high=scale / 2, size=2) xy = gs_wcs.toImage(galsim.PositionD(shift)) g1_true, g2_true = prior.g_prior.sample2d() gal = galsim.Exponential(half_light_radius=hlr, ).shear( g1=g1_true, g2=g2_true).withFlux(flux, ) obj = galsim.Convolve([gal, galsim.Gaussian(fwhm=psf_fwhm)]) im = obj.shift(dx=shift[0], dy=shift[1]).drawImage( nx=image_size, ny=image_size, wcs=gs_wcs, dtype=np.float64, ).array wgt = np.ones_like(im) / noise**2 cen = (np.array(im.shape) - 1) / 2 jac = ngmix.Jacobian( y=cen[0] + xy.y, x=cen[1] + xy.x, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy, ) _im = im + (rng.normal(size=im.shape) * noise) obs = ngmix.Observation( image=_im, weight=wgt, jacobian=jac, psf=psf_obs, ) guess[0] = rng.uniform(low=-0.1, high=0.1) guess[1] = rng.uniform(low=-0.1, high=0.1) guess[2] = rng.uniform(low=-0.1, high=0.1) guess[3] = rng.uniform(low=-0.1, high=0.1) guess[4] = hlr * rng.uniform(low=0.9, high=1.1) guess[5] = flux * rng.uniform(low=0.9, high=1.1) res = fitter.go(obs=obs, guess=guess + rng.normal(size=guess.size) * 0.01) if res['flags'] == 0: _g1, _g2, _ = res['g'][0], res['g'][1], res['pars'][4] g1arr.append(_g1 - g1_true) g2arr.append(_g2 - g2_true) farr.append(res['pars'][5]) xarr.append(res['pars'][1]) yarr.append(res['pars'][0]) if model == 'exp' and use_prior: g1diff = np.mean(g1arr) g2diff = np.mean(g2arr) print('g1vals') print(g1arr) print(g1diff) print('g2vals') print(g2arr) print(g2diff) gtol = 1.0e-5 assert np.abs(g1diff) < gtol assert np.abs(g2diff) < gtol xerr = np.std(xarr) / np.sqrt(len(xarr)) assert np.abs(np.mean(xarr)) < xerr * 5 yerr = np.std(yarr) / np.sqrt(len(yarr)) assert np.abs(np.mean(yarr)) < yerr * 5
def test_ml_max_fitting_gauss_smoke(g1_true, g2_true, wcs_g1, wcs_g2): rng = np.random.RandomState(seed=42) # allow some small relative bias due to approximate exp function tol = 1.0e-5 flux = 400 # in galsim units image_size = 33 cen = (image_size - 1) / 2 gs_wcs = galsim.ShearWCS(0.25, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() scale = np.sqrt(gs_wcs.pixelArea()) g_prior = ngmix.priors.GPriorBA(sigma=0.2, rng=rng) cen_prior = ngmix.priors.CenPrior( cen1=0, cen2=0, sigma1=scale, sigma2=scale, rng=rng, ) T_prior = ngmix.priors.FlatPrior(minval=0.1, maxval=2, rng=rng) F_prior = ngmix.priors.FlatPrior(minval=1e-4, maxval=1e9, rng=rng) prior = ngmix.joint_prior.PriorSimpleSep( cen_prior=cen_prior, g_prior=g_prior, T_prior=T_prior, F_prior=F_prior, ) obj = galsim.Gaussian(fwhm=0.9).shear(g1=g1_true, g2=g2_true).withFlux(flux, ) im = obj.drawImage(nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel').array noise = np.sqrt(np.sum(im**2)) / 1.e6 wgt = np.ones_like(im) / noise**2 g1arr = [] g2arr = [] Tarr = [] farr = [] xarr = [] yarr = [] for _ in range(10): shift = rng.uniform(low=-scale / 2, high=scale / 2, size=2) xy = gs_wcs.toImage(galsim.PositionD(shift)) im = obj.shift(dx=shift[0], dy=shift[1]).drawImage(nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel', dtype=np.float64).array jac = Jacobian( y=cen + xy.y, x=cen + xy.x, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy, ) _im = im + rng.normal(size=im.shape, scale=noise) obs = Observation( image=_im, weight=wgt, jacobian=jac, ) prior = None fitter = Fitter(model='gauss', prior=prior) guess = np.zeros(6) guess[0:2] = rng.uniform(low=-0.1 * scale, high=0.1 * scale, size=2) guess[2] = g1_true + rng.uniform(low=-0.01, high=0.01) guess[3] = g2_true + rng.uniform(low=-0.01, high=0.01) guess[4] = fwhm_to_T(0.9) * rng.uniform(low=0.99, high=1.01) guess[5] = flux * rng.uniform(low=0.99, high=1.01) res = fitter.go(obs=obs, guess=guess) if res['flags'] == 0: _g1, _g2, _T = res['g'][0], res['g'][1], res['pars'][4] g1arr.append(_g1) g2arr.append(_g2) Tarr.append(_T) farr.append(res['pars'][5]) xarr.append(res['pars'][1]) yarr.append(res['pars'][0]) assert len(g1arr) > 0 g1 = np.mean(g1arr) g2 = np.mean(g2arr) assert np.abs(g1 - g1_true) < tol assert np.abs(g2 - g2_true) < tol if g1_true == 0 and g2_true == 0: T = np.mean(Tarr) Ttrue = fwhm_to_T(0.9) assert T / Ttrue - 1 < tol fmn = np.mean(farr) assert np.abs(fmn / flux - 1) < tol xerr = np.std(xarr) / np.sqrt(len(xarr)) assert np.abs(np.mean(xarr)) < xerr * 5 yerr = np.std(yarr) / np.sqrt(len(yarr)) assert np.abs(np.mean(yarr)) < yerr * 5
def run_metacal(*, n_sims, wcs_g1, wcs_g2): """Run metacal and measure m and c. The resulting m and c are printed to STDOUT. Parameters ---------- n_sims : int The number of objects to simulated. wcs_g1 : float The shear on the 1-axis of the WCS Jacobian. wcs_g2 : float The shear on the 2-axis of the WCS Jacobian. """ jc = galsim.ShearWCS(0.263, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() jacobian_dict = { 'dudx': jc.dudx, 'dudy': jc.dudy, 'dvdx': jc.dvdx, 'dvdy': jc.dvdy } swap_g1g2 = False res = _run_metacal(n_sims=n_sims, rng=np.random.RandomState(seed=10), swap_g1g2=swap_g1g2, **jacobian_dict) g1 = np.array([r['noshear']['g'][0] for r in res]) g2 = np.array([r['noshear']['g'][1] for r in res]) g1p = np.array([r['1p']['g'][0] for r in res]) g1m = np.array([r['1m']['g'][0] for r in res]) g2p = np.array([r['2p']['g'][1] for r in res]) g2m = np.array([r['2m']['g'][1] for r in res]) g_true = 0.02 step = 0.01 if swap_g1g2: R11 = (g1p - g1m) / 2 / step R22 = (g2p - g2m) / 2 / step * g_true m, merr, c, cerr = _jack_est(g2, R22, g1, R11) else: R11 = (g1p - g1m) / 2 / step * g_true R22 = (g2p - g2m) / 2 / step m, merr, c, cerr = _jack_est(g1, R11, g2, R22) print("""\ # of sims: {n_sims} wcs_g1 : {wcs_g1:f} wcs_g2 : {wcs_g2:f} dudx : {dudx:f} dudy : {dudy:f} dvdx : {dvdx:f} dvdy : {dvdy:f} m [1e-3] : {m:f} +/- {msd:f} c [1e-4] : {c:f} +/- {csd:f}""".format(n_sims=len(g1), wcs_g1=wcs_g1, wcs_g2=wcs_g2, **jacobian_dict, m=m / 1e-3, msd=merr / 1e-3, c=c / 1e-4, csd=cerr / 1e-4), flush=True)
def test_ml_fitting_gauss_smoke(g1_true, g2_true, wcs_g1, wcs_g2): rng = np.random.RandomState(seed=42) # allow some small relative bias due to approximate exp function tol = 1.0e-5 image_size = 33 cen = (image_size - 1) / 2 gs_wcs = galsim.ShearWCS(0.25, galsim.Shear(g1=wcs_g1, g2=wcs_g2)).jacobian() scale = np.sqrt(gs_wcs.pixelArea()) g_prior = ngmix.priors.GPriorBA(0.2) cen_prior = ngmix.priors.CenPrior(0, 0, scale, scale) T_prior = ngmix.priors.FlatPrior(0.1, 2) F_prior = ngmix.priors.FlatPrior(1e-4, 1e9) prior = ngmix.joint_prior.PriorSimpleSep(cen_prior, g_prior, T_prior, F_prior) obj = galsim.Gaussian(fwhm=0.9).shear(g1=g1_true, g2=g2_true).withFlux(400) im = obj.drawImage(nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel').array noise = np.sqrt(np.sum(im**2)) / 1e16 wgt = np.ones_like(im) / noise**2 g1arr = [] g2arr = [] Tarr = [] farr = [] xarr = [] yarr = [] for _ in range(100): shift = rng.uniform(low=-scale / 2, high=scale / 2, size=2) xy = gs_wcs.toImage(galsim.PositionD(shift)) im = obj.shift(dx=shift[0], dy=shift[1]).drawImage(nx=image_size, ny=image_size, wcs=gs_wcs, method='no_pixel', dtype=np.float64).array jac = Jacobian(y=cen + xy.y, x=cen + xy.x, dudx=gs_wcs.dudx, dudy=gs_wcs.dudy, dvdx=gs_wcs.dvdx, dvdy=gs_wcs.dvdy) _im = im + (rng.normal(size=im.shape) * noise) obs = Observation(image=_im, weight=wgt, jacobian=jac) fitter = LMSimple(obs, 'gauss', prior=prior) guess = np.ones(6) * 0.1 guess[0] = 0 guess[1] = 0 guess[2] = g1_true guess[3] = g2_true guess[4] = fwhm_to_T(0.9) guess[5] = 400 * scale * scale fitter.go(guess + rng.normal(size=6) * 0.01) res = fitter.get_result() if res['flags'] == 0: _g1, _g2, _T = res['g'][0], res['g'][1], res['pars'][4] g1arr.append(_g1) g2arr.append(_g2) Tarr.append(_T) farr.append(res['pars'][5]) xarr.append(res['pars'][1]) yarr.append(res['pars'][0]) g1 = np.mean(g1arr) g2 = np.mean(g2arr) assert np.abs(g1 - g1_true) < tol assert np.abs(g2 - g2_true) < tol if g1_true == 0 and g2_true == 0: T = np.mean(Tarr) Ttrue = fwhm_to_T(0.9) assert T / Ttrue - 1 < tol fmn = np.mean(farr) / scale / scale assert np.abs(fmn / 400 - 1) < tol xerr = np.std(xarr) / np.sqrt(len(xarr)) assert np.abs(np.mean(xarr)) < xerr * 5 yerr = np.std(yarr) / np.sqrt(len(yarr)) assert np.abs(np.mean(yarr)) < yerr * 5