Skip to content

Commit ecb9bed

Browse files
committed
Clearer docs on Cartesian product in Rust and Python
About: - Clarification re Cartesian product in Rust/python - Additional unit tests Issues: - Addresses #345
1 parent 14ba84f commit ecb9bed

File tree

6 files changed

+77
-3
lines changed

6 files changed

+77
-3
lines changed

docs/content/openrust-basic.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,22 @@ Constraints implement the namesake trait, [`Constraint`]. Implementations of [`C
8888

8989
These are the most common constraints in practice.
9090

91+
:::note Cartesian products in Rust
92+
The Rust API for [`CartesianProduct`] uses cumulative lengths, equivalently
93+
exclusive end indices.
94+
95+
For example, if `x0 = x[0..3]` and `x1 = x[3..5]`, then you would write:
96+
97+
```rust
98+
let cart_prod = constraints::CartesianProduct::new()
99+
.add_constraint(3, c0)
100+
.add_constraint(5, c1);
101+
```
102+
103+
This differs from the Python API, which uses inclusive last indices such as
104+
`[2, 4]` for the same two segments.
105+
:::
106+
91107
The construction of a constraint is very easy. Here is an example of a Euclidean ball centered at the origin with given radius:
92108

93109
```rust

docs/content/python-interface.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ following types of constraints:
9999

100100
A Cartesian product is a set $C = C_0 \times C_1 \times \ldots \times C_{s}$. In $\mathbb{R}^n$, a vector $x$ can be segmented as $$x=(x_{(0)}, x_{(1)}, \ldots, x_{(s)}),$$ into $s$ segments, $x_{(i)}\in\mathbb{R}^{m_i}$. The constraint $x \in C$ means $$x_{(i)} \in C_i,$$ for all $i=0,\ldots, s$. For example, consider the vector $x = ({\color{blue}{x_0}}, {\color{blue}{x_1}}, {\color{red}{x_2}}, {\color{red}{x_3}}, {\color{red}{x_4}})$; define the segments $$x_{(0)} = ({\color{blue}{x_0}}, {\color{blue}{x_1}}),\ x_{(1)} = ({\color{red}{x_2}}, {\color{red}{x_3}}, {\color{red}{x_4}})$$ These can be identified by the indices `1` and `4` (last indices of segments).
101101

102+
:::note
103+
In Python, `CartesianProduct` uses inclusive last indices for each segment.
104+
For example, `segment_ids = [1, 4]` means the segments `x[0:2]` and `x[2:5]`.
105+
106+
This is different from the Rust API, where Cartesian products are specified
107+
using cumulative lengths / exclusive end indices.
108+
:::
109+
102110

103111
Let us give an example: we will define the Cartesian product of a ball with a rectangle.
104112
Suppose that $U$ is a Euclidean ball with radius $r=1.5$ centered at

python/opengen/constraints/cartesian.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ def __init__(self, segments: List[int], constraints: List[constraint.Constraint]
1919
We can associate with :math:`x_{[1]}` the indices [0, 1] and with :math:`x_{[2]}` the indices [2, 3, 4].
2020
The *segment ids* are the indices 1 and 4.
2121
22+
Important:
23+
In Python, `segments` uses inclusive last indices. For example,
24+
`segments=[1, 4]` means that the first segment is `x[0:2]` and the
25+
second segment is `x[2:5]`.
26+
27+
This convention is different from the Rust API, where Cartesian
28+
products are specified using cumulative lengths / exclusive end
29+
indices.
30+
2231
Example:
2332
In this example we shall define the set :math:`X = \mathcal{B}_{1.5} \\times R \\times {\\rm I\!R}^{5}`,
2433
where :math:`\mathcal{B}_{1.5}` is a Euclidean ball of dimension 2 with radius 1.5, :math:`R` is a
@@ -31,7 +40,7 @@ def __init__(self, segments: List[int], constraints: List[constraint.Constraint]
3140
>>> segment_ids = [1, 4, 9]
3241
>>> my_set = og.constraints.CartesianProduct(segment_ids, [ball, rect, free])
3342
34-
:param segments: ids of segments
43+
:param segments: inclusive last indices of segments
3544
:param constraints: list of sets
3645
"""
3746

@@ -44,7 +53,7 @@ def __init__(self, segments: List[int], constraints: List[constraint.Constraint]
4453

4554
if segments[0] < 0:
4655
raise ValueError(
47-
"the first element of segment must be a positive integer")
56+
"the first element of segment must be a non-negative integer")
4857

4958
if len(segments) != len(constraints):
5059
raise ValueError(

python/test/test_constraints.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,20 @@ def test_cartesian_segments_empty_args(self):
345345
with self.assertRaises(ValueError) as __context:
346346
og.constraints.CartesianProduct([], sets)
347347

348+
def test_cartesian_segment_ids_are_inclusive_last_indices(self):
349+
cartesian = og.constraints.CartesianProduct(
350+
[0, 2, 5],
351+
[
352+
og.constraints.NoConstraints(),
353+
og.constraints.NoConstraints(),
354+
og.constraints.NoConstraints(),
355+
],
356+
)
357+
358+
self.assertEqual(1, cartesian.segment_dimension(0))
359+
self.assertEqual(2, cartesian.segment_dimension(1))
360+
self.assertEqual(3, cartesian.segment_dimension(2))
361+
348362
def test_cartesian_convex(self):
349363
ball_inf = og.constraints.BallInf(None, 1)
350364
ball_eucl = og.constraints.Ball2(None, 1)

rust/src/constraints/cartesian_product.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ use crate::FunctionCallResult;
2222
/// The constraint $x \in C$ is interpreted as $x_i \in C_i$
2323
/// for all $i=0,\ldots, n-1$.
2424
///
25+
/// # Indexing convention
26+
///
27+
/// In Rust, Cartesian products are defined using cumulative lengths
28+
/// (equivalently, exclusive end indices).
29+
///
30+
/// For example, `.add_constraint(2, c0).add_constraint(5, c1)` means that
31+
/// `c0` acts on `x[0..2]` and `c1` acts on `x[2..5]`.
32+
///
33+
/// This differs from the Python API, which uses inclusive last indices such
34+
/// as `[1, 4]` for the same two segments.
35+
///
2536
#[derive(Default)]
2637
pub struct CartesianProduct<'a, T = f64> {
2738
idx: Vec<usize>,
@@ -91,7 +102,8 @@ impl<'a, T> CartesianProduct<'a, T> {
91102
///
92103
/// # Arguments
93104
///
94-
/// - `ni`: total length of the vector `(x_0, \ldots, x_i)` (see example below)
105+
/// - `ni`: total length of the vector `(x_0, \ldots, x_i)` (that is, the
106+
/// exclusive end index of the `i`-th segment; see example below)
95107
/// - `constraint`: constraint to add; it must implement the trait `Constraint`
96108
///
97109
///

rust/src/constraints/tests.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,21 @@ fn t_cartesian_product_dimension() {
634634
);
635635
}
636636

637+
#[test]
638+
fn t_cartesian_product_indices_are_cumulative_lengths() {
639+
let cartesian = CartesianProduct::new()
640+
.add_constraint(1, NoConstraints::new())
641+
.add_constraint(3, NoConstraints::new())
642+
.add_constraint(6, NoConstraints::new());
643+
644+
assert_eq!(6, cartesian.dimension());
645+
646+
let mut x = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0];
647+
let original = x;
648+
cartesian.project(&mut x).unwrap();
649+
assert_eq!(original, x);
650+
}
651+
637652
#[test]
638653
fn t_cartesian_ball_no_constraint() {
639654
let xc = [1., 0., 0.];

0 commit comments

Comments
 (0)