Skip to content
Open
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
8 changes: 8 additions & 0 deletions src/python/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pybind_library(
srcs = ["r1interval_bindings.cc"],
deps = [
"//:s2",
"@abseil-cpp//absl/hash",
],
)

Expand All @@ -51,6 +52,7 @@ pybind_library(
srcs = ["r2point_bindings.cc"],
deps = [
"//:s2",
"@abseil-cpp//absl/hash",
],
)

Expand All @@ -59,6 +61,7 @@ pybind_library(
srcs = ["r2rect_bindings.cc"],
deps = [
"//:s2",
"@abseil-cpp//absl/hash",
],
)

Expand All @@ -67,6 +70,8 @@ pybind_library(
srcs = ["s1angle_bindings.cc"],
deps = [
"//:s2",
"@abseil-cpp//absl/hash",
"@abseil-cpp//absl/strings",
],
)

Expand All @@ -85,6 +90,7 @@ pybind_library(
srcs = ["s1interval_bindings.cc"],
deps = [
"//:s2",
"@abseil-cpp//absl/hash",
"@abseil-cpp//absl/strings",
],
)
Expand All @@ -94,6 +100,7 @@ pybind_library(
srcs = ["s2point_bindings.cc"],
deps = [
"//:s2",
"@abseil-cpp//absl/hash",
],
)

Expand All @@ -102,6 +109,7 @@ pybind_library(
srcs = ["s2latlng_bindings.cc"],
deps = [
"//:s2",
"@abseil-cpp//absl/hash",
"@abseil-cpp//absl/strings",
],
)
Expand Down
1 change: 1 addition & 0 deletions src/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ The Python bindings follow the C++ API closely but with Pythonic conventions:

**Operators:**
- Standard Python operators work as expected: `+`, `-`, `*`, `==`, `!=`, `<`, `>` (for C++ classes that implement those operators)
- Types that define `__eq__` also define `__hash__`, so they can be used as dictionary keys and in sets.

**Method Overloads:**
- Instead of Python overloads, C++ overloaded methods are exposed under distinct names. For different argument types, the short name is used for the same-type argument and a longer name for other types (e.g., `R2Rect.contains(other: R2Rect)` and `R2Rect.contains_point(p: R2Point)`). For overloads with significantly different behavior, descriptive names are chosen (e.g., `R2Rect.vertex(k)` for CCW vertex index and `R2Rect.vertex_ij(i, j)` for axis-direction vertex access).
Expand Down
4 changes: 4 additions & 0 deletions src/python/r1interval_bindings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <sstream>

#include "absl/hash/hash.h"
#include "s2/r1interval.h"

namespace py = pybind11;
Expand Down Expand Up @@ -101,6 +102,9 @@ void bind_r1interval(py::module& m) {
// Operators
.def(py::self == py::self, "Return true if two intervals contain the same set of points")
.def(py::self != py::self, "Return true if two intervals do not contain the same set of points")
.def("__hash__", [](const R1Interval& self) {
return absl::HashOf(self);
})

// String representation
.def("__repr__", [](const R1Interval& i) {
Expand Down
6 changes: 6 additions & 0 deletions src/python/r1interval_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@ def test_empty_intervals_are_equal(self):
e2 = s2.R1Interval()
self.assertTrue(e1 == e2)

def test_hash(self):
a = s2.R1Interval(0.0, 1.0)
b = s2.R1Interval(0.0, 1.0)
self.assertEqual(hash(a), hash(b))
self.assertEqual(len({a, b}), 1)

# String representation

def test_string_representation(self):
Expand Down
4 changes: 4 additions & 0 deletions src/python/r2point_bindings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <sstream>

#include "absl/hash/hash.h"
#include "s2/r2.h"

namespace py = pybind11;
Expand Down Expand Up @@ -72,6 +73,9 @@ void bind_r2point(py::module& m) {
.def("__itruediv__", [](R2Point& self, double v) -> R2Point& {
return self /= v;
}, py::arg("v"), "In-place division by scalar")
.def("__hash__", [](const R2Point& self) {
return absl::HashOf(self);
})

// String representation
.def("__repr__", [](const R2Point& p) {
Expand Down
6 changes: 6 additions & 0 deletions src/python/r2point_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ def test_in_place_division(self):
self.assertAlmostEqual(p.x, 2.0)
self.assertAlmostEqual(p.y, 3.0)

def test_hash(self):
a = s2.R2Point(1.0, 2.0)
b = s2.R2Point(1.0, 2.0)
self.assertEqual(hash(a), hash(b))
self.assertEqual(len({a, b}), 1)

# String representation

def test_string_representation(self):
Expand Down
4 changes: 4 additions & 0 deletions src/python/r2rect_bindings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <sstream>

#include "absl/hash/hash.h"
#include "s2/r2rect.h"

namespace py = pybind11;
Expand Down Expand Up @@ -163,6 +164,9 @@ void bind_r2rect(py::module& m) {
// Operators
.def(py::self == py::self, "Return true if two rectangles are equal")
.def(py::self != py::self, "Return true if two rectangles are not equal")
.def("__hash__", [](const R2Rect& self) {
return absl::HashOf(self);
})

// String representation
.def("__repr__", [](const R2Rect& r) {
Expand Down
6 changes: 6 additions & 0 deletions src/python/r2rect_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,12 @@ def test_empty_rects_are_equal(self):
e2 = s2.R2Rect()
self.assertTrue(e1 == e2)

def test_hash(self):
a = s2.R2Rect(s2.R2Point(0.0, 0.0), s2.R2Point(1.0, 1.0))
b = s2.R2Rect(s2.R2Point(0.0, 0.0), s2.R2Point(1.0, 1.0))
self.assertEqual(hash(a), hash(b))
self.assertEqual(len({a, b}), 1)

# String representation

def test_string_representation(self):
Expand Down
4 changes: 4 additions & 0 deletions src/python/s1angle_bindings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <cmath>
#include <sstream>

#include "absl/hash/hash.h"
#include "absl/strings/str_cat.h"
#include "s2/s1angle.h"
#include "s2/s2point.h"
Expand Down Expand Up @@ -146,6 +147,9 @@ void bind_s1angle(py::module& m) {
.def("__itruediv__", [](S1Angle& self, double m) -> S1Angle& {
return self /= m;
}, py::arg("m"), "In-place division by scalar")
.def("__hash__", [](S1Angle self) {
return absl::HashOf(self);
})

// String representation
.def("__repr__", [](S1Angle a) {
Expand Down
6 changes: 6 additions & 0 deletions src/python/s1angle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,12 @@ def test_in_place_scalar_division(self):
a /= 2.0
self.assertAlmostEqual(a.degrees, 45.0)

def test_hash(self):
a = s2.S1Angle.from_degrees(45.0)
b = s2.S1Angle.from_degrees(45.0)
self.assertEqual(hash(a), hash(b))
self.assertEqual(len({a, b}), 1)

# String representation

def test_repr(self):
Expand Down
4 changes: 4 additions & 0 deletions src/python/s1interval_bindings.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <pybind11/pybind11.h>
#include <pybind11/operators.h>

#include "absl/hash/hash.h"
#include "absl/strings/str_cat.h"
#include "s2/s1interval.h"

Expand Down Expand Up @@ -134,6 +135,9 @@ void bind_s1interval(py::module& m) {
"Return true if two intervals contain the same set of points")
.def(py::self != py::self,
"Return true if two intervals do not contain the same set of points")
.def("__hash__", [](const S1Interval& self) {
return absl::HashOf(self);
})

// String representation
.def("__repr__", [](const S1Interval& i) {
Expand Down
6 changes: 6 additions & 0 deletions src/python/s1interval_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ def test_equality(self):
self.assertTrue(interval1 == interval2)
self.assertTrue(interval1 != interval3)

def test_hash(self):
a = s2.S1Interval(0.0, 1.0)
b = s2.S1Interval(0.0, 1.0)
self.assertEqual(hash(a), hash(b))
self.assertEqual(len({a, b}), 1)

# String representation

def test_string_representation(self):
Expand Down
4 changes: 4 additions & 0 deletions src/python/s2latlng_bindings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <cmath>
#include <sstream>

#include "absl/hash/hash.h"
#include "absl/strings/str_cat.h"
#include "s2/s1angle.h"
#include "s2/s2latlng.h"
Expand Down Expand Up @@ -180,6 +181,9 @@ void bind_s2latlng(py::module& m) {
"Multiply by scalar (reversed operands).\n\n"
"The result is automatically normalized.\n"
"Note: the native C++ implementation does not normalize.")
.def("__hash__", [](const S2LatLng& self) {
return absl::HashOf(self);
})

// String representation
.def("to_string_in_degrees", &S2LatLng::ToStringInDegrees,
Expand Down
6 changes: 6 additions & 0 deletions src/python/s2latlng_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,12 @@ def test_scalar_multiplication_normalizes(self):
# 240 degrees longitude -> wrapped to -120
self.assertAlmostEqual(result.lng.degrees, -120.0)

def test_hash(self):
a = s2.S2LatLng.from_degrees(45.0, 90.0)
b = s2.S2LatLng.from_degrees(45.0, 90.0)
self.assertEqual(hash(a), hash(b))
self.assertEqual(len({a, b}), 1)

# String representation

def test_repr(self):
Expand Down
4 changes: 4 additions & 0 deletions src/python/s2point_bindings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <sstream>

#include "absl/hash/hash.h"
#include "s2/s2point.h"

namespace py = pybind11;
Expand Down Expand Up @@ -70,6 +71,9 @@ void bind_s2point(py::module& m) {
.def("__itruediv__", [](S2Point& self, double v) -> S2Point& {
return self /= v;
}, py::arg("v"), "In-place division by scalar")
.def("__hash__", [](const S2Point& self) {
return absl::HashOf(self);
})

// String representation
// __repr__ prefixes class name, __str__ delegates to C++ operator<<
Expand Down
6 changes: 6 additions & 0 deletions src/python/s2point_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ def test_equality(self):
self.assertEqual(p1, p2)
self.assertNotEqual(p1, p3)

def test_hash(self):
a = s2.S2Point(1.0, 0.0, 0.0)
b = s2.S2Point(1.0, 0.0, 0.0)
self.assertEqual(hash(a), hash(b))
self.assertEqual(len({a, b}), 1)

# String representation

def test_string_representation(self):
Expand Down
6 changes: 6 additions & 0 deletions src/s2/r1interval.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <iostream>
#include <ostream>

#include "absl/hash/hash.h"
#include "absl/log/absl_check.h"
#include "s2/_fp_contract_off.h" // IWYU pragma: keep
#include "s2/util/math/vector.h" // IWYU pragma: export
Expand Down Expand Up @@ -226,4 +227,9 @@ inline std::ostream& operator<<(std::ostream& os, const R1Interval& x) {
return os << "[" << x.lo() << ", " << x.hi() << "]";
}

template <typename H>
H AbslHashValue(H h, const R1Interval& interval) {
return H::combine(std::move(h), interval.lo(), interval.hi());
}

#endif // S2_R1INTERVAL_H_
12 changes: 12 additions & 0 deletions src/s2/r1interval_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <string>

#include <gtest/gtest.h>
#include "absl/hash/hash_testing.h"
#include "absl/strings/string_view.h"

using absl::string_view;
Expand Down Expand Up @@ -187,3 +188,14 @@ TEST(R1Interval, ApproxEquals) {
EXPECT_FALSE(R1Interval(1 - kLo, 2 + kHi).ApproxEquals(R1Interval(1, 2)));
EXPECT_FALSE(R1Interval(1 + kLo, 2 - kHi).ApproxEquals(R1Interval(1, 2)));
}

TEST(R1Interval, SupportsAbslHash) {
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({
R1Interval::Empty(),
R1Interval(0, 0),
R1Interval(0, 1),
R1Interval(1, 2),
R1Interval(-1, 1),
R1Interval(0.5, 1.5),
}));
}
6 changes: 6 additions & 0 deletions src/s2/r2rect.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <iosfwd>
#include <ostream>

#include "absl/hash/hash.h"
#include "absl/log/absl_check.h"
#include "s2/_fp_contract_off.h" // IWYU pragma: keep
#include "s2/r1interval.h"
Expand Down Expand Up @@ -251,4 +252,9 @@ inline bool R2Rect::operator!=(const R2Rect& other) const {

std::ostream& operator<<(std::ostream& os, const R2Rect& r);

template <typename H>
H AbslHashValue(H h, const R2Rect& rect) {
return H::combine(std::move(h), rect.x(), rect.y());
}

#endif // S2_R2RECT_H_
12 changes: 12 additions & 0 deletions src/s2/r2rect_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <string>

#include <gtest/gtest.h>
#include "absl/hash/hash_testing.h"
#include "absl/strings/string_view.h"
#include "s2/r1interval.h"
#include "s2/r2.h"
Expand Down Expand Up @@ -237,3 +238,14 @@ TEST(R2Rect, Expanded) {
EXPECT_TRUE(R2Rect(R2Point(0.2, 0.4), R2Point(0.3, 0.7)).Expanded(0.1).
ApproxEquals(R2Rect(R2Point(0.1, 0.3), R2Point(0.4, 0.8))));
}

TEST(R2Rect, SupportsAbslHash) {
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({
R2Rect::Empty(),
R2Rect(R2Point(0, 0), R2Point(0, 0)),
R2Rect(R2Point(0, 0), R2Point(1, 1)),
R2Rect(R2Point(1, 2), R2Point(3, 4)),
R2Rect(R2Point(-1, -2), R2Point(1, 2)),
R2Rect(R1Interval(0, 1), R1Interval(2, 3)),
}));
}
6 changes: 6 additions & 0 deletions src/s2/s1angle.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <ostream>
#include <type_traits>

#include "absl/hash/hash.h"
#include "absl/log/absl_check.h"
#include "s2/util/coding/coder.h"
#include "s2/_fp_contract_off.h" // IWYU pragma: keep
Expand Down Expand Up @@ -387,4 +388,9 @@ inline constexpr S1Angle S1Angle::UnsignedE7(uint32_t e7) {
// decimal point, e.g. "17.3745904".
std::ostream& operator<<(std::ostream& os, S1Angle a);

template <typename H>
H AbslHashValue(H h, S1Angle angle) {
return H::combine(std::move(h), angle.radians());
}

#endif // S2_S1ANGLE_H_
Loading