Skip to content

Commit

Permalink
Add Region.isDisjoint method.
Browse files Browse the repository at this point in the history
This new method can be implemented more efficiently for CompoundRegion
that `relate` which returns superset of region relations.
  • Loading branch information
andy-slac committed Nov 14, 2024
1 parent 5f70990 commit 4c455b1
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 0 deletions.
2 changes: 2 additions & 0 deletions include/lsst/sphgeom/CompoundRegion.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class UnionRegion : public CompoundRegion {
using Region::contains;
bool contains(UnitVector3d const &v) const override;
Relationship relate(Region const &r) const override;
bool isDisjoint(Region const& other) const override;
std::vector<std::uint8_t> encode() const override { return _encode(TYPE_CODE); }

///@{
Expand Down Expand Up @@ -158,6 +159,7 @@ class IntersectionRegion : public CompoundRegion {
using Region::contains;
bool contains(UnitVector3d const &v) const override;
Relationship relate(Region const &r) const override;
bool isDisjoint(Region const& other) const override;
std::vector<std::uint8_t> encode() const override { return _encode(TYPE_CODE); }

///@{
Expand Down
19 changes: 19 additions & 0 deletions include/lsst/sphgeom/Region.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,25 @@ class Region {
virtual Relationship relate(Ellipse const &) const = 0;
///@}

///@{
/// `disjoint` tests whether this region is disjoint from other region.
/// This method returns a subset of information returned from `relate`,
/// the reason for its existence is that in same cases it may be
/// implemented more efficiently.
///
/// The meaning of the returned value is the same as for `relate` metod -
/// `true` means that regions are definitely disjoint, and `false` means
/// that regions may overlap.
///
/// Note that some subclasses implement `isDisjointFrom` method for
/// specific region types which return exact answer.
virtual bool isDisjoint(Region const& other) const {
// Default implementation just uses `relate`.
auto r = this->relate(other);
return (r & DISJOINT) == DISJOINT;
}
///@}

/// `encode` serializes this region into an opaque byte string. Byte strings
/// emitted by encode can be deserialized with decode.
virtual std::vector<std::uint8_t> encode() const = 0;
Expand Down
1 change: 1 addition & 0 deletions python/lsst/sphgeom/_region.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ void defineClass(py::class_<Region, std::unique_ptr<Region>> &cls) {
cls.def("relate",
(Relationship(Region::*)(Region const &) const) & Region::relate,
"region"_a);
cls.def("isDisjoint", &Region::isDisjoint, "region"_a);
cls.def("encode", &python::encode);
cls.def_static("decode", &python::decode<Region>, "bytes"_a);
cls.def_static("getRegions", Region::getRegions, "region"_a);
Expand Down
21 changes: 21 additions & 0 deletions src/CompoundRegion.cc
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@ Relationship UnionRegion::relate(Region const &rhs) const {
return result;
}

bool UnionRegion::isDisjoint(Region const &rhs) const {
// Union is disjoint if all operands are disjoint.
for (auto&& operand: operands()) {
if (not operand->isDisjoint(rhs)) {
return false;
}
}
return true;
}

Box IntersectionRegion::getBoundingBox() const {
return getIntersectionBounds(*this, [](Region const &r) { return r.getBoundingBox(); });
}
Expand Down Expand Up @@ -252,5 +262,16 @@ Relationship IntersectionRegion::relate(Region const &rhs) const {
return result;
}

bool IntersectionRegion::isDisjoint(Region const &rhs) const {
// Intersection is disjoint if any operand is disjoint.
for (auto&& operand: operands()) {
if (operand->isDisjoint(rhs)) {
return true;
}
}
// False means it may overlap.
return false;
}

} // namespace sphgeom
} // namespace lsst
2 changes: 2 additions & 0 deletions tests/test_Box.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,12 @@ def test_relationships(self):
self.assertTrue(b3.contains(u))
b4 = Box.fromDegrees(200, 10, 300, 20)
self.assertTrue(b1.isDisjointFrom(b4))
self.assertTrue(b1.isDisjoint(b4))
r = b1.relate(LonLat.fromDegrees(135, 10))
self.assertEqual(r, CONTAINS)
r = b4.relate(b1)
self.assertEqual(r, DISJOINT)
self.assertTrue(b4.isDisjoint(b1))

def test_vectorized_contains(self):
b = Box.fromDegrees(200, 10, 300, 20)
Expand Down
2 changes: 2 additions & 0 deletions tests/test_Circle.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ def test_relationships(self):
self.assertTrue(c.intersects(UnitVector3d.X()))
self.assertTrue(e.isDisjointFrom(d))
self.assertEqual(d.relate(c), CONTAINS)
self.assertFalse(d.isDisjoint(c))
self.assertEqual(e.relate(d), DISJOINT)
self.assertTrue(e.isDisjoint(d))

def test_vectorized_contains(self):
b = Circle(UnitVector3d(*np.random.randn(3)), Angle(0.4 * math.pi))
Expand Down
18 changes: 18 additions & 0 deletions tests/test_CompoundRegion.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,15 @@ def testRelate(self):
self.assertEqual(self.box.relate(self.instance), WITHIN)
self.assertEqual(self.faraway.relate(self.instance), DISJOINT)

def testIsDisjoint(self):
"""Test region-region relationship checks."""
self.assertFalse(self.instance.isDisjoint(self.circle))
self.assertFalse(self.instance.isDisjoint(self.box))
self.assertTrue(self.instance.isDisjoint(self.faraway))
self.assertFalse(self.circle.isDisjoint(self.instance))
self.assertFalse(self.box.isDisjoint(self.instance))
self.assertTrue(self.faraway.isDisjoint(self.instance))


class IntersectionRegionTestCase(CompoundRegionTestMixin, unittest.TestCase):
"""Test intersection region."""
Expand All @@ -203,6 +212,15 @@ def testRelate(self):
self.assertEqual(self.box.relate(self.instance), CONTAINS)
self.assertEqual(self.faraway.relate(self.instance), DISJOINT)

def testIsDisjoint(self):
"""Test region-region relationship checks."""
self.assertFalse(self.instance.isDisjoint(self.box))
self.assertFalse(self.instance.isDisjoint(self.circle))
self.assertTrue(self.instance.isDisjoint(self.faraway))
self.assertFalse(self.circle.isDisjoint(self.instance))
self.assertFalse(self.box.isDisjoint(self.instance))
self.assertTrue(self.faraway.isDisjoint(self.instance))

def testGetRegion(self):
c1 = Circle(UnitVector3d(0.0, 0.0, 1.0), 1.0)
c2 = Circle(UnitVector3d(1.0, 0.0, 1.0), 2.0)
Expand Down
2 changes: 2 additions & 0 deletions tests/test_ConvexPolygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,13 @@ def testRelationships(self):
self.assertTrue(p.isWithin(boundingCircle))
self.assertTrue(p.intersects(boundingCircle))
self.assertFalse(p.isDisjointFrom(boundingCircle))
self.assertFalse(p.isDisjoint(boundingCircle))
self.assertFalse(p.contains(boundingCircle))
tinyCircle = Circle(boundingCircle.getCenter())
self.assertFalse(p.isWithin(tinyCircle))
self.assertTrue(p.intersects(tinyCircle))
self.assertFalse(p.isDisjointFrom(tinyCircle))
self.assertFalse(p.isDisjoint(tinyCircle))
self.assertTrue(p.contains(tinyCircle))

def test_vectorized_contains(self):
Expand Down

0 comments on commit 4c455b1

Please sign in to comment.