Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add from_zone and union in from_cones #169

Merged
merged 6 commits into from
Sep 9, 2024
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

### Added

* 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'.

## [0.16.2]

## Fixed
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 = 'f4bbcab15ce705d4af8aace03286b947a51deea6', features = ["storage"] }
moc = { git = 'https://github.com/cds-astro/cds-moc-rust', rev = '5032e6163fd88cb57ba535eca9bff744931cfbb2', 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
4 changes: 2 additions & 2 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Detailed list of all methods
============================
API: Detailed list of all methods
=================================

Auto-generated API documentation for MOCpy.

Expand Down
132 changes: 99 additions & 33 deletions notebooks/01-Creating_MOCs_from_shapes.ipynb

Large diffs are not rendered by default.

128 changes: 103 additions & 25 deletions python/mocpy/moc/moc.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,9 @@ def contains_lonlat(self, lon, lat, keep_inside=True):
lat : `astropy.coordinates.Latitude` or its supertype `astropy.units.Quantity`
Declination array in deg
keep_inside : bool, optional
True by default. If so the mask describes coordinates lying inside the MOC. If ``keep_inside``
is false, contains will return the mask of the coordinates lying outside the MOC.
True by default. If so the mask describes coordinates lying inside the MOC.
If ``keep_inside`` is false, contains will return the mask of the coordinates
lying outside the MOC.

Raises
------
Expand All @@ -358,17 +359,14 @@ def contains_lonlat(self, lon, lat, keep_inside=True):
Examples
--------
>>> from mocpy import MOC
>>> import numpy as np
>>> from astropy.coordinates import Angle
>>> import astropy.units as u
>>> # create lists of coordinates
>>> lon = Angle(np.array([[1, 2, 3], [-2, -40, -5]]), unit=u.deg)
>>> lat = Angle(np.array([[20, 25, 10], [-60, 80, 0]]), unit=u.deg)
>>> lon = Angle([1, 2, 3, -2, -40, -5], unit="deg")
>>> lat = Angle([20, 25, 10, -60, 80, 0], unit="deg")
>>> # create a polygonal moc from these
>>> moc = MOC.from_polygon(lon=lon, lat=lat, max_depth=12)
>>> moc.contains_lonlat(lon=lon, lat=lat) # returns all true
array([[ True, True, True],
[ True, True, True]])
array([ True, True, True, True, True, True])

See Also
--------
Expand Down Expand Up @@ -1188,9 +1186,9 @@ def from_elliptical_cone(cls, lon, lat, a, b, pa, max_depth, *, delta_depth=2):
Maximum HEALPix cell resolution.
delta_depth : int, optional
To control the approximation, you can choose to perform the computations at a deeper
depth using the `depth_delta` parameter.
depth using the `delta_depth` parameter.
The depth at which the computations will be made will therefore be equal to
`depth` + `depth_delta`.
`depth` + `delta_depth`.

Returns
-------
Expand Down Expand Up @@ -1245,9 +1243,9 @@ def from_cone(cls, lon, lat, radius, max_depth, *, delta_depth=2):
Maximum HEALPix cell resolution.
delta_depth : int, optional
To control the approximation, you can choose to perform the computations at a deeper
depth using the `depth_delta` parameter.
depth using the `delta_depth` parameter.
The depth at which the computations will be made will therefore be equal to
`max_depth` + `depth_delta`.
`max_depth` + `delta_depth`.

Returns
-------
Expand All @@ -1266,6 +1264,11 @@ def from_cone(cls, lon, lat, radius, max_depth, *, delta_depth=2):
... max_depth=10
... )
"""
if len(lon) != 1:
raise ValueError(
"'MOC.from_cone' only works with one cone. To create MOCs "
"from multiple cones, use 'MOC.from_cones'.",
)
index = mocpy.from_cone(
lon[0],
lat[0],
Expand All @@ -1277,7 +1280,17 @@ def from_cone(cls, lon, lat, radius, max_depth, *, delta_depth=2):

@classmethod
@validate_lonlat
def from_cones(cls, lon, lat, radius, max_depth, *, delta_depth=2, n_threads=None):
def from_cones(
cls,
lon,
lat,
radius,
max_depth,
*,
union_strategy=None,
delta_depth=2,
n_threads=None,
):
"""
Create a list of MOCs from cones.

Expand All @@ -1296,31 +1309,55 @@ def from_cones(cls, lon, lat, radius, max_depth, *, delta_depth=2, n_threads=Non
The radius angle of the cone. Can be scalar or a list of radii.
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.
delta_depth : int, optional
To control the approximation, you can choose to perform the computations at a deeper
depth using the `depth_delta` parameter.
depth using the `delta_depth` parameter.
The depth at which the computations will be made will therefore be equal to
`max_depth` + `depth_delta`.
`max_depth` + `delta_depth`.
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.

Returns
-------
result : List[`~mocpy.moc.MOC`]
The resulting list of MOCs
List[`~mocpy.moc.MOC`] or `~mocpy.MOC`
The resulting list of MOCs, or if 'union_strategy' is not None, the MOC of the
union of all the cones.

Examples
--------
>>> from mocpy import MOC
>>> import astropy.units as u
>>> moc = MOC.from_cone(
>>> moc = MOC.from_cones(
... lon=[1, 4] * u.deg,
... lat=[2, 5] * u.deg,
... radius=10 * u.deg,
... max_depth=10
... radius=1 * u.arcmin,
... max_depth=12,
... union_strategy="small_cones"
... )
"""
if union_strategy == "small_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_small_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'.")

if radius.isscalar:
indices = mocpy.from_same_cones(
lon,
Expand All @@ -1341,13 +1378,54 @@ def from_cones(cls, lon, lat, radius, max_depth, *, delta_depth=2, n_threads=Non
)
return [cls(index) for index in indices]

@classmethod
def from_zone(cls, coordinates, max_depth):
"""
Create a MOC from a zone.

The zone is defined by a range of longitudes and latitudes. Its sides follow great
circles in longitudes and small circles for latitudes.

Parameters
----------
coordinates : `~astropy.coordinates.SkyCoord`
A couple of coordinates for the bottom left and the upper right corner of the
zone.
max_depth : int
Maximum HEALPix cell resolution.

Returns
-------
`~mocpy.moc.MOC`
The resulting MOC

Examples
--------
>>> from mocpy import MOC
>>> from astropy.coordinates import SkyCoord
>>> moc = MOC.from_zone(
... SkyCoord([[0, 0], [20, 20]], unit="deg"),
... max_depth=5
... )
"""
index = mocpy.from_zone(
coordinates[0].icrs.ra.deg,
coordinates[0].icrs.dec.deg,
coordinates[1].icrs.ra.deg,
coordinates[1].icrs.dec.deg,
np.uint8(max_depth),
)
return cls(index)

@classmethod
@validate_lonlat
def from_box(cls, lon, lat, a, b, angle, max_depth):
"""
Create a MOC from a box/rectangle.

The box is centered around the (`lon`, `lat`) position.
The box is centered around the (`lon`, `lat`) position. The sides and cross from
the center follow great circles. As such, the box is the intersection between
two orthogonal spherical wedges having the same center.
The coordinates should be expressed in equatorial coordinates using the
ICRS reference. We follow the Space MOC standard.

Expand Down Expand Up @@ -1513,9 +1591,9 @@ def from_ring(
Maximum HEALPix cell resolution.
delta_depth : int, optional
To control the approximation, you can choose to perform the computations at a deeper
depth using the `depth_delta` parameter.
depth using the `delta_depth` parameter.
The depth at which the computations will be made will therefore be equal to
`max_depth` + `depth_delta`.
`max_depth` + `delta_depth`.

Returns
-------
Expand Down Expand Up @@ -1733,9 +1811,9 @@ def from_stcs(cls, stcs, max_depth, delta_depth=2):
Maximum HEALPix cell resolution.
delta_depth : int, optional
To control the approximation, you can choose to perform the computations at a deeper
depth using the `depth_delta` parameter.
depth using the `delta_depth` parameter.
The depth at which the computations will be made will therefore be equal to
`max_depth` + `depth_delta`.
`max_depth` + `delta_depth`.

Returns
-------
Expand Down
31 changes: 31 additions & 0 deletions python/mocpy/tests/test_moc.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,13 @@ def test_from_ring():
)


def test_from_zone():
moc = MOC.from_zone(SkyCoord([[-50, -50], [50, 50]], unit="deg"), max_depth=5)
# test the diagonal
for coordinate in range(-50, 60, 10):
assert moc.contains_skycoords(SkyCoord(coordinate, coordinate, unit="deg"))


def test_from_box():
a = Angle("10d")
b = Angle("2d")
Expand Down Expand Up @@ -713,6 +720,11 @@ def test_from_elliptical_cone():
)


def test_from_cone():
with pytest.raises(ValueError, match="'MOC.from_cone' only works with one cone.*"):
MOC.from_cone([2, 4] * u.deg, [5, 6] * u.deg, radius=2 * u.arcmin, max_depth=2)


def test_from_cones():
# same radius
radius = 2 * u.arcmin
Expand All @@ -727,6 +739,14 @@ def test_from_cones():
barycenter.ra,
barycenter.dec,
) < Angle(1 * u.arcsec)
moc = MOC.from_cones(
lon,
lat,
radius=radius,
max_depth=14,
union_strategy="small_cones",
)
assert isinstance(moc, MOC)
# different radii
radii = [5, 6] * u.arcmin
cones = MOC.from_cones(lon, lat, radius=radii, max_depth=14)
Expand All @@ -736,6 +756,17 @@ def test_from_cones():
cone.sky_fraction
> (((radius) ** 2).to(u.steradian) / 4 * u.steradian).value
)
moc = MOC.from_cones(
lon,
lat,
radius=radii,
max_depth=14,
union_strategy="small_cones",
)
assert isinstance(moc, MOC)
# test error
with pytest.raises(ValueError, match="'union_strategy'*"):
MOC.from_cones(lon, lat, radius=radii, max_depth=14, union_strategy="big_cones")


@pytest.fixture()
Expand Down
Loading
Loading