Skip to content

Commit 26b74b3

Browse files
committed
Add lam/diam/r0 Quantity support to gsobjects
1 parent 99c8391 commit 26b74b3

File tree

6 files changed

+172
-14
lines changed

6 files changed

+172
-14
lines changed

galsim/airy.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
__all__ = [ 'Airy' ]
2020

2121
import math
22+
import astropy.units as u
2223

2324
from . import _galsim
2425
from .gsobject import GSObject
@@ -80,13 +81,16 @@ class Airy(GSObject):
8081
gsparams: An optional `GSParams` argument. [default: None]
8182
"""
8283
_req_params = { }
83-
_opt_params = { "flux" : float , "obscuration" : float, "diam" : float,
84-
"scale_unit" : str }
84+
_opt_params = { "flux" : float ,
85+
"obscuration" : float,
86+
"diam" : (float, u.Quantity),
87+
"scale_unit" : str
88+
}
8589
# Note that this is not quite right; it's true that either lam_over_diam or lam should be
8690
# supplied, but if lam is supplied then diam is required. Errors in which parameters are used
8791
# may be caught either by config or by the python code itself, depending on the particular
8892
# error.
89-
_single_params = [{ "lam_over_diam" : float , "lam" : float } ]
93+
_single_params = [{ "lam_over_diam" : float , "lam" : (float, u.Quantity) } ]
9094

9195
# For an unobscured Airy, we have the following factor which can be derived using the
9296
# integral result given in the Wikipedia page (http://en.wikipedia.org/wiki/Airy_disk),
@@ -108,6 +112,11 @@ def __init__(self, lam_over_diam=None, lam=None, diam=None, obscuration=0., flux
108112
self._flux = float(flux)
109113
self._gsparams = GSParams.check(gsparams)
110114

115+
if isinstance(lam, u.Quantity):
116+
lam = lam.to_value(u.nm)
117+
if isinstance(diam, u.Quantity):
118+
diam = diam.to_value(u.m)
119+
111120
# Parse arguments: either lam_over_diam in arbitrary units, or lam in nm and diam in m.
112121
# If the latter, then get lam_over_diam in units of scale_unit, as specified in
113122
# docstring.

galsim/kolmogorov.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
__all__ = [ 'Kolmogorov' ]
2020

2121
import numpy as np
22+
import astropy.units as u
2223
import math
2324

2425
from . import _galsim
@@ -120,12 +121,21 @@ class Kolmogorov(GSObject):
120121
fwhm: The full-width half-max size
121122
half_light_radius: The half-light radius
122123
"""
123-
_opt_params = { "flux" : float, "r0" : float, "r0_500" : float, "scale_unit" : str }
124+
_opt_params = {
125+
"flux" : float,
126+
"r0" : (float, u.Quantity),
127+
"r0_500" : (float, u.Quantity),
128+
"scale_unit" : str
129+
}
124130
# Note that this is not quite right; it's true that exactly one of these 4 should be supplied,
125131
# but if lam is supplied then r0 is required. Errors in which parameters are used may be
126132
# caught either by config or by the python code itself, depending on the particular error.
127-
_single_params = [ { "lam_over_r0" : float, "fwhm" : float, "half_light_radius" : float,
128-
"lam" : float } ]
133+
_single_params = [ {
134+
"lam_over_r0" : float,
135+
"fwhm" : float,
136+
"half_light_radius" : float,
137+
"lam" : (float, u.Quantity)
138+
} ]
129139

130140
# The FWHM of the Kolmogorov PSF is ~0.976 lambda/r0 (e.g., Racine 1996, PASP 699, 108).
131141
# In SBKolmogorov.cpp we refine this factor to 0.9758634299
@@ -166,6 +176,13 @@ def __init__(self, lam_over_r0=None, fwhm=None, half_light_radius=None, lam=None
166176
self._flux = float(flux)
167177
self._gsparams = GSParams.check(gsparams)
168178

179+
if isinstance(lam, u.Quantity):
180+
lam = lam.to_value(u.nm)
181+
if isinstance(r0, u.Quantity):
182+
r0 = r0.to_value(u.m)
183+
if isinstance(r0_500, u.Quantity):
184+
r0_500 = r0_500.to_value(u.m)
185+
169186
if fwhm is not None :
170187
if any(item is not None for item in (lam_over_r0, lam, r0, r0_500, half_light_radius)):
171188
raise GalSimIncompatibleValuesError(

galsim/phase_psf.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from heapq import heappush, heappop
2222
import numpy as np
23+
import astropy.units as u
2324
import copy
2425

2526
from . import fits
@@ -1807,7 +1808,7 @@ class OpticalPSF(GSObject):
18071808
gsparams: An optional `GSParams` argument. [default: None]
18081809
"""
18091810
_opt_params = {
1810-
"diam": float,
1811+
"diam": (float, u.Quantity),
18111812
"defocus": float,
18121813
"astig1": float,
18131814
"astig2": float,
@@ -1833,7 +1834,7 @@ class OpticalPSF(GSObject):
18331834
"pupil_plane_size": float,
18341835
"scale_unit": str,
18351836
"fft_sign": str}
1836-
_single_params = [{"lam_over_diam": float, "lam": float}]
1837+
_single_params = [{"lam_over_diam": float, "lam": (float, u.Quantity)}]
18371838

18381839
_has_hard_edges = False
18391840
_is_axisymmetric = False
@@ -1852,6 +1853,10 @@ def __init__(self, lam_over_diam=None, lam=None, diam=None, tip=0., tilt=0., def
18521853
pupil_angle=0.*radians, scale_unit=arcsec, fft_sign='+', gsparams=None,
18531854
_force_stepk=0., _force_maxk=0.,
18541855
suppress_warning=False, geometric_shooting=False):
1856+
if isinstance(lam, u.Quantity):
1857+
lam = lam.to_value(u.nm)
1858+
if isinstance(diam, u.Quantity):
1859+
diam = diam.to_value(u.m)
18551860
if fft_sign not in ['+', '-']:
18561861
raise GalSimValueError("Invalid fft_sign", fft_sign, allowed_values=['+','-'])
18571862
if isinstance(scale_unit, str):

galsim/second_kick.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
__all__ = [ 'SecondKick' ]
2020

21+
import astropy.units as u
22+
2123
from . import _galsim
2224
from .gsobject import GSObject
2325
from .gsparams import GSParams
@@ -84,7 +86,11 @@ class SecondKick(GSObject):
8486
construct one (e.g., 'arcsec', 'radians', etc.). [default: galsim.arcsec]
8587
gsparams: An optional `GSParams` argument. [default: None]
8688
"""
87-
_req_params = { "lam" : float, "r0" : float, "diam" : float }
89+
_req_params = {
90+
"lam" : (float, u.Quantity),
91+
"r0" : (float, u.Quantity),
92+
"diam" : (float, u.Quantity),
93+
}
8894
_opt_params = { "obscuration" : float, "kcrit" : float, "flux" : float, "scale_unit" : str }
8995

9096
_has_hard_edges = False
@@ -94,6 +100,13 @@ class SecondKick(GSObject):
94100

95101
def __init__(self, lam, r0, diam, obscuration=0, kcrit=0.2, flux=1,
96102
scale_unit=arcsec, gsparams=None):
103+
if isinstance(lam, u.Quantity):
104+
lam = lam.to_value(u.nm)
105+
if isinstance(r0, u.Quantity):
106+
r0 = r0.to_value(u.m)
107+
if isinstance(diam, u.Quantity):
108+
diam = diam.to_value(u.m)
109+
97110
if isinstance(scale_unit, str):
98111
self._scale_unit = AngleUnit.from_name(scale_unit)
99112
else:

galsim/vonkarman.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
__all__ = [ 'VonKarman' ]
2020

2121
import numpy as np
22+
import astropy.units as u
2223

2324
from . import _galsim
2425
from .gsobject import GSObject
@@ -102,9 +103,17 @@ class VonKarman(GSObject):
102103
keyword. [default: False]
103104
gsparams: An optional `GSParams` argument. [default: None]
104105
"""
105-
_req_params = { "lam" : float }
106-
_opt_params = { "L0" : float, "flux" : float, "scale_unit" : str, "do_delta" : bool }
107-
_single_params = [ { "r0" : float, "r0_500" : float } ]
106+
_req_params = { "lam" : (float, u.Quantity) }
107+
_opt_params = {
108+
"L0" : (float, u.Quantity),
109+
"flux" : float,
110+
"scale_unit" : str,
111+
"do_delta" : bool
112+
}
113+
_single_params = [ {
114+
"r0" : (float, u.Quantity),
115+
"r0_500" : (float, u.Quantity)
116+
} ]
108117

109118
_has_hard_edges = False
110119
_is_axisymmetric = True
@@ -113,6 +122,15 @@ class VonKarman(GSObject):
113122

114123
def __init__(self, lam, r0=None, r0_500=None, L0=25.0, flux=1, scale_unit=arcsec,
115124
force_stepk=0.0, do_delta=False, suppress_warning=False, gsparams=None):
125+
if isinstance(lam, u.Quantity):
126+
lam = lam.to_value(u.nm)
127+
if isinstance(r0, u.Quantity):
128+
r0 = r0.to_value(u.m)
129+
if isinstance(r0_500, u.Quantity):
130+
r0_500 = r0_500.to_value(u.m)
131+
if isinstance(L0, u.Quantity):
132+
L0 = L0.to_value(u.m)
133+
116134
# We lose stability if L0 gets too large. This should be close enough to infinity for
117135
# all practical purposes though.
118136
if L0 > 1e10:

tests/test_config_gsobject.py

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import logging
2020
import numpy as np
21+
import astropy.units as u
2122
import os
2223
import sys
2324

@@ -222,6 +223,7 @@ def test_airy():
222223
'gsparams' : { 'xvalue_accuracy' : 1.e-2 }
223224
},
224225
'gal6' : { 'type' : 'Airy' , 'lam' : 400., 'diam' : 4.0, 'scale_unit' : 'arcmin' },
226+
'gal7' : { 'type' : 'Airy' , 'lam' : 400*u.nm, 'diam' : '4000 mm', 'scale_unit' : 'arcmin' },
225227
'bad1' : { 'type' : 'Airy' , 'lam_over_diam' : 0.4, 'lam' : 400, 'diam' : 10 },
226228
'bad2' : { 'type' : 'Airy' , 'flux' : 1.3 },
227229
'bad3' : { 'type' : 'Airy' , 'lam_over_diam' : 0.4, 'obsc' : 0.3, 'flux' : 100 },
@@ -259,6 +261,8 @@ def test_airy():
259261
gal6a = galsim.config.BuildGSObject(config, 'gal6')[0]
260262
gal6b = galsim.Airy(lam=400., diam=4., scale_unit=galsim.arcmin)
261263
gsobject_compare(gal6a, gal6b)
264+
gal7a = galsim.config.BuildGSObject(config, 'gal7')[0]
265+
gsobject_compare(gal6a, gal7a)
262266

263267
# Make sure they don't match when using the default GSParams
264268
gal5c = galsim.Airy(lam_over_diam=45)
@@ -298,6 +302,8 @@ def test_kolmogorov():
298302
'gal5' : { 'type' : 'Kolmogorov' , 'lam_over_r0' : 1, 'flux' : 50,
299303
'gsparams' : { 'integration_relerr' : 1.e-2, 'integration_abserr' : 1.e-4 }
300304
},
305+
'gal6' : { 'type' : 'Kolmogorov' , 'lam' : '400 nm', 'r0_500' : '15 cm' },
306+
'gal7' : { 'type' : 'Kolmogorov' , 'lam' : '$4000*u.Angstrom', 'r0' : 0.18 },
301307
'bad1' : { 'type' : 'Kolmogorov' , 'fwhm' : 2, 'lam_over_r0' : 3, 'flux' : 100 },
302308
'bad2' : { 'type' : 'Kolmogorov', 'flux' : 100 },
303309
'bad3' : { 'type' : 'Kolmogorov' , 'lam_over_r0' : 2, 'lam' : 400, 'r0' : 0.15 },
@@ -334,13 +340,103 @@ def test_kolmogorov():
334340
with assert_raises(AssertionError):
335341
gsobject_compare(gal5a, gal5c)
336342

343+
gal6a = galsim.config.BuildGSObject(config, 'gal6')[0]
344+
gal6b = galsim.Kolmogorov(lam=400*u.nm, r0_500=15*u.cm)
345+
gsobject_compare(gal6a, gal6b)
346+
347+
gal7a = galsim.config.BuildGSObject(config, 'gal7')[0]
348+
gal7b = galsim.Kolmogorov(lam=4000*u.Angstrom, r0=0.18)
349+
gsobject_compare(gal7a, gal7b)
350+
337351
with assert_raises(galsim.GalSimConfigError):
338352
galsim.config.BuildGSObject(config, 'bad1')
339353
with assert_raises(galsim.GalSimConfigError):
340354
galsim.config.BuildGSObject(config, 'bad2')
341355
with assert_raises(galsim.GalSimConfigError):
342356
galsim.config.BuildGSObject(config, 'bad3')
343357

358+
359+
@timer
360+
def test_VonKarman():
361+
"""Test various ways to build a VonKarman
362+
"""
363+
config = {
364+
'gal1' : { 'type' : 'VonKarman' , 'lam' : 500, 'r0' : 0.2 },
365+
'gal2' : { 'type' : 'VonKarman' , 'lam' : 760, 'r0_500' : 0.2 },
366+
'gal3' : { 'type' : 'VonKarman' , 'lam' : 450*u.nm, 'r0_500' : '20 cm', 'flux' : 1.e6,
367+
'ellip' : { 'type' : 'QBeta' , 'q' : 0.6, 'beta' : 0.39 * galsim.radians }
368+
},
369+
'gal4' : { 'type' : 'VonKarman' , 'lam' : 500, 'r0' : 0.2,
370+
'dilate' : 3, 'ellip' : galsim.Shear(e1=0.3),
371+
'rotate' : 12 * galsim.degrees,
372+
'lens' : {
373+
'shear' : galsim.Shear(g1=0.03, g2=-0.05),
374+
'mu' : 1.03,
375+
},
376+
'shift' : { 'type' : 'XY', 'x' : 0.7, 'y' : -1.2 },
377+
},
378+
'gal5' : { 'type' : 'VonKarman' , 'lam' : 500, 'r0' : 0.2, 'do_delta' : True,
379+
'gsparams' : { 'integration_relerr' : 1.e-2, 'integration_abserr' : 1.e-4 }
380+
},
381+
'bad1' : { 'type' : 'VonKarman' , 'fwhm' : 2, 'lam_over_r0' : 3, 'flux' : 100 },
382+
'bad2' : { 'type' : 'VonKarman', 'flux' : 100 },
383+
'bad3' : { 'type' : 'VonKarman' , 'lam' : 400, 'r0' : 0.15, 'r0_500' : 0.12 },
384+
}
385+
386+
gal1a = galsim.config.BuildGSObject(config, 'gal1')[0]
387+
gal1b = galsim.VonKarman(lam = 500, r0 = 0.2)
388+
gsobject_compare(gal1a, gal1b)
389+
390+
gal2a = galsim.config.BuildGSObject(config, 'gal2')[0]
391+
gal2b = galsim.VonKarman(lam = 760, r0_500 = 0.2)
392+
gsobject_compare(gal2a, gal2b)
393+
394+
gal3a = galsim.config.BuildGSObject(config, 'gal3')[0]
395+
gal3b = galsim.VonKarman(lam = 450*u.nm, r0_500 = 20*u.cm, flux = 1.e6)
396+
gal3b = gal3b.shear(q = 0.6, beta = 0.39 * galsim.radians)
397+
gsobject_compare(gal3a, gal3b)
398+
399+
400+
gal4a = galsim.config.BuildGSObject(config, 'gal4')[0]
401+
gal4b = galsim.VonKarman(lam = 500, r0 = 0.2)
402+
gal4b = gal4b.dilate(3).shear(e1 = 0.3).rotate(12 * galsim.degrees)
403+
gal4b = gal4b.lens(0.03, -0.05, 1.03).shift(dx = 0.7, dy = -1.2)
404+
gsobject_compare(gal4a, gal4b)
405+
406+
gal5a = galsim.config.BuildGSObject(config, 'gal5')[0]
407+
gsparams = galsim.GSParams(integration_relerr=1.e-2, integration_abserr=1.e-4)
408+
gal5b = galsim.VonKarman(lam=500, r0=0.2, do_delta=True, gsparams=gsparams)
409+
gsobject_compare(gal5a, gal5b)
410+
411+
with assert_raises(galsim.GalSimConfigError):
412+
galsim.config.BuildGSObject(config, 'bad1')
413+
with assert_raises(galsim.GalSimConfigError):
414+
galsim.config.BuildGSObject(config, 'bad2')
415+
with assert_raises(galsim.GalSimConfigError):
416+
galsim.config.BuildGSObject(config, 'bad3')
417+
418+
419+
@timer
420+
def test_secondKick():
421+
config = {
422+
'gal1' : { 'type' : 'SecondKick' , 'lam' : 500, 'r0' : 0.2, 'diam': 4.0 },
423+
'gal2' : { 'type' : 'SecondKick' , 'lam' : '0.5 micron', 'r0' : '10 cm', 'diam': 8.2*u.m, 'flux' : 100 },
424+
'gal3' : { 'type' : 'SecondKick' , 'lam' : '$2*450*u.nm', 'r0' : 0.2, 'diam': 4.0, 'obscuration' : 0.2, 'kcrit' : 0.1 },
425+
}
426+
427+
gal1a = galsim.config.BuildGSObject(config, 'gal1')[0]
428+
gal1b = galsim.SecondKick(lam = 500, r0 = 0.2, diam=4.0)
429+
gsobject_compare(gal1a, gal1b)
430+
431+
gal2a = galsim.config.BuildGSObject(config, 'gal2')[0]
432+
gal2b = galsim.SecondKick(lam = 0.5*u.micron, r0 = 10*u.cm, diam=8.2*u.m, flux = 100)
433+
gsobject_compare(gal2a, gal2b)
434+
435+
gal3a = galsim.config.BuildGSObject(config, 'gal3')[0]
436+
gal3b = galsim.SecondKick(lam = 2*450, r0 = 0.2, diam=4.0, obscuration=0.2, kcrit=0.1)
437+
gsobject_compare(gal3a, gal3b)
438+
439+
344440
@timer
345441
def test_opticalpsf():
346442
"""Test various ways to build a OpticalPSF
@@ -371,10 +467,10 @@ def test_opticalpsf():
371467
'pupil_plane_im' :
372468
os.path.join(".","Optics_comparison_images","sample_pupil_rolled.fits"),
373469
'pupil_angle' : 27.*galsim.degrees },
374-
'gal6' : {'type' : 'OpticalPSF' , 'lam' : 874.0, 'diam' : 7.4, 'flux' : 70.,
470+
'gal6' : {'type' : 'OpticalPSF' , 'lam' : '874 nm', 'diam' : '7.4 m', 'flux' : 70.,
375471
'aberrations' : [0.06, 0.12, -0.08, 0.07, 0.04, 0.0, 0.0, -0.13],
376472
'obscuration' : 0.1 },
377-
'gal7' : {'type' : 'OpticalPSF' , 'lam' : 874.0, 'diam' : 7.4, 'aberrations' : []},
473+
'gal7' : {'type' : 'OpticalPSF' , 'lam' : 0.874*u.micron, 'diam' : '$740*u.cm', 'aberrations' : []},
378474
'bad1' : {'type' : 'OpticalPSF' , 'lam' : 874.0, 'diam' : 7.4, 'lam_over_diam' : 0.2},
379475
'bad2' : {'type' : 'OpticalPSF' , 'lam_over_diam' : 0.2,
380476
'aberrations' : "0.06, 0.12, -0.08, 0.07, 0.04, 0.0, 0.0, -0.13"},

0 commit comments

Comments
 (0)