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
19 changes: 19 additions & 0 deletions autoarray/dataset/abstract/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,25 @@ def __init__(
"""
) from e

# Guarding on the base class covers `Imaging`, `Interferometer` and every other
# dataset subclass. Array shapes are static under JAX, so no tracer gate is
# needed here (unlike the scalar guards in `autoarray.validate`).
data_shape = getattr(data, "shape_native", None)
noise_map_shape = getattr(noise_map, "shape_native", None)

if (
data_shape is not None
and noise_map_shape is not None
and tuple(data_shape) != tuple(noise_map_shape)
):
raise exc.DatasetException(
f"noise_map must have the same shape as data; got data with "
f"shape_native {tuple(data_shape)!r} and noise_map with shape_native "
f"{tuple(noise_map_shape)!r}. Every fit quantity pairs a data value "
f"with its noise value pixel-by-pixel, so a mismatch has no "
f"well-defined meaning"
)

self.noise_map = noise_map

self.over_sample_size_lp = (
Expand Down
21 changes: 21 additions & 0 deletions autoarray/geometry/geometry_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Tuple, Union

from autoarray import type as ty
from autoarray import validate


def convert_shape_native_1d(shape_native: Union[int, Tuple[int]]) -> Tuple[int]:
Expand Down Expand Up @@ -46,8 +47,15 @@ def convert_pixel_scales_1d(pixel_scales: ty.PixelScales) -> Tuple[float]:
-------
Tuple[float]
The pixel scale as a 1-element tuple `(float,)`.

Raises
------
ValueError
If any entry is a concrete scalar which is not finite and above zero.
"""

validate.validate_pixel_scales(pixel_scales=pixel_scales)

if type(pixel_scales) is float:
pixel_scales = (pixel_scales,)

Expand Down Expand Up @@ -205,8 +213,21 @@ def convert_pixel_scales_2d(pixel_scales: ty.PixelScales) -> Tuple[float, float]
-------
Tuple[float, float]
The pixel scale as a 2-element tuple `(float, float)`.

Raises
------
ValueError
If any entry is a concrete scalar which is not finite and above zero.

Notes
-----
This is the single chokepoint every ``Mask2D`` factory and ``Grid2D.uniform``
funnel their ``pixel_scales`` through, so validating here covers them all rather
than repeating a guard at each construction site.
"""

validate.validate_pixel_scales(pixel_scales=pixel_scales)

if type(pixel_scales) is float:
pixel_scales = (pixel_scales, pixel_scales)

Expand Down
38 changes: 38 additions & 0 deletions autoarray/inversion/regularization/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,48 @@
import numpy as np
from typing import Optional, TYPE_CHECKING

from autoarray import validate

if TYPE_CHECKING:
from autoarray.inversion.linear_obj.linear_obj import LinearObj


def validate_coefficient(coefficient, name: str = "coefficient"):
"""
Raise if a regularization coefficient is a concrete scalar which is negative or
non-finite.

Every regularization scheme calls this from its constructor, so the message for
this class of mistake is written once here rather than per scheme.

Zero is permitted: it is a degenerate but meaningful request for no regularization.
Negative is not, and is **not** inert despite appearances — see the note below.

Coefficients are free model parameters, so under a JAX-traced fit this receives a
tracer rather than a number. `autoarray.validate` gates on concreteness before
comparing, so the guard costs nothing inside a trace.

Parameters
----------
coefficient
The regularization coefficient to validate.
name
The parameter's name, used in the error message (schemes with more than one
coefficient pass their own, e.g. ``inner_coefficient``).
"""
validate.validate_non_negative_finite(
value=coefficient,
name=name,
extra=(
"A regularization coefficient sets the strength of the smoothing applied "
"to the reconstruction, which cannot be negative. A negative value is not "
"inert: `regularization_matrix_from` squares it, which hides the sign, but "
"`regularization_weights_from` returns it unsquared and so leaks negative "
"regularization weights into every consumer of that method"
),
)


class AbstractRegularization:
is_split_regularization = False
"""
Expand Down
3 changes: 3 additions & 0 deletions autoarray/inversion/regularization/adapt.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from autoarray.inversion.linear_obj.linear_obj import LinearObj

from autoarray.inversion.regularization.abstract import AbstractRegularization
from autoarray.inversion.regularization.abstract import validate_coefficient


def adapt_regularization_weights_from(
Expand Down Expand Up @@ -193,7 +194,9 @@ def __init__(

super().__init__()

validate_coefficient(coefficient=inner_coefficient, name="inner_coefficient")
self.inner_coefficient = inner_coefficient
validate_coefficient(coefficient=outer_coefficient, name="outer_coefficient")
self.outer_coefficient = outer_coefficient
self.signal_scale = signal_scale

Expand Down
3 changes: 3 additions & 0 deletions autoarray/inversion/regularization/adapt_split_zeroth.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
from autoarray.inversion.regularization.adapt import Adapt
from autoarray.inversion.regularization.brightness_zeroth import BrightnessZeroth
from autoarray.inversion.regularization import regularization_util
from autoarray.inversion.regularization.abstract import validate_coefficient


class AdaptSplitZeroth(Adapt):
is_split_regularization = True

def __init__(
self,
zeroth_coefficient: float = 1.0,
Expand Down Expand Up @@ -77,6 +79,7 @@ def __init__(
low signal regions.
"""

validate_coefficient(coefficient=zeroth_coefficient, name="zeroth_coefficient")
self.zeroth_coefficient = zeroth_coefficient
self.zeroth_signal_scale = zeroth_signal_scale

Expand Down
2 changes: 2 additions & 0 deletions autoarray/inversion/regularization/brightness_zeroth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from autoarray.inversion.linear_obj.linear_obj import LinearObj

from autoarray.inversion.regularization.abstract import AbstractRegularization
from autoarray.inversion.regularization.abstract import validate_coefficient


def brightness_zeroth_regularization_weights_from(
Expand Down Expand Up @@ -104,6 +105,7 @@ def __init__(

super().__init__()

validate_coefficient(coefficient=coefficient)
self.coefficient = coefficient
self.signal_scale = signal_scale

Expand Down
2 changes: 2 additions & 0 deletions autoarray/inversion/regularization/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from autoarray.inversion.linear_obj.linear_obj import LinearObj

from autoarray.inversion.regularization.abstract import AbstractRegularization
from autoarray.inversion.regularization.abstract import validate_coefficient


def constant_regularization_matrix_from(
Expand Down Expand Up @@ -101,6 +102,7 @@ def __init__(self, coefficient: float = 1.0):
The regularization coefficient which controls the degree of smooth of the inversion reconstruction.
"""

validate_coefficient(coefficient=coefficient)
self.coefficient = coefficient

super().__init__()
Expand Down
5 changes: 5 additions & 0 deletions autoarray/inversion/regularization/constant_zeroth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from autoarray.inversion.linear_obj.linear_obj import LinearObj

from autoarray.inversion.regularization.abstract import AbstractRegularization
from autoarray.inversion.regularization.abstract import validate_coefficient


def constant_zeroth_regularization_matrix_from(
Expand Down Expand Up @@ -76,7 +77,11 @@ class ConstantZeroth(AbstractRegularization):
def __init__(self, coefficient_neighbor=1.0, coefficient_zeroth=1.0):
super().__init__()

validate_coefficient(
coefficient=coefficient_neighbor, name="coefficient_neighbor"
)
self.coefficient_neighbor = coefficient_neighbor
validate_coefficient(coefficient=coefficient_zeroth, name="coefficient_zeroth")
self.coefficient_zeroth = coefficient_zeroth

def regularization_weights_from(self, linear_obj: LinearObj, xp=np) -> np.ndarray:
Expand Down
2 changes: 2 additions & 0 deletions autoarray/inversion/regularization/curvature_mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from autoarray.inversion.regularization.abstract import AbstractRegularization
from autoarray.operators import derivative_util
from autoarray.inversion.regularization.abstract import validate_coefficient


def curvature_reg_matrix_via_mask_from(mask, pixel_scale: float = 1.0) -> np.ndarray:
Expand Down Expand Up @@ -71,6 +72,7 @@ def __init__(self, coefficient: float = 1.0):
The regularization coefficient which multiplies the matrix,
setting the strength of the smoothing.
"""
validate_coefficient(coefficient=coefficient)
self.coefficient = coefficient

super().__init__()
Expand Down
2 changes: 2 additions & 0 deletions autoarray/inversion/regularization/exponential_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from autoarray.inversion.linear_obj.linear_obj import LinearObj

from autoarray.inversion.regularization.abstract import AbstractRegularization
from autoarray.inversion.regularization.abstract import validate_coefficient


def exp_cov_matrix_from(
Expand Down Expand Up @@ -110,6 +111,7 @@ def __init__(
convention assumes ``C_ii ~ 1``, which holds for this unweighted kernel but not
for the adaptive one; see :func:`apply_jitter` for why and when to switch.
"""
validate_coefficient(coefficient=coefficient)
self.coefficient = coefficient
self.scale = scale
self.jitter = jitter
Expand Down
2 changes: 2 additions & 0 deletions autoarray/inversion/regularization/fourth_order_mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from autoarray.inversion.linear_obj.linear_obj import LinearObj

from autoarray.inversion.regularization.abstract import AbstractRegularization
from autoarray.inversion.regularization.abstract import validate_coefficient
from autoarray.operators import derivative_util


Expand Down Expand Up @@ -74,6 +75,7 @@ def __init__(self, coefficient: float = 1.0):
The regularization coefficient which multiplies the matrix,
setting the strength of the smoothing.
"""
validate_coefficient(coefficient=coefficient)
self.coefficient = coefficient

super().__init__()
Expand Down
2 changes: 2 additions & 0 deletions autoarray/inversion/regularization/gaussian_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from autoarray.inversion.linear_obj.linear_obj import LinearObj

from autoarray.inversion.regularization.abstract import AbstractRegularization
from autoarray.inversion.regularization.abstract import validate_coefficient


def gauss_cov_matrix_from(
Expand Down Expand Up @@ -100,6 +101,7 @@ def __init__(
convention assumes ``C_ii ~ 1``, which holds for this unweighted kernel but not
for the adaptive one; see :func:`apply_jitter` for why and when to switch.
"""
validate_coefficient(coefficient=coefficient)
self.coefficient = coefficient
self.scale = scale
self.jitter = jitter
Expand Down
3 changes: 3 additions & 0 deletions autoarray/inversion/regularization/matern_adapt_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
quadratic_form_via_cholesky,
)
from autoarray.inversion.regularization.adapt import adapt_regularization_weights_from
from autoarray.inversion.regularization.abstract import validate_coefficient


class MaternAdaptKernel(MaternKernel):
Expand Down Expand Up @@ -86,7 +87,9 @@ def __init__(
jitter=jitter,
jitter_relative=jitter_relative,
)
validate_coefficient(coefficient=inner_coefficient, name="inner_coefficient")
self.inner_coefficient = inner_coefficient
validate_coefficient(coefficient=outer_coefficient, name="outer_coefficient")
self.outer_coefficient = outer_coefficient
self.signal_scale = signal_scale

Expand Down
2 changes: 2 additions & 0 deletions autoarray/inversion/regularization/matern_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from autoarray.inversion.linear_obj.linear_obj import LinearObj

from autoarray.inversion.regularization.abstract import AbstractRegularization
from autoarray.inversion.regularization.abstract import validate_coefficient


def kv_xp(v, z, xp=np):
Expand Down Expand Up @@ -318,6 +319,7 @@ def __init__(
for the adaptive one; see :func:`apply_jitter` for why and when to switch.
"""

validate_coefficient(coefficient=coefficient)
self.coefficient = coefficient
self.scale = scale
self.nu = nu
Expand Down
2 changes: 2 additions & 0 deletions autoarray/inversion/regularization/zeroth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from autoarray.inversion.linear_obj.linear_obj import LinearObj

from autoarray.inversion.regularization.abstract import AbstractRegularization
from autoarray.inversion.regularization.abstract import validate_coefficient


def zeroth_regularization_matrix_from(
Expand Down Expand Up @@ -70,6 +71,7 @@ def __init__(self, coefficient: float = 1.0):
The regularization coefficient which controls the degree of smooth of the inversion reconstruction.
"""

validate_coefficient(coefficient=coefficient)
self.coefficient = coefficient

super().__init__()
Expand Down
22 changes: 22 additions & 0 deletions autoarray/mask/mask_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from autoarray import exc
from autoarray import type as ty
from autoarray import validate
from autoarray.geometry.geometry_2d import Geometry2D
from autoarray.mask.derive.mask_2d import DeriveMask2D
from autoarray.mask.derive.grid_2d import DeriveGrid2D
Expand Down Expand Up @@ -219,6 +220,13 @@ def __init__(
if len(mask.shape) != 2:
raise exc.MaskException("The input mask is not a two dimensional array")

# Every `Mask2D` factory returns through this constructor, and `Grid2D.uniform`
# reaches it via `Grid2D.no_mask` -> `Mask2D.all_false`, so a degenerate shape
# is caught once here rather than at each construction site.
validate.validate_shape_native(
shape_native=mask.shape, name="shape_native", exc_type=exc.MaskException
)

super().__init__(
mask=mask,
origin=origin,
Expand Down Expand Up @@ -426,6 +434,12 @@ def circular_annular(
and visa versa.
"""

validate.validate_radii_ordered(
inner_radius=inner_radius,
outer_radius=outer_radius,
exc_type=exc.MaskException,
)

pixel_scales = geometry_util.convert_pixel_scales_2d(pixel_scales=pixel_scales)

mask = mask_2d_util.mask_2d_circular_annular_from(
Expand Down Expand Up @@ -549,6 +563,14 @@ def elliptical_annular(
If `True`, the `bool`'s of the input `mask` are inverted, for example `False`'s become `True`
and visa versa.
"""
validate.validate_radii_ordered(
inner_radius=inner_major_axis_radius,
outer_radius=outer_major_axis_radius,
inner_name="inner_major_axis_radius",
outer_name="outer_major_axis_radius",
exc_type=exc.MaskException,
)

pixel_scales = geometry_util.convert_pixel_scales_2d(pixel_scales=pixel_scales)

mask = mask_2d_util.mask_2d_elliptical_annular_from(
Expand Down
Loading
Loading