Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions galsim/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
'GalSimSEDError', 'GalSimHSMError', 'GalSimFFTSizeError',
'GalSimConfigError', 'GalSimConfigValueError',
'GalSimNotImplementedError',
'GalSimWarning', 'GalSimDeprecationWarning',
'convert_cpp_errors', 'galsim_warn', ]
'GalSimWarning', 'GalSimDeprecationWarning', 'GalSimFFTSizeWarning',
'convert_cpp_errors', 'galsim_warn', 'galsim_warn_fft' ]

import warnings
from contextlib import contextmanager
Expand Down Expand Up @@ -125,10 +125,6 @@
# GalSimHSMError: Use this for errors from the HSM algorithm. They are emitted in C++, but
# we use `with convert_cpp_errors(GalSimHSMError):` to convert them.
#
# GalSimFFTSizeError: Use this when a requested FFT would exceed the relevant maximum_fft_size
# for the object, so the recommendation is raise this parameter if that
# is possible.
#
# GalSimConfigError: Use this for errors processing a config dict.
#
# GalSimConfigValueError: Use this when a config dict has a value that is invalid. Basically,
Expand Down Expand Up @@ -345,6 +341,9 @@ class GalSimFFTSizeError(GalSimError):
mem: The estimated memory that would be required (in GB) for the FFT.
"""
def __init__(self, message, size):
from .deprecated import depr
depr(GalSimFFTSizeError, 2.7, '',
"Cases that used to raise GalSimFFTSizeError now emit a GalSimFFTSizeWarning instead.")
self.message = message
self.size = size
self.mem = size * size * 24. / 1024**3
Expand Down Expand Up @@ -412,6 +411,31 @@ class GalSimDeprecationWarning(GalSimWarning):
"""
def __repr__(self): return 'galsim.GalSimDeprecationWarning(%r)'%(str(self))

class GalSimFFTSizeWarning(GalSimWarning):
"""A GalSim-specific warning class indicating that a requested FFT exceeds the relevant
maximum_fft_size.

Attributes:
size: The size that was deemed too large
mem: The estimated memory that would be required (in GB) for the FFT.
"""
def __init__(self, message, size):
self.message = message
self.size = size
self.mem = size * size * 24. / 1024**3
message += "\nThe required FFT size would be {0} x {0}, which requires ".format(size)
message += "{0:.2f} GB of memory.\n".format(self.mem)
message += "If you can handle the large FFT and want to suppress this warning,\n"
message += "you may update gsparams.maximum_fft_size."
super(GalSimFFTSizeWarning, self).__init__(message)
def __repr__(self):
return 'galsim.GalSimFFTSizeWarning(%r,%r)'%(self.message, self.size)
def __reduce__(self):
return GalSimFFTSizeWarning, (self.message, self.size)

def galsim_warn_fft(message, size):
warnings.warn(GalSimFFTSizeWarning(message, size))

@contextmanager
def convert_cpp_errors(error_type=GalSimError):
try:
Expand Down
19 changes: 7 additions & 12 deletions galsim/gsobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
from .position import _PositionD, _PositionI, Position, parse_pos_args
from ._utilities import lazy_property
from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError
from .errors import GalSimFFTSizeError, GalSimNotImplementedError, convert_cpp_errors, galsim_warn
from .errors import GalSimNotImplementedError, convert_cpp_errors
from .errors import galsim_warn, galsim_warn_fft
from .image import Image, ImageD, ImageF, ImageCD, ImageCF
from .shear import Shear, _Shear
from .angle import Angle
Expand Down Expand Up @@ -162,17 +163,11 @@ class GSObject:
>>> conv = galsim.Convolve([gal,psf])
>>> im = galsim.Image(1000,1000, scale=0.02) # Note the very small pixel scale!
>>> im = conv.drawImage(image=im) # This uses the default GSParams.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "galsim/gsobject.py", line 1666, in drawImage
added_photons = prof.drawFFT(draw_image, add)
File "galsim/gsobject.py", line 1877, in drawFFT
kimage, wrap_size = self.drawFFT_makeKImage(image)
File "galsim/gsobject.py", line 1802, in drawFFT_makeKImage
raise GalSimFFTSizeError("drawFFT requires an FFT that is too large.", Nk)
galsim.errors.GalSimFFTSizeError: drawFFT requires an FFT that is too large.
galsim/errors.py:437: GalSimFFTSizeWarning: drawFFT requires a very large FFT.
The required FFT size would be 12288 x 12288, which requires 3.38 GB of memory.
If you can handle the large FFT, you may update gsparams.maximum_fft_size.
If you can handle the large FFT and want to suppress this warning,
you may update gsparams.maximum_fft_size.
warnings.warn(GalSimFFTSizeWarning(message, size))
>>> big_fft_params = galsim.GSParams(maximum_fft_size=12300)
>>> conv = galsim.Convolve([gal,psf],gsparams=big_fft_params)
>>> im = conv.drawImage(image=im) # Now it works (but is slow!)
Expand Down Expand Up @@ -1968,7 +1963,7 @@ def drawFFT_makeKImage(self, image):
Nk = int(np.ceil(maxk/dk)) * 2

if Nk > self.gsparams.maximum_fft_size:
raise GalSimFFTSizeError("drawFFT requires an FFT that is too large.", Nk)
galsim_warn_fft("drawFFT requires a very large FFT.", Nk)

bounds = _BoundsI(0,Nk//2,-Nk//2,Nk//2)
if image.dtype in (np.complex128, np.float64, np.int32, np.uint32):
Expand Down
18 changes: 10 additions & 8 deletions galsim/gsparams.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,16 @@ class GSParams:
Parameters:
minimum_fft_size: The minimum size of any FFT that may need to be performed.
[default: 128]
maximum_fft_size: The maximum allowed size of an image for performing an FFT. This
is more about memory use than accuracy. We have this maximum
value to help prevent the user from accidentally trying to perform
an extremely large FFT that crashes the program. Instead, GalSim
will raise an exception indicating that the image is too large,
which is often a sign of an error in the user's code. However, if
you have the memory to handle it, you can raise this limit to
allow the calculation to happen. [default: 8192]
maximum_fft_size: The maximum allowed size of an image for performing an FFT without
warning. This is more about memory use than accuracy. We have this
maximum value to inform a user who accidentally performs an extremely
large FFT why they just crashed the program. GalSim used to
raise an exception indicating that the image is too large,
which is often a sign of an error in the user's code. However, we
now just emit a warning about the large FFT, so if the code crashes
you have some indication of why. If you have the memory to handle it,
you can raise this limit to allow the calculation to happen without
seeing the warning. [default: 8192]
folding_threshold: This sets a maximum amount of real space folding that is allowed,
an effect caused by the periodic nature of FFTs. FFTs implicitly
use periodic boundary conditions, and a profile specified on a
Expand Down
6 changes: 3 additions & 3 deletions galsim/phase_psf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from .interpolatedimage import InterpolatedImage
from .utilities import doc_inherit, OrderedWeakRef, rotate_xy, lazy_property, basestring
from .errors import GalSimValueError, GalSimRangeError, GalSimIncompatibleValuesError
from .errors import GalSimFFTSizeError, galsim_warn
from .errors import galsim_warn, galsim_warn_fft
from .photon_array import TimeSampler, PhotonArray
from .airy import Airy
from .second_kick import SecondKick
Expand Down Expand Up @@ -328,7 +328,7 @@ def _generate_pupil_plane(self):

# Check FFT size
if self._npix > self.gsparams.maximum_fft_size:
raise GalSimFFTSizeError("Created pupil plane array that is too large.",self._npix)
galsim_warn_fft("Created pupil plane array that will need a very large fft.",self._npix)

# Shrink scale such that size = scale * npix exactly.
self._pupil_plane_scale = self._pupil_plane_size / self._npix
Expand Down Expand Up @@ -383,7 +383,7 @@ def _load_pupil_plane(self):

# Check FFT size
if self._npix > self.gsparams.maximum_fft_size:
raise GalSimFFTSizeError("Loaded pupil plane array that is too large.", self._npix)
galsim_warn_fft("Loaded pupil plane array that will need a very large fft.",self._npix)

# Sanity checks
if self._pupil_plane_im.array.shape[0] != self._pupil_plane_im.array.shape[1]:
Expand Down
4 changes: 2 additions & 2 deletions tests/test_chromatic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1493,7 +1493,7 @@ def test_gsparams():
# getting properly forwarded through the internals of ChromaticObjects.
gsparams = galsim.GSParams(maximum_fft_size=16)
gal = galsim.Gaussian(fwhm=1, gsparams=gsparams) * bulge_SED
with assert_raises(galsim.GalSimFFTSizeError):
with assert_warns(galsim.GalSimFFTSizeWarning):
gal.drawImage(bandpass)
assert (galsim.Gaussian(fwhm=1) * bulge_SED) != gal
assert (galsim.Gaussian(fwhm=1) * bulge_SED).withGSParams(gsparams) == gal
Expand All @@ -1503,7 +1503,7 @@ def test_gsparams():
gal = galsim.Gaussian(fwhm=1) * bulge_SED
psf = galsim.Gaussian(sigma=0.4)
final = galsim.Convolve([gal, psf], gsparams=gsparams)
with assert_raises(galsim.GalSimFFTSizeError):
with assert_warns(galsim.GalSimFFTSizeWarning):
final.drawImage(bandpass)

# Use a restrictive one this time, so we test the "most restrictive gsparams" feature
Expand Down
2 changes: 1 addition & 1 deletion tests/test_config_gsobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ def test_sersic():
# and would be rather slow.
gal6a = galsim.config.BuildGSObject(config, 'gal6')[0]
gal6b = galsim.Sersic(n=0.7, half_light_radius=1, flux=50)
with assert_raises(galsim.GalSimFFTSizeError):
with assert_warns(galsim.GalSimFFTSizeWarning):
gsobject_compare(gal6a, gal6b, conv=galsim.Gaussian(sigma=1))

gal7a = galsim.config.BuildGSObject(config, 'gal7')[0]
Expand Down
22 changes: 22 additions & 0 deletions tests/test_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#

import os
import warnings
import numpy as np

import galsim
Expand Down Expand Up @@ -968,6 +969,27 @@ def test_save_photons():
assert np.allclose(np.sum(image.photons.flux), flux, rtol=0.1)
repr(obj)

@timer
def test_galsim_fft_size_error():
"""Test basic usage of GalSimFFTSizeError
"""
# This feela a little gratuitous, since almost certainly no one used GalSimFFTSizeError
# for anything directly. Even catching it seems unlikely. But it was technically part
# of our API, so just deprecate it and make sure it still works appropriately.
err = check_dep(galsim.GalSimFFTSizeError, "Test FFT is too big.", 10240)
print('str = ',str(err))
print('repr = ',repr(err))
assert str(err) == ("Test FFT is too big.\nThe required FFT size would be 10240 x 10240, "
"which requires 2.34 GB of memory.\nIf you can handle "
"the large FFT, you may update gsparams.maximum_fft_size.")
assert err.size == 10240
np.testing.assert_almost_equal(err.mem, 2.34375)
assert isinstance(err, galsim.GalSimError)
with warnings.catch_warnings():
warnings.filterwarnings("ignore",category=galsim.GalSimDeprecationWarning)
check_pickle(err)



if __name__ == "__main__":
runtests(__file__)
26 changes: 11 additions & 15 deletions tests/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,21 +203,6 @@ def test_galsim_hsm_error():
check_pickle(err)


@timer
def test_galsim_fft_size_error():
"""Test basic usage of GalSimFFTSizeError
"""
err = galsim.GalSimFFTSizeError("Test FFT is too big.", 10240)
print('str = ',str(err))
print('repr = ',repr(err))
assert str(err) == ("Test FFT is too big.\nThe required FFT size would be 10240 x 10240, "
"which requires 2.34 GB of memory.\nIf you can handle "
"the large FFT, you may update gsparams.maximum_fft_size.")
assert err.size == 10240
np.testing.assert_almost_equal(err.mem, 2.34375)
assert isinstance(err, galsim.GalSimError)
check_pickle(err)


@timer
def test_galsim_config_error():
Expand Down Expand Up @@ -296,6 +281,17 @@ def test_galsim_deprecation_warning():
assert isinstance(err, UserWarning)
check_pickle(err)

@timer
def test_galsim_fftsize_warning():
"""Test basic usage of GalSimFFTSizeWarning
"""
err = galsim.GalSimFFTSizeWarning("Test", 10240)
print('str = ',str(err))
print('repr = ',repr(err))
assert str(err).startswith("Test")
assert isinstance(err, UserWarning)
check_pickle(err)


if __name__ == "__main__":
runtests(__file__)
18 changes: 10 additions & 8 deletions tests/test_phase_psf.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,20 @@ def test_aperture():
np.testing.assert_almost_equal(stepk, 2.*np.pi/size)
np.testing.assert_almost_equal(maxk, np.pi/scale)

# If the constructed pupil plane would be too large, raise an error
with assert_raises(galsim.GalSimFFTSizeError):
ap = galsim.Aperture(1.7, pupil_plane_scale=1.e-4)
# If the constructed pupil plane would be too large, emit a warning
# For testing this and the next one, we change gsparams.maximum_fft_size, rather than try
# to build or load a really large image.
with assert_warns(galsim.GalSimFFTSizeWarning):
ap = galsim.Aperture(1.7, pupil_plane_scale=0.01,
gsparams=galsim.GSParams(maximum_fft_size=64))
ap._illuminated # Only triggers once we force it to build the illuminated array

# Similar if the given image is too large.
# Here, we change gsparams.maximum_fft_size, rather than build a really large image to load.
with assert_raises(galsim.GalSimFFTSizeError):
with assert_warns(galsim.GalSimFFTSizeWarning):
ap = galsim.Aperture(1.7, pupil_plane_im=im, gsparams=galsim.GSParams(maximum_fft_size=64))
ap._illuminated

# Other choices just give warnings about pupil scale or size being inappropriate
# Other choices give warnings about pupil scale or size being inappropriate
with assert_warns(galsim.GalSimWarning):
ap = galsim.Aperture(diam=1.7, pupil_plane_size=3, pupil_plane_scale=0.03)
ap._illuminated
Expand Down Expand Up @@ -1537,8 +1539,8 @@ def test_t_persistence():
nphot = 1_000_000
photons = psf.drawImage(save_photons=True, method='phot', n_photons=nphot).photons
assert photons.hasAllocatedTimes()
assert np.min(photons.time) > 10.0
assert np.max(photons.time) < 25.0
assert np.min(photons.time) >= 10.0
assert np.max(photons.time) <= 25.0 + 1.e-10 # slight slop to allow for numerical imprecision


@timer
Expand Down
Loading