Skip to content

Commit

Permalink
feat: add union_strategy in from_cones and from_boxes #174
Browse files Browse the repository at this point in the history
feat: add union_strategy in from_cones and from_boxes
  • Loading branch information
ManonMarchand authored Sep 11, 2024
2 parents 30f6081 + 5377cd8 commit a23a754
Show file tree
Hide file tree
Showing 6 changed files with 491 additions and 62 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Add option to turn off optimization in `fill`. The optimization degrades MOCs that are
way more precise than the given WCS [#166]
* Creation of a MOC from a zone (defined by min/max ra and dec)`MOC.from_zone`
* Creation of a single MOC from a lot of small cones is faster with the new option in
`MOC.from_cones`: the keyword 'union_strategy' can now take the value 'small_cones'.
* Creation of a single MOC from a lot of cones/boxes is faster with the new option in
`MOC.from_cones`/`MOC.from_boxes`: the keyword 'union_strategy' can now take the value
'small_cones'/'small_boxes' or 'large_cones'/'large_boxes'.
Small cones/boxes is faster for non-overlapping cones/boxes.

## [0.16.2]

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ crate-type = ["cdylib"]

[dependencies]
# moc = { version = "0.15", features = ["storage"] }
moc = { git = 'https://github.com/cds-astro/cds-moc-rust', rev = '5032e6163fd88cb57ba535eca9bff744931cfbb2', features = ["storage"] }
moc = { git = 'https://github.com/cds-astro/cds-moc-rust', rev = '361eb278fe782bfc053433495c33e3f16e20cdbd', features = ["storage"] }
healpix = { package = "cdshealpix", version = "0.6" }
# healpix = { package = "cdshealpix", git = 'https://github.com/cds-astro/cds-healpix-rust', branch = 'master' }
rayon = "1.10"
Expand Down
104 changes: 89 additions & 15 deletions notebooks/01-Creating_MOCs_from_shapes.ipynb

Large diffs are not rendered by default.

98 changes: 75 additions & 23 deletions python/mocpy/moc/moc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1316,9 +1316,9 @@ def from_cones(
max_depth : int
Maximum HEALPix cell resolution.
union_strategy : str, optional
Return the union of all the cones instead of the list of MOCs. For now, the
"small_cones" strategy is implemented. It works better if the cones don't overlap
a lot.
Return the union of all the cones instead of the list of MOCs. Can be either
"small_cones" or "large_cones". The "small_cone" strategy will be faster for
non-overlapping cones and the "large_cones" for the other case.
delta_depth : int, optional
To control the approximation, you can choose to perform the computations at a deeper
depth using the `delta_depth` parameter.
Expand Down Expand Up @@ -1361,8 +1361,25 @@ def from_cones(
)
return cls(index)

if union_strategy == "large_cones":
if radius.isscalar:
radii = np.full(len(lon), Angle(radius).to_value(u.deg))
else:
radii = Angle(radius).to_value(u.deg)
index = mocpy.from_large_cones(
lon,
lat,
radii,
np.uint8(max_depth),
np.uint8(delta_depth),
n_threads,
)
return cls(index)

if union_strategy is not None:
raise ValueError("'union_strategy' can only be None or 'small_cones'.")
raise ValueError(
"'union_strategy' can only be None, 'large_cones', or 'small_cones'."
)

if radius.isscalar:
indices = mocpy.from_same_cones(
Expand Down Expand Up @@ -1480,7 +1497,9 @@ def from_box(cls, lon, lat, a, b, angle, max_depth):

@classmethod
@validate_lonlat
def from_boxes(cls, lon, lat, a, b, angle, max_depth, *, n_threads=None):
def from_boxes(
cls, lon, lat, a, b, angle, max_depth, *, n_threads=None, union_strategy=None
):
"""
Create a MOC from a box/rectangle.
Expand All @@ -1505,11 +1524,16 @@ def from_boxes(cls, lon, lat, a, b, angle, max_depth, *, n_threads=None):
n_threads : int, optional
The number of threads to be used. If this is set to None (default value),
all available threads will be used.
union_strategy : str, optional
Return the union of all the boxes instead of the list of MOCs. Can be either
"small_boxes" or "large_boxes". The "small_boxes" strategy will be faster for
non-overlapping boxes and the "large_boxes" for the other case.
Returns
-------
result : list[`~mocpy.moc.MOC`]
The resulting list of MOCs.
result : list[`~mocpy.MOC`] or `~mocpy.MOC`
The resulting list of MOCs. If 'union_strategy' is not None, returns the MOC
of the union of all boxes instead.
Examples
--------
Expand All @@ -1531,35 +1555,63 @@ def from_boxes(cls, lon, lat, a, b, angle, max_depth, *, n_threads=None):
... a=[10, 20]*u.deg,
... b=[5, 10]*u.deg,
... angle=[30, 10]*u.deg,
... max_depth=10
... max_depth=10,
... union_strategy="small_boxes"
... )
"""
params = [a, b, angle]
max_depth = np.uint8(max_depth)
if any(isinstance(param, u.Quantity) and param.isscalar for param in params):
if not all(isinstance(param, u.Quantity) for param in params):
raise ValueError(
"'a', 'b' and 'angle' should either be all astropy angle-equivalent"
"scalar values or they should all be iterable angle-equivalent. "
" scalar values or they should all be iterable angle-equivalent. "
"They cannot be a mix of both.",
)
indices = mocpy.from_same_boxes(
lon,
lat,
np.float64(a.to_value(u.deg)),
np.float64(b.to_value(u.deg)),
np.float64(angle.to_value(u.deg)),
np.uint8(max_depth),
n_threads=n_threads,
)
return [cls(index) for index in indices]
if union_strategy is None:
indices = mocpy.from_same_boxes(
lon,
lat,
np.float64(a.to_value(u.deg)),
np.float64(b.to_value(u.deg)),
np.float64(angle.to_value(u.deg)),
max_depth,
n_threads=n_threads,
)
return [cls(index) for index in indices]
# no exception for same boxes in the union case
a = np.full(len(lon), Angle(a).to_value(u.deg))
b = np.full(len(lon), Angle(b).to_value(u.deg))
angle = np.full(len(lon), Angle(angle).to_value(u.deg))
else:
a = Angle(a).to_value(u.deg)
b = Angle(b).to_value(u.deg)
angle = Angle(angle).to_value(u.deg)
# different boxes
if union_strategy == "small_boxes":
return cls(
mocpy.from_small_boxes(
lon, lat, a, b, angle, max_depth, n_threads=n_threads
)
)
if union_strategy == "large_boxes":
return cls(
mocpy.from_large_boxes(
lon, lat, a, b, angle, max_depth, n_threads=n_threads
)
)
if union_strategy is not None:
raise ValueError(
"'union_strategy' can only be None, 'large_boxes', or 'small_boxes'."
)

indices = mocpy.from_boxes(
lon,
lat,
np.array(Angle(a).to_value(u.deg), dtype=np.float64),
np.array(Angle(b).to_value(u.deg), dtype=np.float64),
np.array(Angle(angle).to_value(u.deg), dtype=np.float64),
np.uint8(max_depth),
a,
b,
angle,
max_depth,
n_threads=n_threads,
)
return [cls(index) for index in indices]
Expand Down
38 changes: 37 additions & 1 deletion python/mocpy/tests/test_moc.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,21 @@ def test_from_box():
)
assert len(list_boxes_same) == 2
assert list_boxes_same[0] == moc
# union strategies
union_boxes_same = MOC.from_boxes(
lon=[0, 0] * u.deg,
lat=[0, 0] * u.deg,
a=a,
b=b,
angle=30 * u.deg,
max_depth=10,
union_strategy="large_boxes",
)
assert union_boxes_same == MOC.from_box(
lon=0 * u.deg, lat=0 * u.deg, a=a, b=b, angle=30 * u.deg, max_depth=10
)

# not same boxes
a = [Angle("10d"), Angle("20d")]
b = [Angle("2d"), Angle("4d")]
list_boxes_different = MOC.from_boxes(
Expand All @@ -644,7 +659,6 @@ def test_from_box():
)
assert len(list_boxes_different) == 2
assert list_boxes_different[0] == moc

# mixed iterables and scalars raise an error
with pytest.raises(ValueError, match="'a', 'b' and 'angle' should*"):
MOC.from_boxes(
Expand All @@ -655,6 +669,20 @@ def test_from_box():
angle=30 * u.deg,
max_depth=10,
)
# union strategy possible choices
with pytest.raises(
ValueError,
match="'union_strategy' can only be None, 'large_boxes', or 'small_boxes'.",
):
MOC.from_boxes(
lon=[0, 0] * u.deg,
lat=[0, 0] * u.deg,
a=a,
b=b,
angle=[30, 30] * u.deg,
max_depth=10,
union_strategy="large_cones", # voluntary confusion between cones and boxes
)


def test_from_astropy_regions():
Expand Down Expand Up @@ -754,6 +782,14 @@ def test_from_cones():
union_strategy="small_cones",
)
assert isinstance(moc, MOC)
moc2 = MOC.from_cones(
lon,
lat,
radius=radius,
max_depth=14,
union_strategy="large_cones",
)
assert moc == moc2
# different radii
radii = [5, 6] * u.arcmin
cones = MOC.from_cones(lon, lat, radius=radii, max_depth=14)
Expand Down
Loading

0 comments on commit a23a754

Please sign in to comment.