oxmpl-py 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. oxmpl_py-0.1.0/PKG-INFO +11 -0
  2. oxmpl_py-0.1.0/oxmpl/Cargo.lock +157 -0
  3. oxmpl_py-0.1.0/oxmpl/Cargo.toml +12 -0
  4. oxmpl_py-0.1.0/oxmpl/src/base/error.rs +89 -0
  5. oxmpl_py-0.1.0/oxmpl/src/base/goal.rs +16 -0
  6. oxmpl_py-0.1.0/oxmpl/src/base/mod.rs +9 -0
  7. oxmpl_py-0.1.0/oxmpl/src/base/planner.rs +17 -0
  8. oxmpl_py-0.1.0/oxmpl/src/base/problem_definition.rs +9 -0
  9. oxmpl_py-0.1.0/oxmpl/src/base/space.rs +38 -0
  10. oxmpl_py-0.1.0/oxmpl/src/base/spaces/mod.rs +2 -0
  11. oxmpl_py-0.1.0/oxmpl/src/base/spaces/real_vector_state_space.rs +150 -0
  12. oxmpl_py-0.1.0/oxmpl/src/base/spaces/so2_state_space.rs +48 -0
  13. oxmpl_py-0.1.0/oxmpl/src/base/state.rs +70 -0
  14. oxmpl_py-0.1.0/oxmpl/src/base/unittests/rvss_tests.rs +304 -0
  15. oxmpl_py-0.1.0/oxmpl/src/base/unittests/so2ss_tests.rs +192 -0
  16. oxmpl_py-0.1.0/oxmpl/src/base/validity.rs +144 -0
  17. oxmpl_py-0.1.0/oxmpl/src/geometric/mod.rs +1 -0
  18. oxmpl_py-0.1.0/oxmpl/src/geometric/planners/mod.rs +1 -0
  19. oxmpl_py-0.1.0/oxmpl/src/geometric/planners/rrt/mod.rs +2 -0
  20. oxmpl_py-0.1.0/oxmpl/src/geometric/planners/rrt/rrt.rs +190 -0
  21. oxmpl_py-0.1.0/oxmpl/src/lib.rs +2 -0
  22. oxmpl_py-0.1.0/oxmpl/tests/rrt_tests.rs +195 -0
  23. oxmpl_py-0.1.0/oxmpl-py/Cargo.lock +280 -0
  24. oxmpl_py-0.1.0/oxmpl-py/Cargo.toml +16 -0
  25. oxmpl_py-0.1.0/oxmpl-py/src/base.rs +201 -0
  26. oxmpl_py-0.1.0/oxmpl-py/src/geometric.rs +31 -0
  27. oxmpl_py-0.1.0/oxmpl-py/src/lib.rs +19 -0
  28. oxmpl_py-0.1.0/oxmpl-py/src/macros.rs +63 -0
  29. oxmpl_py-0.1.0/oxmpl-py/tests/test_rrt_integration.py +119 -0
  30. oxmpl_py-0.1.0/pyproject.toml +22 -0
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: oxmpl_py
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
7
+ Summary: Python Bindings for the OxMPL Library
8
+ Author: Junior Sundar <juniorsundar@gmail.com>
9
+ Author-email: Junior Sundar <juniorsundar@gmail.com>
10
+ License: BSD-3
11
+ Requires-Python: >=3.8
@@ -0,0 +1,157 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "bitflags"
7
+ version = "2.9.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
10
+
11
+ [[package]]
12
+ name = "cfg-if"
13
+ version = "1.0.1"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
16
+
17
+ [[package]]
18
+ name = "getrandom"
19
+ version = "0.3.3"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
22
+ dependencies = [
23
+ "cfg-if",
24
+ "libc",
25
+ "r-efi",
26
+ "wasi",
27
+ ]
28
+
29
+ [[package]]
30
+ name = "libc"
31
+ version = "0.2.172"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
34
+
35
+ [[package]]
36
+ name = "oxmpl"
37
+ version = "0.1.0"
38
+ dependencies = [
39
+ "rand",
40
+ ]
41
+
42
+ [[package]]
43
+ name = "ppv-lite86"
44
+ version = "0.2.21"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
47
+ dependencies = [
48
+ "zerocopy",
49
+ ]
50
+
51
+ [[package]]
52
+ name = "proc-macro2"
53
+ version = "1.0.95"
54
+ source = "registry+https://github.com/rust-lang/crates.io-index"
55
+ checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
56
+ dependencies = [
57
+ "unicode-ident",
58
+ ]
59
+
60
+ [[package]]
61
+ name = "quote"
62
+ version = "1.0.40"
63
+ source = "registry+https://github.com/rust-lang/crates.io-index"
64
+ checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
65
+ dependencies = [
66
+ "proc-macro2",
67
+ ]
68
+
69
+ [[package]]
70
+ name = "r-efi"
71
+ version = "5.2.0"
72
+ source = "registry+https://github.com/rust-lang/crates.io-index"
73
+ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
74
+
75
+ [[package]]
76
+ name = "rand"
77
+ version = "0.9.1"
78
+ source = "registry+https://github.com/rust-lang/crates.io-index"
79
+ checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
80
+ dependencies = [
81
+ "rand_chacha",
82
+ "rand_core",
83
+ ]
84
+
85
+ [[package]]
86
+ name = "rand_chacha"
87
+ version = "0.9.0"
88
+ source = "registry+https://github.com/rust-lang/crates.io-index"
89
+ checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
90
+ dependencies = [
91
+ "ppv-lite86",
92
+ "rand_core",
93
+ ]
94
+
95
+ [[package]]
96
+ name = "rand_core"
97
+ version = "0.9.3"
98
+ source = "registry+https://github.com/rust-lang/crates.io-index"
99
+ checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
100
+ dependencies = [
101
+ "getrandom",
102
+ ]
103
+
104
+ [[package]]
105
+ name = "syn"
106
+ version = "2.0.103"
107
+ source = "registry+https://github.com/rust-lang/crates.io-index"
108
+ checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
109
+ dependencies = [
110
+ "proc-macro2",
111
+ "quote",
112
+ "unicode-ident",
113
+ ]
114
+
115
+ [[package]]
116
+ name = "unicode-ident"
117
+ version = "1.0.18"
118
+ source = "registry+https://github.com/rust-lang/crates.io-index"
119
+ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
120
+
121
+ [[package]]
122
+ name = "wasi"
123
+ version = "0.14.2+wasi-0.2.4"
124
+ source = "registry+https://github.com/rust-lang/crates.io-index"
125
+ checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
126
+ dependencies = [
127
+ "wit-bindgen-rt",
128
+ ]
129
+
130
+ [[package]]
131
+ name = "wit-bindgen-rt"
132
+ version = "0.39.0"
133
+ source = "registry+https://github.com/rust-lang/crates.io-index"
134
+ checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
135
+ dependencies = [
136
+ "bitflags",
137
+ ]
138
+
139
+ [[package]]
140
+ name = "zerocopy"
141
+ version = "0.8.25"
142
+ source = "registry+https://github.com/rust-lang/crates.io-index"
143
+ checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
144
+ dependencies = [
145
+ "zerocopy-derive",
146
+ ]
147
+
148
+ [[package]]
149
+ name = "zerocopy-derive"
150
+ version = "0.8.25"
151
+ source = "registry+https://github.com/rust-lang/crates.io-index"
152
+ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
153
+ dependencies = [
154
+ "proc-macro2",
155
+ "quote",
156
+ "syn",
157
+ ]
@@ -0,0 +1,12 @@
1
+ [package]
2
+ name = "oxmpl"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7
+ [lib]
8
+ name = "oxmpl"
9
+ crate-type = ["lib"]
10
+
11
+ [dependencies]
12
+ rand = "0.9.1"
@@ -0,0 +1,89 @@
1
+ use std::{error, fmt};
2
+
3
+ #[derive(Debug, PartialEq)]
4
+ pub enum StateSpaceError {
5
+ /// The length of the provided bounds does not match the dimension.
6
+ DimensionMismatch { expected: usize, found: usize },
7
+ /// A lower bound is greater than or equal to its corresponding upper bound.
8
+ InvalidBound { lower: f64, upper: f64 },
9
+ /// A 0-dimensional space was requested without explicit (empty) bounds.
10
+ ZeroDimensionUnbounded,
11
+ }
12
+ impl fmt::Display for StateSpaceError {
13
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14
+ match self {
15
+ Self::DimensionMismatch { expected, found } => write!(
16
+ f,
17
+ "provided bounds length ({found}) does not match specified dimension ({expected})."
18
+ ),
19
+ Self::InvalidBound { lower, upper } => {
20
+ write!(
21
+ f,
22
+ "Lower bound {lower} is greater than upper bound {upper}."
23
+ )
24
+ }
25
+ Self::ZeroDimensionUnbounded => {
26
+ write!(f, "Cannot create 0-dimensional unbounded space.")
27
+ }
28
+ }
29
+ }
30
+ }
31
+ impl error::Error for StateSpaceError {}
32
+
33
+ #[derive(Debug, PartialEq)]
34
+ pub enum StateSamplingError {
35
+ /// When trying to sample a space that isn't bounded.
36
+ UnboundedDimension { dimension_index: usize },
37
+ /// The space or region to be sampled has no volume (e.g., all dimensions have min >= max).
38
+ ZeroVolume,
39
+ /// It was not possible to generate a sample that satisfies the goal conditions.
40
+ GoalRegionUnsatisfiable,
41
+ /// An iterative sampling attempt (like for a goal region) failed to find a sample within a set number of attempts.
42
+ GoalSamplingTimeout { attempts: u32 },
43
+ }
44
+ impl fmt::Display for StateSamplingError {
45
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46
+ match self {
47
+ Self::UnboundedDimension { dimension_index } => {
48
+ write!(
49
+ f,
50
+ "Cannot sample uniformly because dimension {dimension_index} is unbounded."
51
+ )
52
+ }
53
+ Self::ZeroVolume => {
54
+ write!(f, "Cannot sample from a region with zero volume.")
55
+ }
56
+ Self::GoalRegionUnsatisfiable => {
57
+ write!(f, "Could not generate a sample from the goal region because its constraints may be unsatisfiable.")
58
+ }
59
+ Self::GoalSamplingTimeout { attempts } => {
60
+ write!(
61
+ f,
62
+ "Failed to generate a goal sample within {attempts} attempts."
63
+ )
64
+ }
65
+ }
66
+ }
67
+ }
68
+ impl error::Error for StateSamplingError {}
69
+
70
+ #[derive(Debug, PartialEq)]
71
+ pub enum PlanningError {
72
+ /// Solution search timed-out.
73
+ Timeout,
74
+ /// No solution found.
75
+ NoSolutionFound,
76
+ }
77
+ impl fmt::Display for PlanningError {
78
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79
+ match self {
80
+ Self::Timeout => {
81
+ write!(f, "No solution found within timeout.")
82
+ }
83
+ Self::NoSolutionFound => {
84
+ write!(f, "No solution found.")
85
+ }
86
+ }
87
+ }
88
+ }
89
+ impl error::Error for PlanningError {}
@@ -0,0 +1,16 @@
1
+ use crate::base::{error::StateSamplingError, state::State};
2
+ use rand::Rng;
3
+
4
+ pub trait Goal<S: State> {
5
+ fn is_satisfied(&self, state: &S) -> bool;
6
+ }
7
+
8
+ pub trait GoalRegion<S: State>: Goal<S> {
9
+ /// Returns the distance from a state to the goal region.
10
+ fn distance_goal(&self, state: &S) -> f64;
11
+ }
12
+
13
+ pub trait GoalSampleableRegion<S: State>: GoalRegion<S> {
14
+ /// Generates a state from the goal region.
15
+ fn sample_goal(&self, rng: &mut impl Rng) -> Result<S, StateSamplingError>;
16
+ }
@@ -0,0 +1,9 @@
1
+ mod spaces;
2
+
3
+ pub mod error;
4
+ pub mod goal;
5
+ pub mod planner;
6
+ pub mod problem_definition;
7
+ pub mod space;
8
+ pub mod state;
9
+ pub mod validity;
@@ -0,0 +1,17 @@
1
+ use crate::base::{
2
+ error::PlanningError, goal::Goal, problem_definition::ProblemDefinition, space::StateSpace,
3
+ state::State, validity::StateValidityChecker,
4
+ };
5
+ use std::{sync::Arc, time::Duration};
6
+
7
+ #[derive(Clone)]
8
+ pub struct Path<S: State>(pub Vec<S>);
9
+
10
+ pub trait Planner<S: State, SP: StateSpace<StateType = S>, G: Goal<S>> {
11
+ fn setup(
12
+ &mut self,
13
+ problem_def: Arc<ProblemDefinition<S, SP, G>>,
14
+ validity_checker: Arc<dyn StateValidityChecker<S> + Send + Sync>,
15
+ );
16
+ fn solve(&mut self, timeout: Duration) -> Result<Path<S>, PlanningError>;
17
+ }
@@ -0,0 +1,9 @@
1
+ use crate::base::{goal::Goal, space::StateSpace, state::State};
2
+ use std::sync::Arc;
3
+
4
+ pub struct ProblemDefinition<S: State, SP: StateSpace<StateType = S>, G: Goal<S>> {
5
+ pub space: Arc<SP>,
6
+ pub start_states: Vec<S>,
7
+ pub goal: Arc<G>,
8
+ // Potentially state_validity_checker here if not passed directly to planner
9
+ }
@@ -0,0 +1,38 @@
1
+ use rand::Rng;
2
+
3
+ use crate::base::{error::StateSamplingError, state::State};
4
+
5
+ pub use crate::base::spaces::{
6
+ real_vector_state_space::RealVectorStateSpace,
7
+ so2_state_space::SO2StateSpace,
8
+ };
9
+
10
+ pub trait StateSpace {
11
+ /// StateType defines what is acceptable in current StateSpace
12
+ type StateType: State;
13
+ /// Find distance between current state1 and target state2. Depends on StateSpace.
14
+ fn distance(&self, state1: &Self::StateType, state2: &Self::StateType) -> f64;
15
+ /// Find state interpolated between `from` and `to` states given 0<=`t`<=1.
16
+ fn interpolate(
17
+ &self,
18
+ from: &Self::StateType,
19
+ to: &Self::StateType,
20
+ t: f64,
21
+ state: &mut Self::StateType,
22
+ );
23
+ /// Modifies the given state to ensure it conforms to the space's defined bounds.
24
+ fn enforce_bounds(&self, state: &mut Self::StateType);
25
+
26
+ fn satisfies_bounds(&self, state: &Self::StateType) -> bool;
27
+
28
+ /// Generates a state uniformly at random from the entire state space.
29
+ fn sample_uniform(&self, rng: &mut impl Rng) -> Result<Self::StateType, StateSamplingError>;
30
+ }
31
+
32
+ // #[cfg(test)]
33
+ // #[path = "unittests/rvss_tests.rs"]
34
+ // mod rvss_tests;
35
+
36
+ // #[cfg(test)
37
+ // #[path = "unittests/so2ss_tests.rs"]
38
+ // mod so2ss_tests;
@@ -0,0 +1,2 @@
1
+ pub mod real_vector_state_space;
2
+ pub mod so2_state_space;
@@ -0,0 +1,150 @@
1
+ use rand::Rng;
2
+
3
+ use crate::base::{
4
+ error::{StateSamplingError, StateSpaceError}, space::StateSpace, state::RealVectorState
5
+ };
6
+
7
+ pub struct RealVectorStateSpace {
8
+ /// n-Dimensionality of VectorStateSpace i.e. R^n.
9
+ pub dimension: usize,
10
+ pub bounds: Vec<(f64, f64)>,
11
+ }
12
+
13
+ impl RealVectorStateSpace {
14
+ pub fn new(
15
+ dimension: usize,
16
+ bounds_option: Option<Vec<(f64, f64)>>,
17
+ ) -> Result<Self, StateSpaceError> {
18
+ let bounds = match bounds_option {
19
+ Some(explicit_bounds) => {
20
+ if explicit_bounds.len() != dimension {
21
+ return Err(StateSpaceError::DimensionMismatch {
22
+ expected: dimension,
23
+ found: explicit_bounds.len(),
24
+ });
25
+ }
26
+ for bound in &explicit_bounds {
27
+ if bound.0 >= bound.1 {
28
+ return Err(StateSpaceError::InvalidBound {
29
+ lower: bound.0,
30
+ upper: bound.1,
31
+ });
32
+ }
33
+ }
34
+ explicit_bounds
35
+ }
36
+ None => {
37
+ if dimension == 0 {
38
+ return Err(StateSpaceError::ZeroDimensionUnbounded);
39
+ }
40
+ vec![(f64::NEG_INFINITY, f64::INFINITY); dimension]
41
+ }
42
+ };
43
+
44
+ Ok(Self { dimension, bounds })
45
+ }
46
+ }
47
+
48
+ impl StateSpace for RealVectorStateSpace {
49
+ type StateType = RealVectorState;
50
+
51
+ /// Find distance between current state1 and target state2. Depends on StateSpace.
52
+ /// In RealVectorStateSpace, this refers to the L2-norm.
53
+ fn distance(&self, state1: &Self::StateType, state2: &Self::StateType) -> f64 {
54
+ assert_eq!(
55
+ state1.values.len(),
56
+ self.dimension,
57
+ "State1 has incorrect dimension for this space."
58
+ );
59
+ assert_eq!(
60
+ state2.values.len(),
61
+ self.dimension,
62
+ "State2 has incorrect dimension for this space."
63
+ );
64
+ state1
65
+ .values
66
+ .iter()
67
+ .zip(state2.values.iter())
68
+ .map(|(v1, v2)| (v1 - v2).powi(2))
69
+ .sum::<f64>()
70
+ .sqrt()
71
+ }
72
+
73
+ fn interpolate(
74
+ &self,
75
+ from: &Self::StateType,
76
+ to: &Self::StateType,
77
+ t: f64,
78
+ out_state: &mut Self::StateType,
79
+ ) {
80
+ assert_eq!(
81
+ from.values.len(),
82
+ self.dimension,
83
+ "From-state has incorrect dimension."
84
+ );
85
+ assert_eq!(
86
+ to.values.len(),
87
+ self.dimension,
88
+ "To-state has incorrect dimension."
89
+ );
90
+ assert_eq!(
91
+ out_state.values.len(),
92
+ self.dimension,
93
+ "Out-state has incorrect dimension or not properly initialized."
94
+ );
95
+ for i in 0..from.values.len() {
96
+ out_state.values[i] = from.values[i] + (to.values[i] - from.values[i]) * t;
97
+ }
98
+ }
99
+
100
+ fn enforce_bounds(&self, state: &mut Self::StateType) {
101
+ if state.values.len() != self.dimension {
102
+ assert_eq!(
103
+ state.values.len(),
104
+ self.dimension,
105
+ "State and space dimension mismatch when enforcing bounds."
106
+ );
107
+ }
108
+ for (i, value) in state.values.iter_mut().enumerate() {
109
+ if i < self.bounds.len() {
110
+ let (lower, upper) = self.bounds[i];
111
+ *value = value.clamp(lower, upper);
112
+ }
113
+ }
114
+ }
115
+
116
+ fn satisfies_bounds(&self, state: &Self::StateType) -> bool {
117
+ if state.values.len() != self.dimension {
118
+ assert_eq!(
119
+ state.values.len(),
120
+ self.dimension,
121
+ "State and space dimension mismatch when checking bound satisfaction."
122
+ );
123
+ }
124
+ for i in 0..self.dimension {
125
+ let (lower, upper) = self.bounds[i];
126
+ if state.values[i] - f64::EPSILON > upper || state.values[i] + f64::EPSILON < lower {
127
+ return false;
128
+ }
129
+ }
130
+ true
131
+ }
132
+
133
+ fn sample_uniform(&self, rng: &mut impl Rng) -> Result<Self::StateType, StateSamplingError> {
134
+ let mut values = Vec::with_capacity(self.dimension);
135
+
136
+ for i in 0..self.dimension {
137
+ let (lower, upper) = self.bounds[i];
138
+
139
+ if !lower.is_finite() || !upper.is_finite() {
140
+ return Err(StateSamplingError::UnboundedDimension { dimension_index: i });
141
+ }
142
+ if lower >= upper {
143
+ return Err(StateSamplingError::ZeroVolume);
144
+ }
145
+ values.push(rng.random_range(lower..upper));
146
+ }
147
+
148
+ Ok(RealVectorState { values })
149
+ }
150
+ }
@@ -0,0 +1,48 @@
1
+ use std::f64::consts::PI;
2
+ use rand::Rng;
3
+
4
+ use crate::base::{error::StateSamplingError, space::StateSpace, state::SO2State};
5
+
6
+ pub struct SO2StateSpace {
7
+ // pub bounds: Vec<(f64, f64)>,
8
+ }
9
+
10
+ impl StateSpace for SO2StateSpace {
11
+ type StateType = SO2State;
12
+
13
+ fn distance(&self, state1: &Self::StateType, state2: &Self::StateType) -> f64 {
14
+ let mut diff = state1.value - state2.value;
15
+ diff = (diff + PI).rem_euclid(2.0 * PI) - PI;
16
+ diff.abs()
17
+ }
18
+
19
+ fn interpolate(
20
+ &self,
21
+ from: &Self::StateType,
22
+ to: &Self::StateType,
23
+ t: f64,
24
+ out_state: &mut Self::StateType,
25
+ ) {
26
+ let mut diff_to_from = to.clone().normalise().value - from.clone().normalise().value;
27
+ if diff_to_from > PI {
28
+ diff_to_from -= 2.0 * PI;
29
+ } else if diff_to_from < -PI {
30
+ diff_to_from += 2.0 * PI;
31
+ }
32
+ out_state.value = from.value + diff_to_from * t;
33
+ out_state.value = out_state.normalise().value;
34
+ }
35
+
36
+ // TODO
37
+ fn enforce_bounds(&self, _state: &mut Self::StateType) {}
38
+
39
+ // TODO
40
+ fn satisfies_bounds(&self, _state: &Self::StateType) -> bool {
41
+ false
42
+ }
43
+
44
+ fn sample_uniform(&self, rng: &mut impl Rng) -> Result<SO2State, StateSamplingError> {
45
+ todo!()
46
+ }
47
+ }
48
+
@@ -0,0 +1,70 @@
1
+ use std::f64::consts::PI;
2
+
3
+ pub trait State: Clone + Send + Sync + 'static {}
4
+
5
+ #[derive(Clone, Debug, PartialEq)]
6
+ pub struct RealVectorState {
7
+ pub values: Vec<f64>,
8
+ }
9
+ impl State for RealVectorState {}
10
+
11
+ #[derive(Clone, Debug, PartialEq)]
12
+ pub struct SO2State {
13
+ pub value: f64,
14
+ }
15
+ impl SO2State {
16
+ pub fn new(val: f64) -> Self {
17
+ SO2State {
18
+ value: (val + PI).rem_euclid(2.0 * PI) - PI,
19
+ }
20
+ }
21
+
22
+ pub fn normalise(&mut self) -> Self {
23
+ SO2State {
24
+ value: (self.value + PI).rem_euclid(2.0 * PI) - PI,
25
+ }
26
+ }
27
+ }
28
+ impl State for SO2State {}
29
+
30
+ #[cfg(test)]
31
+ mod tests {
32
+ use super::*;
33
+
34
+ #[test]
35
+ fn test_real_vector_state_creation() {
36
+ let state = RealVectorState {
37
+ values: vec![1.0, 2.0],
38
+ };
39
+ assert_eq!(state.values, vec![1.0, 2.0]);
40
+ }
41
+
42
+ #[test]
43
+ fn test_real_vector_state_clone() {
44
+ let state1 = RealVectorState {
45
+ values: vec![1.0, 2.0],
46
+ };
47
+ let state2 = state1.clone();
48
+ assert_eq!(state1, state2);
49
+ }
50
+
51
+ #[test]
52
+ fn test_so2_state_creation() {
53
+ let state = SO2State { value: 1.0 };
54
+ assert_eq!(state.value, 1.0);
55
+ }
56
+
57
+ #[test]
58
+ fn test_so2_state_clone() {
59
+ let state1 = SO2State { value: 1.0 };
60
+ let state2 = state1.clone();
61
+ assert_eq!(state1, state2);
62
+ }
63
+
64
+ #[test]
65
+ fn test_so2_state_normalise() {
66
+ let mut state1 = SO2State { value: 3.0 * PI / 2.0 };
67
+ let state2 = state1.normalise();
68
+ assert_eq!(state2.value, -PI / 2.0);
69
+ }
70
+ }