├── .codeclimate.yml
├── .gitignore
├── .travis.yml
├── Cargo.toml
├── LICENSE
├── README.md
├── logo.png
├── rustfmt.toml
└── src
├── arrays.rs
├── discrete.rs
├── interval.rs
├── lib.rs
├── ops
├── intersection.rs
├── mod.rs
└── union.rs
├── option.rs
├── partitions.rs
├── real.rs
└── tuples.rs
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | engines:
2 | fixme:
3 | enabled: true
4 |
5 | ratings:
6 | paths:
7 | - src/**
8 | - examples/**
9 |
10 | exclude_paths:
11 | - target/
12 | - extern/
13 | - Cargo.*
14 | - rustfmt.toml
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
7 | Cargo.lock
8 |
9 | # These are backup files generated by rustfmt
10 | **/*.rs.bk
11 |
12 | # This is a debugging file used for development
13 | examples/sandbox.rs
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: rust
2 | dist: trusty
3 | sudo: required
4 |
5 | # Cache cargo symbols for faster build
6 | cache: cargo
7 |
8 | addons:
9 | apt:
10 | packages:
11 | - libcurl4-openssl-dev
12 | - libelf-dev
13 | - libdw-dev
14 | - binutils-dev
15 | - gfortran
16 | - libopenblas-dev
17 | - cmake
18 | sources:
19 | - kalakris-cmake
20 |
21 | rust:
22 | - stable
23 |
24 | before_script:
25 | - export PATH=$HOME/.cargo/bin:$PATH
26 | - cargo install cargo-update || echo "cargo-update already installed"
27 | - cargo install cargo-travis || echo "cargo-travis already installed"
28 | - cargo install-update -a # update outdated cached binaries
29 |
30 | script:
31 | - |
32 | cargo build &&
33 | cargo test #&&
34 | # cargo bench &&
35 | # cargo --only stable doc
36 |
37 | after_success:
38 | - cargo coveralls
39 | - |
40 | [ $TRAVIS_BRANCH = master ] &&
41 | [ $TRAVIS_PULL_REQUEST = false ] &&
42 | cargo doc --no-deps &&
43 | echo "" > target/doc/index.html &&
44 | pip install --user ghp-import &&
45 | /home/travis/.local/bin/ghp-import -n target/doc &&
46 | git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
47 |
48 | env:
49 | global:
50 | - TRAVIS_CARGO_NIGHTLY_FEATURE=""
51 | - secure: AcBwzPJGszAqn4hW891+ZzbcxVA5wETXUmBg61cGKegHhFKBssB7HaNTcVX1pL3lHUiNGiLKReM+xbMPbth9c3ttz1SKlVsDJbQzt/lNaOjMmdkXd/0PNC7ZWT3yXx8sIbuyz2XCL6TW3dkvYt3G2+Ioy0BxeRepEbfhm1NEMWHoQXbLVkFiH4XtpfADY4DENWiuUbVA3/8+Qwc9R7i4mIf7J8EQWc1+HcNhuy556FrmkkAIDvRU2h0sl9HhjMrTHIKPalfXuriW4pQbZHH+JbA9Xh8MbX230PMqZaELxYkh020Fxy4z2z0VvISmLoMBFQb6JSh11AWQsFno1TZkVqhG2yl1tBPvdKgaqrBLZ8MsES8bG5i/LVDv2kM+dmbtWQmRmn6PnvBmJeJW5bbYx4F2PW57mDUebK/TKEF+1HbBR9KGm2fIihk46mvhxxpQ3ZE7A8lDs17Up9ZBhhYM5RJJ174Ls1Yn+qmtdhEAe5IJ4x5xRNYraTlKJHy2FCgqTeLEKB8TzDB0DUhaDMY6EnuOJdZFjEk5QSzSZJBKNSfyqO+N43vloWt3lszscekgQtYHATvDhylYFrIdRFPrqB3/BYIYOHEzZblTCSres8zmMBTfnWyAn37Z4y4fWsq3NUXcZkOd29HfjAi8qa7vVDQ4d1e1c0RCPOSKiCb7zP0=
52 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "spaces"
3 | description = "Set/space primitives for defining machine learning problems."
4 |
5 | version = "6.0.0"
6 | authors = ["Tom Spooner "]
7 |
8 | readme = "README.md"
9 | license-file = "LICENSE"
10 |
11 | keywords = ["geometry", "vector", "spaces", "machine", "learning"]
12 |
13 | repository = "https://github.com/tspooner/spaces"
14 | documentation = "https://docs.rs/spaces"
15 |
16 | [badges]
17 | travis-ci = { repository = "tspooner/spaces", branch = "master" }
18 | coveralls = { repository = "tspooner/spaces", branch = "master", service = "github" }
19 |
20 | [features]
21 | default = []
22 |
23 | serde = ["intervals/serde"]
24 |
25 | [dependencies]
26 | intervals = "2.1"
27 | itertools = "0.10"
28 | num-traits = "0.2"
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Tom Spooner
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # spaces ([api](https://docs.rs/spaces))
4 |
5 | [](https://crates.io/crates/spaces)
6 | [](https://travis-ci.org/tspooner/spaces)
7 | [](https://coveralls.io/github/tspooner/spaces?branch=master)
8 |
9 | ## Overview
10 | `spaces` provides a set of set/space primitives for machine learning problems.
11 | Traits such as `Space`, and it's derivatives, may be used to define
12 | state/action spaces, for example. Mappings between different spaces may also be
13 | defined which simplify many common preprocessing tasks.
14 |
15 | ## Installation
16 | ```toml
17 | [dependencies]
18 | spaces = "6.0"
19 | ```
20 |
21 | ## Contributing
22 | Pull requests are welcome. For major changes, please open an issue first to
23 | discuss what you would like to change.
24 |
25 | Please make sure to update tests as appropriate and adhere to the angularjs
26 | commit message conventions (see
27 | [here](https://gist.github.com/stephenparish/9941e89d80e2bc58a153)).
28 |
29 | ## License
30 | [MIT](https://choosealicense.com/licenses/mit/)
31 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tspooner/spaces/77c4cf506f2efac230078479a1dfe477d396f533/logo.png
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | fn_single_line = true
2 | where_single_line = true
3 | imports_indent = "Block"
4 | imports_layout = "HorizontalVertical"
5 | match_block_trailing_comma = true
6 | normalize_comments = true
7 | reorder_imports = true
8 | use_try_shorthand = true
9 | wrap_comments = true
10 |
--------------------------------------------------------------------------------
/src/arrays.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | prelude::*,
3 | ops::{UnionPair, IntersectionPair},
4 | };
5 | use std::{iter::Map, convert::TryInto};
6 | use itertools::{Itertools, structs::MultiProduct};
7 |
8 | impl Space for [D; N] {
9 | type Value = [D::Value; N];
10 |
11 | fn is_empty(&self) -> bool { self.iter().any(|d| d.is_empty()) }
12 |
13 | fn contains(&self, val: &Self::Value) -> bool {
14 | self.iter().zip(val.iter()).all(|(d, x)| d.contains(x))
15 | }
16 | }
17 |
18 | impl FiniteSpace for [D; N] {
19 | fn cardinality(&self) -> usize { self.iter().map(|d| d.cardinality()).product() }
20 | }
21 |
22 | impl IterableSpace for [D; N]
23 | where
24 | D::Value: Clone,
25 | D::ElemIter: Clone,
26 | {
27 | // TODO - Ideally, we would replace MultiProduct with an optimised implementation
28 | // for yielding arrays directly, not using an intermediate Vec.
29 | type ElemIter = Map<
30 | MultiProduct,
31 | fn(Vec) -> [D::Value; N]
32 | >;
33 |
34 | fn elements(&self) -> Self::ElemIter {
35 | let iters: Vec<_> = self.iter().map(|s| s.elements()).collect();
36 |
37 | iters.into_iter().multi_cartesian_product().map(|x| {
38 | x.try_into().map_err(|_| ()).unwrap()
39 | })
40 | }
41 | }
42 |
43 | impl Union for [D; N]
44 | where
45 | D: Space,
46 | S: Space,
47 | {
48 | type Output = UnionPair;
49 |
50 | fn union(self, rhs: S) -> Self::Output { UnionPair(self, rhs) }
51 | }
52 |
53 | impl Intersection for [D; N]
54 | where
55 | D: Space,
56 | S: Space,
57 | {
58 | type Output = IntersectionPair;
59 |
60 | fn intersect(self, rhs: S) -> Option {
61 | Some(IntersectionPair(self, rhs))
62 | }
63 | }
64 |
65 | impl Closure for [D; N] {
66 | type Output = [D::Output; N];
67 |
68 | fn closure(self) -> Self::Output { self.map(|d| d.closure()) }
69 | }
70 |
71 | #[cfg(test)]
72 | mod tests {
73 | use super::*;
74 | use crate::intervals::Interval;
75 |
76 | #[test]
77 | fn test_is_empty() {
78 | assert!([
79 | Interval::open_unchecked(0.0f64, 0.0),
80 | Interval::open_unchecked(1.0, 1.0),
81 | ].is_empty());
82 |
83 | assert!([
84 | Interval::open_unchecked(0.0f64, 0.0),
85 | Interval::open_unchecked(1.0, 2.0),
86 | ].is_empty());
87 |
88 | assert!(![
89 | Interval::degenerate(0.0f64),
90 | Interval::unit()
91 | ].is_empty());
92 | }
93 |
94 | #[test]
95 | fn test_contains() {
96 | let s = [
97 | Interval::degenerate(0.0f64),
98 | Interval::unit()
99 | ];
100 |
101 | for b in [0.0, 0.25, 0.5, 0.75, 1.0] {
102 | assert!(s.contains(&[0.0, b]));
103 |
104 | for a in [-1.0, 1.0] {
105 | assert!(!s.contains(&[a, b]));
106 | }
107 | }
108 | }
109 |
110 | #[test]
111 | fn test_card() {
112 | assert_eq!([
113 | Interval::degenerate(1usize),
114 | Interval::degenerate(0),
115 | ].cardinality(), 1);
116 |
117 | assert_eq!([
118 | Interval::lorc_unchecked(0usize, 2usize),
119 | Interval::lorc_unchecked(0usize, 2usize)
120 | ].cardinality(), 4);
121 |
122 | assert_eq!([
123 | Interval::closed_unchecked(0usize, 2usize),
124 | Interval::closed_unchecked(0, 100)
125 | ].cardinality(), 303);
126 | }
127 |
128 | #[test]
129 | fn test_values() {
130 | let space = [Interval::closed_unchecked(0, 1), Interval::closed_unchecked(2, 3)];
131 | let values: Vec<_> = space.elements().collect();
132 |
133 | assert_eq!(values, vec![
134 | [0, 2],
135 | [0, 3],
136 | [1, 2],
137 | [1, 3],
138 | ])
139 | }
140 |
141 | #[test]
142 | fn test_union() {
143 | let x = [
144 | Interval::closed_unchecked(0.0, 1.0),
145 | Interval::closed_unchecked(0.0, 1.0)
146 | ];
147 | let y = [
148 | Interval::closed_unchecked(5.0, 6.0),
149 | Interval::closed_unchecked(5.0, 6.0)
150 | ];
151 | let z = x.union(y);
152 |
153 | assert!(z.contains(&[0.0, 0.0]));
154 | assert!(z.contains(&[1.0, 1.0]));
155 | assert!(z.contains(&[5.0, 5.0]));
156 | assert!(z.contains(&[6.0, 6.0]));
157 |
158 | assert!(!z.contains(&[0.0, 5.0]));
159 | assert!(!z.contains(&[1.0, 6.0]));
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/discrete.rs:
--------------------------------------------------------------------------------
1 | //! Module for discrete scalar spaces.
2 | use crate::{intervals, ops};
3 | use num_traits::{PrimInt, Signed, Unsigned};
4 |
5 | /// Build a space representing binary (base-2) values.
6 | pub fn binary() -> Binary {
7 | intervals::Interval {
8 | left: intervals::bounds::Closed(false),
9 | right: intervals::bounds::Closed(true),
10 | }
11 | }
12 |
13 | pub type Binary = intervals::Closed;
14 |
15 | /// Build a space representing the set of signed integers.
16 | pub fn integers() -> Integers { intervals::Interval::unbounded() }
17 |
18 | pub type Integers = intervals::Unbounded;
19 |
20 | /// Build a space representing the set of non-zero signed integers.
21 | pub fn non_zero_integers() -> NonZeroIntegers {
22 | let x = intervals::Interval::right_open(V::zero());
23 | let y = intervals::Interval::left_open(V::zero());
24 |
25 | ops::UnionPair(x, y)
26 | }
27 |
28 | pub type NonZeroIntegers = ops::UnionPair, intervals::LeftOpen>;
29 |
30 | /// Build a space representing the set of unsigned integers.
31 | pub fn non_negative_integers() -> NonNegativeIntegers {
32 | intervals::Interval::left_closed(V::zero())
33 | }
34 |
35 | pub type NonNegativeIntegers = intervals::LeftClosed;
36 |
37 | /// Build a space representing the set of unsigned integers.
38 | pub fn positive_integers() -> PositiveIntegers {
39 | intervals::Interval::left_open(V::zero())
40 | }
41 |
42 | pub type PositiveIntegers = intervals::LeftOpen;
43 |
44 | /// Build a space representing the set of unsigned integers.
45 | pub fn non_positive_integers() -> NonPositiveIntegers {
46 | intervals::Interval::right_closed(V::zero())
47 | }
48 |
49 | pub type NonPositiveIntegers = intervals::RightClosed;
50 |
51 | /// Build a space representing the set of unsigned integers.
52 | pub fn negative_integers() -> NegativeIntegers {
53 | intervals::Interval::right_open(V::zero())
54 | }
55 |
56 | pub type NegativeIntegers = intervals::RightOpen;
57 |
58 | /// Build a space representing the set of natural numbers.
59 | pub fn naturals() -> Naturals { positive_integers() }
60 |
61 | pub type Naturals = PositiveIntegers;
62 |
--------------------------------------------------------------------------------
/src/interval.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | Space, OrderedSpace, FiniteSpace, IterableSpace,
3 | ops::{Union, UnionPair, Intersection, Closure}
4 | };
5 | use std::ops::{RangeInclusive, RangeTo, RangeFrom, RangeToInclusive};
6 | use intervals::{Interval, bounds::{self, OpenOrClosed}};
7 |
8 | ///////////////////////////////////////////////////////////////////
9 | // Core Implementations
10 | ///////////////////////////////////////////////////////////////////
11 | impl Space for Interval
12 | where
13 | L: bounds::Bound,
14 | R: bounds::Bound,
15 |
16 | L::Value: Clone,
17 | {
18 | type Value = L::Value;
19 |
20 | fn is_empty(&self) -> bool {
21 | match (self.left.value(), self.right.value()) {
22 | (Some(l), Some(r)) if !self.left.is_closed() && !self.right.is_closed() => l == r,
23 | _ => false,
24 | }
25 | }
26 |
27 | fn contains(&self, val: &L::Value) -> bool {
28 | use OpenOrClosed::*;
29 |
30 | let check_left = self.inf().map_or(true, |l| match l {
31 | Open(ref l) => val > l,
32 | Closed(ref l) => val >= l,
33 | });
34 | let check_right = self.sup().map_or(true, |r| match r {
35 | Open(ref r) => val < r,
36 | Closed(ref r) => val <= r,
37 | });
38 |
39 | check_left && check_right
40 | }
41 | }
42 |
43 | impl OrderedSpace for Interval
44 | where
45 | L: bounds::Bound,
46 | R: bounds::Bound,
47 |
48 | L::Value: Clone,
49 | {
50 | fn inf(&self) -> Option> {
51 | self.left.value().cloned().map(|l| if self.left.is_open() {
52 | OpenOrClosed::Open(l)
53 | } else {
54 | OpenOrClosed::Closed(l)
55 | })
56 | }
57 |
58 | fn sup(&self) -> Option> {
59 | self.right.value().cloned().map(|r| if self.right.is_open() {
60 | OpenOrClosed::Open(r)
61 | } else {
62 | OpenOrClosed::Closed(r)
63 | })
64 | }
65 | }
66 |
67 | macro_rules! impl_fs {
68 | ($v:ident; $left:ty, $right:ty; |$me:ident| $inner:block) => {
69 | impl<$v> FiniteSpace for Interval<$left, $right>
70 | where
71 | $v: num_traits::PrimInt,
72 | <$v as std::ops::Sub>::Output: num_traits::NumCast,
73 | {
74 | fn cardinality(&$me) -> usize {
75 | num_traits::NumCast::from($inner).unwrap()
76 | }
77 | }
78 | }
79 | }
80 |
81 | #[inline]
82 | fn card_oo(left: V, right: V) -> ::Output {
83 | let d = right - left;
84 |
85 | if d <= V::one() { V::zero() } else { d }
86 | }
87 |
88 | #[inline]
89 | fn card_co(left: V, right: V) -> ::Output {
90 | right - left
91 | }
92 |
93 | #[inline]
94 | fn card_cc(left: V, right: V) -> ::Output {
95 | right - left + V::one()
96 | }
97 |
98 | impl_fs!(V; bounds::Closed, bounds::Closed; |self| { card_cc(self.left.0, self.right.0) });
99 | impl_fs!(V; bounds::Closed, bounds::Open; |self| { card_co(self.left.0, self.right.0) });
100 | impl_fs!(V; bounds::Closed, bounds::OpenOrClosed; |self| {
101 | match self.right {
102 | bounds::OpenOrClosed::Open(r) => card_co(self.left.0, r),
103 | bounds::OpenOrClosed::Closed(r) => card_cc(self.left.0, r),
104 | }
105 | });
106 |
107 | impl_fs!(V; bounds::Open, bounds::Closed; |self| { card_co(self.left.0, self.right.0) });
108 | impl_fs!(V; bounds::Open, bounds::Open; |self| { card_oo(self.left.0, self.right.0) });
109 | impl_fs!(V; bounds::Open, bounds::OpenOrClosed; |self| {
110 | match self.right {
111 | bounds::OpenOrClosed::Open(r) => card_oo(self.left.0, r),
112 | bounds::OpenOrClosed::Closed(r) => card_co(self.left.0, r),
113 | }
114 | });
115 |
116 | impl_fs!(V; bounds::OpenOrClosed, bounds::Closed; |self| {
117 | match self.left {
118 | bounds::OpenOrClosed::Open(l) => card_co(l, self.right.0),
119 | bounds::OpenOrClosed::Closed(l) => card_cc(l, self.right.0),
120 | }
121 | });
122 | impl_fs!(V; bounds::OpenOrClosed, bounds::Open; |self| {
123 | match self.left {
124 | bounds::OpenOrClosed::Open(l) => card_oo(l, self.right.0),
125 | bounds::OpenOrClosed::Closed(l) => card_co(l, self.right.0),
126 | }
127 | });
128 | impl_fs!(V; bounds::OpenOrClosed, bounds::OpenOrClosed; |self| {
129 | use intervals::bounds::OpenOrClosed::{Open, Closed};
130 |
131 | match (self.left, self.right) {
132 | (Open(l), Open(r)) => card_oo(l, r),
133 | (Closed(l), Open(r)) | (Open(l), Closed(r)) => card_co(l, r),
134 | (Closed(l), Closed(r)) => card_cc(l, r),
135 | }
136 | });
137 |
138 | ///////////////////////////////////////////////////////////////////
139 | // Iter Implementations
140 | ///////////////////////////////////////////////////////////////////
141 | macro_rules! impl_iter {
142 | ($v:ident; $left:ty, $right:ty; |$me:ident| -> $out:ty $code:block) => {
143 | impl<$v> IterableSpace for Interval<$left, $right>
144 | where
145 | $v: num_traits::PrimInt,
146 |
147 | $out: Iterator- ,
148 | {
149 | type ElemIter = $out;
150 |
151 | fn elements(&$me) -> Self::ElemIter { $code }
152 | }
153 | }
154 | }
155 |
156 | // Closed + ...
157 | impl_iter!(V; bounds::Closed, bounds::Closed; |self| -> RangeInclusive {
158 | self.left.0..=self.right.0
159 | });
160 | impl_iter!(V; bounds::Closed, bounds::Open; |self| -> RangeInclusive {
161 | self.left.0..=(self.right.0 - V::one())
162 | });
163 | impl_iter!(V; bounds::Closed, bounds::OpenOrClosed; |self| -> RangeInclusive {
164 | match self.right {
165 | OpenOrClosed::Open(r) => self.left.0..=(r - V::one()),
166 | OpenOrClosed::Closed(r) => self.left.0..=r,
167 | }
168 | });
169 |
170 | impl IterableSpace for Interval, bounds::NoBound>
171 | where
172 | V: num_traits::PrimInt,
173 |
174 | RangeFrom: Iterator
- ,
175 | {
176 | type ElemIter = RangeFrom;
177 |
178 | fn elements(&self) -> Self::ElemIter {
179 | self.left.0..
180 | }
181 | }
182 |
183 | // Open + ...
184 | impl_iter!(V; bounds::Open, bounds::Closed; |self| -> RangeInclusive {
185 | (self.left.0 + V::one())..=self.right.0
186 | });
187 | impl_iter!(V; bounds::Open, bounds::Open; |self| -> RangeInclusive {
188 | (self.left.0 + V::one())..=(self.right.0 - V::one())
189 | });
190 | impl_iter!(V; bounds::Open, bounds::OpenOrClosed; |self| -> RangeInclusive {
191 | let l = self.left.0 + V::one();
192 |
193 | match self.right {
194 | OpenOrClosed::Open(r) => l..=(r - V::one()),
195 | OpenOrClosed::Closed(r) => l..=r,
196 | }
197 | });
198 |
199 | impl IterableSpace for Interval, bounds::NoBound>
200 | where
201 | V: num_traits::PrimInt,
202 |
203 | RangeFrom: Iterator
- ,
204 | {
205 | type ElemIter = RangeFrom;
206 |
207 | fn elements(&self) -> Self::ElemIter {
208 | (self.left.0 + V::one())..
209 | }
210 | }
211 |
212 | // OpenOrClosed + ...
213 | impl_iter!(V; bounds::OpenOrClosed, bounds::Closed; |self| -> RangeInclusive {
214 | let r = self.right.0;
215 |
216 | match self.left {
217 | OpenOrClosed::Open(l) => (l + V::one())..=r,
218 | OpenOrClosed::Closed(l) => l..=r,
219 | }
220 | });
221 | impl_iter!(V; bounds::OpenOrClosed, bounds::Open; |self| -> RangeInclusive {
222 | let r = self.right.0 - V::one();
223 |
224 | match self.left {
225 | OpenOrClosed::Open(l) => (l + V::one())..=(r - V::one()),
226 | OpenOrClosed::Closed(l) => l..=r,
227 | }
228 | });
229 | impl_iter!(V; bounds::OpenOrClosed, bounds::OpenOrClosed; |self| -> RangeInclusive {
230 | match (self.left, self.right) {
231 | (OpenOrClosed::Open(l), OpenOrClosed::Open(r)) => (l + V::one())..=(r - V::one()),
232 | (OpenOrClosed::Open(l), OpenOrClosed::Closed(r)) => (l + V::one())..=r,
233 | (OpenOrClosed::Closed(l), OpenOrClosed::Open(r)) => l..=(r - V::one()),
234 | (OpenOrClosed::Closed(l), OpenOrClosed::Closed(r)) => l..=r,
235 | }
236 | });
237 |
238 | impl IterableSpace for Interval, bounds::NoBound>
239 | where
240 | V: num_traits::PrimInt,
241 |
242 | RangeFrom: Iterator
- ,
243 | {
244 | type ElemIter = RangeFrom;
245 |
246 | fn elements(&self) -> Self::ElemIter {
247 | match self.left {
248 | OpenOrClosed::Open(l) => (l + V::one())..,
249 | OpenOrClosed::Closed(l) => l..,
250 | }
251 | }
252 | }
253 |
254 | // NoBound + ...
255 | impl IterableSpace for Interval, bounds::Closed>
256 | where
257 | V: num_traits::PrimInt,
258 |
259 | RangeToInclusive: Iterator
- ,
260 | {
261 | type ElemIter = RangeToInclusive;
262 |
263 | fn elements(&self) -> Self::ElemIter {
264 | ..=self.right.0
265 | }
266 | }
267 |
268 | impl IterableSpace for Interval, bounds::Open>
269 | where
270 | V: num_traits::PrimInt,
271 |
272 | RangeTo: Iterator
- ,
273 | {
274 | type ElemIter = RangeTo;
275 |
276 | fn elements(&self) -> Self::ElemIter {
277 | ..self.right.0
278 | }
279 | }
280 |
281 | impl IterableSpace for Interval, bounds::OpenOrClosed>
282 | where
283 | V: num_traits::PrimInt,
284 |
285 | RangeToInclusive: Iterator
- ,
286 | {
287 | type ElemIter = RangeToInclusive;
288 |
289 | fn elements(&self) -> Self::ElemIter {
290 | match self.right {
291 | OpenOrClosed::Open(r) => ..=(r - V::one()),
292 | OpenOrClosed::Closed(r) => ..=r,
293 | }
294 | }
295 | }
296 |
297 | ///////////////////////////////////////////////////////////////////
298 | // Op Implementations
299 | ///////////////////////////////////////////////////////////////////
300 | impl Union> for Interval
301 | where
302 | L: bounds::Bound,
303 | R: bounds::Bound,
304 |
305 | LL: bounds::Bound,
306 | RR: bounds::Bound,
307 |
308 | Interval: Space,
309 | Interval: Space,
310 | {
311 | type Output = UnionPair, Interval>;
312 |
313 | fn union(self, rhs: Interval) -> Self::Output { UnionPair(self, rhs) }
314 | }
315 |
316 | impl Intersection> for Interval
317 | where
318 | L: bounds::Pinch,
319 | R: bounds::Pinch,
320 |
321 | L::Value: PartialOrd,
322 |
323 | LL: bounds::Bound,
324 | RR: bounds::Bound,
325 |
326 | Interval: crate::Space,
327 | Interval: crate::Space,
328 |
329 | intervals::IntersectionOf: crate::Space,
330 | bounds::Validator: bounds::ValidateBounds,
331 | {
332 | type Output = intervals::IntersectionOf;
333 |
334 | fn intersect(self, rhs: Interval) -> Option {
335 | Interval::intersect(self, rhs)
336 | }
337 | }
338 |
339 | impl Closure for Interval
340 | where
341 | L: bounds::Bound,
342 | R: bounds::Bound,
343 |
344 | Interval: Space,
345 | Interval: Space,
346 | {
347 | type Output = Interval;
348 |
349 | fn closure(self) -> Self::Output {
350 | Interval {
351 | left: self.left.with_limit_point(),
352 | right: self.right.with_limit_point(),
353 | }
354 | }
355 | }
356 |
357 | impl Closure for UnionPair, Interval>
358 | where
359 | L: bounds::Unroll,
360 | R: bounds::Unroll,
361 |
362 | LL: bounds::Bound,
363 | RR: bounds::Bound,
364 |
365 | L::Value: Clone,
366 | {
367 | type Output = crate::intervals::UnionClosureOf;
368 |
369 | fn closure(self) -> Self::Output {
370 | Interval::union_closure(self.0, self.1)
371 | }
372 | }
373 |
374 | #[cfg(test)]
375 | mod tests {
376 | use super::*;
377 |
378 | #[test]
379 | fn test_closed_intersection() {
380 | let a = Interval::closed_unchecked(0.0, 1.0);
381 | let b = Interval::closed_unchecked(1.0, 2.0);
382 | let c = Interval::closed_unchecked(2.0, 3.0);
383 |
384 | assert_eq!(a.intersect(a).unwrap(), a);
385 |
386 | assert_eq!(a.intersect(b).unwrap(), Interval::degenerate(1.0));
387 | assert_eq!(b.intersect(c).unwrap(), Interval::degenerate(2.0));
388 |
389 | assert_eq!(a.intersect(c), None);
390 | }
391 |
392 | #[test]
393 | fn test_iter_cc() {
394 | let vals: Vec<_> = Interval::closed_unchecked(0, 5).elements().collect();
395 |
396 | assert_eq!(vals, vec![0, 1, 2, 3, 4, 5]);
397 |
398 | let vals: Vec<_> = Interval::new_unchecked(
399 | bounds::Closed(0),
400 | bounds::OpenOrClosed::Closed(5)
401 | ).elements().collect();
402 |
403 | assert_eq!(vals, vec![0, 1, 2, 3, 4, 5]);
404 |
405 | let vals: Vec<_> = Interval::new_unchecked(
406 | bounds::OpenOrClosed::Closed(0),
407 | bounds::OpenOrClosed::Closed(5)
408 | ).elements().collect();
409 |
410 | assert_eq!(vals, vec![0, 1, 2, 3, 4, 5]);
411 | }
412 |
413 | #[test]
414 | fn test_iter_co() {
415 | let vals: Vec<_> = Interval::lcro_unchecked(0, 5).elements().collect();
416 |
417 | assert_eq!(vals, vec![0, 1, 2, 3, 4]);
418 |
419 | let vals: Vec<_> = Interval::new_unchecked(
420 | bounds::Closed(0),
421 | bounds::OpenOrClosed::Open(5)
422 | ).elements().collect();
423 |
424 | assert_eq!(vals, vec![0, 1, 2, 3, 4]);
425 |
426 | let vals: Vec<_> = Interval::new_unchecked(
427 | bounds::OpenOrClosed::Closed(0),
428 | bounds::OpenOrClosed::Open(5)
429 | ).elements().collect();
430 |
431 | assert_eq!(vals, vec![0, 1, 2, 3, 4]);
432 | }
433 |
434 | #[test]
435 | fn test_iter_oc() {
436 | let vals: Vec<_> = Interval::lorc_unchecked(0, 5).elements().collect();
437 |
438 | assert_eq!(vals, vec![1, 2, 3, 4, 5]);
439 |
440 | let vals: Vec<_> = Interval::new_unchecked(
441 | bounds::Open(0),
442 | bounds::OpenOrClosed::Closed(5)
443 | ).elements().collect();
444 |
445 | assert_eq!(vals, vec![1, 2, 3, 4, 5]);
446 |
447 | let vals: Vec<_> = Interval::new_unchecked(
448 | bounds::OpenOrClosed::Open(0),
449 | bounds::OpenOrClosed::Closed(5)
450 | ).elements().collect();
451 |
452 | assert_eq!(vals, vec![1, 2, 3, 4, 5]);
453 | }
454 |
455 | #[test]
456 | fn test_iter_oo() {
457 | let vals: Vec<_> = Interval::open_unchecked(0, 5).elements().collect();
458 |
459 | assert_eq!(vals, vec![1, 2, 3, 4]);
460 |
461 | let vals: Vec<_> = Interval::new_unchecked(
462 | bounds::Open(0),
463 | bounds::OpenOrClosed::Open(5)
464 | ).elements().collect();
465 |
466 | assert_eq!(vals, vec![1, 2, 3, 4]);
467 |
468 | let vals: Vec<_> = Interval::new_unchecked(
469 | bounds::OpenOrClosed::Open(0),
470 | bounds::OpenOrClosed::Open(5)
471 | ).elements().collect();
472 |
473 | assert_eq!(vals, vec![1, 2, 3, 4]);
474 | }
475 | }
476 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Set/space primitives for defining machine learning problems.
2 | //!
3 | //! `spaces` provides set/space primitives to be used for defining properties of
4 | //! machine learning problems. Traits such as `Space`, and it's derivatives, may
5 | //! be used to define state/action spaces, for example.
6 | extern crate itertools;
7 | extern crate num_traits;
8 |
9 | pub mod discrete;
10 | pub mod real;
11 |
12 | pub extern crate intervals;
13 |
14 | use intervals::bounds::OpenOrClosed;
15 |
16 | mod arrays;
17 | mod interval;
18 | mod partitions;
19 | mod option;
20 | mod tuples;
21 |
22 | ///////////////////////////////////////////////////////////////////////////
23 | // Core Definitions
24 | ///////////////////////////////////////////////////////////////////////////
25 | /// Trait for types representing spaces (i.e. abstract collections).
26 | pub trait Space {
27 | /// The data representation for elements of the space.
28 | type Value;
29 |
30 | /// Return true if the space contains no values.
31 | ///
32 | /// ```
33 | /// # extern crate spaces;
34 | /// # use spaces::{Space, ops::Intersection, real};
35 | /// let space = real::reals::();
36 | /// assert!(!space.is_empty());
37 | ///
38 | /// let space = real::negative_reals::().intersect(
39 | /// real::positive_reals::()
40 | /// );
41 | /// assert!(space.is_none());
42 | /// ```
43 | fn is_empty(&self) -> bool;
44 |
45 | /// Returns true iff `val` is contained within the space.
46 | fn contains(&self, val: &Self::Value) -> bool;
47 | }
48 |
49 | /// Trait for types representing ordered spaces.
50 | pub trait OrderedSpace: Space
51 | where Self::Value: PartialOrd
52 | {
53 | /// Return the infimum of the space, if it exists.
54 | fn inf(&self) -> Option>;
55 |
56 | /// Returns the supremum of the space, if it exists.
57 | fn sup(&self) -> Option>;
58 |
59 | /// Returns true iff `self` has a well-defined infimum.
60 | fn is_lower_bounded(&self) -> bool { self.inf().is_some() }
61 |
62 | /// Returns true iff `self` has a well-defined supremum.
63 | fn is_upper_bounded(&self) -> bool { self.sup().is_some() }
64 |
65 | /// Returns true iff `self` is bounded above and below.
66 | fn is_bounded(&self) -> bool { self.is_lower_bounded() && self.is_upper_bounded() }
67 | }
68 |
69 | /// Trait for defining spaces containing a finite set of values.
70 | pub trait FiniteSpace: Space {
71 | /// Return the cardinality of the space.
72 | ///
73 | /// The cardinality of a space is given by the number of elements
74 | /// contained within said set.
75 | fn cardinality(&self) -> usize;
76 | }
77 |
78 | /// Trait for `Space` types that have an associated value iterator.
79 | pub trait IterableSpace: Space {
80 | /// The associated iterator type.
81 | type ElemIter: Iterator
- ;
82 |
83 | /// Return an iterator over the elements of this space.
84 | fn elements(&self) -> Self::ElemIter;
85 | }
86 |
87 | ///////////////////////////////////////////////////////////////////////////
88 | // Set Operations
89 | ///////////////////////////////////////////////////////////////////////////
90 | pub mod ops;
91 |
92 | ///////////////////////////////////////////////////////////////////////////
93 | // Prelude
94 | ///////////////////////////////////////////////////////////////////////////
95 | mod prelude {
96 | pub use super::{
97 | ops::{Union, Intersection, Closure},
98 | FiniteSpace, OrderedSpace, Space, IterableSpace,
99 | };
100 | }
101 |
--------------------------------------------------------------------------------
/src/ops/intersection.rs:
--------------------------------------------------------------------------------
1 | use crate::{Space, OrderedSpace};
2 | use super::{OoC, LRB, min_val, max_val, Union, UnionPair};
3 |
4 | fn clip_ooc(x: OoC, y: OoC, cmp: impl Fn(T, T) -> LRB) -> OoC {
5 | use crate::intervals::bounds::OpenOrClosed::*;
6 |
7 | match (x, y) {
8 | (Open(x), Open(y)) => cmp(x, y).translate(Open, Open, Open),
9 | (Open(x), Closed(y)) => cmp(x, y).translate(Open, Open, Closed),
10 | (Closed(x), Open(y)) => cmp(x, y).translate(Closed, Open, Open),
11 | (Closed(x), Closed(y)) => cmp(x, y).translate(Closed, Closed, Closed),
12 | }
13 | }
14 |
15 | /// Trait for types that support the intersect operation.
16 | ///
17 | /// The intersection of a collection of sets is the set containing all
18 | /// such elements that are present in each set within the collection.
19 | pub trait Intersection: Space {
20 | type Output: Space;
21 |
22 | fn intersect(self, rhs: Rhs) -> Option;
23 | }
24 |
25 | pub type IntersectionOf
= >::Output;
26 |
27 | // TODO - Add warning to docstring that explains why this type should be avoided.
28 | // Namely, that you can lead to panic! when calling is_empty().
29 | /// Type representing the intersection of two spaces.
30 | #[derive(Copy, Clone, PartialEq, Eq, Debug)]
31 | pub struct IntersectionPair>(pub A, pub B);
32 |
33 | impl Space for IntersectionPair
34 | where
35 | A: Space,
36 | B: Space,
37 | {
38 | type Value = A::Value;
39 |
40 | fn is_empty(&self) -> bool {
41 | if self.0.is_empty() || self.1.is_empty() { return true; }
42 |
43 | panic!(
44 | "It's not currently possible to evaluate IntersectionPair::is_empty when neither \
45 | interior space are empty."
46 | )
47 | }
48 |
49 | fn contains(&self, val: &A::Value) -> bool { self.0.contains(val) && self.1.contains(val) }
50 | }
51 |
52 | impl OrderedSpace for IntersectionPair
53 | where
54 | A: OrderedSpace,
55 | B: OrderedSpace,
56 |
57 | A::Value: PartialOrd,
58 | {
59 | fn inf(&self) -> Option> {
60 | match (self.0.inf(), self.1.inf()) {
61 | (Some(left), Some(right)) => Some(clip_ooc(left, right, min_val)),
62 | _ => None,
63 | }
64 | }
65 |
66 | fn sup(&self) -> Option> {
67 | match (self.0.sup(), self.1.sup()) {
68 | (Some(left), Some(right)) => Some(clip_ooc(left, right, max_val)),
69 | _ => None,
70 | }
71 | }
72 | }
73 |
74 | impl Union for IntersectionPair
75 | where
76 | A: Space,
77 | B: Space,
78 | Rhs: Space,
79 | {
80 | type Output = UnionPair;
81 |
82 | fn union(self, rhs: Rhs) -> Self::Output { UnionPair(self, rhs) }
83 | }
84 |
85 | impl Intersection for IntersectionPair
86 | where
87 | A: Space,
88 | B: Space,
89 | Rhs: Space,
90 | {
91 | type Output = IntersectionPair;
92 |
93 | fn intersect(self, rhs: Rhs) -> Option {
94 | if self.0.is_empty() || self.1.is_empty() { return None; }
95 |
96 | Some(IntersectionPair(self, rhs))
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/ops/mod.rs:
--------------------------------------------------------------------------------
1 | //! Module for operations acting on spaces.
2 | use crate::Space;
3 |
4 | type OoC = crate::intervals::bounds::OpenOrClosed;
5 |
6 | enum LRB { Left(T), Both(T), Right(T), }
7 |
8 | impl LRB {
9 | fn translate(
10 | self,
11 | left: impl FnOnce(T) -> OoC,
12 | both: impl FnOnce(T) -> OoC,
13 | right: impl FnOnce(T) -> OoC,
14 | ) -> OoC {
15 | match self {
16 | LRB::Left(x) => left(x),
17 | LRB::Both(x) => both(x),
18 | LRB::Right(x) => right(x),
19 | }
20 | }
21 | }
22 |
23 | fn min_val(x: T, y: T) -> LRB {
24 | if x < y { LRB::Left(x) } else if x == y { LRB::Both(x) } else { LRB::Right(y) }
25 | }
26 |
27 | fn max_val(x: T, y: T) -> LRB {
28 | if x < y { LRB::Right(y) } else if x == y { LRB::Both(x) } else { LRB::Left(x) }
29 | }
30 |
31 | /// Trait for types that have a well-defined closure.
32 | pub trait Closure: Space {
33 | type Output: Space;
34 |
35 | fn closure(self) -> Self::Output;
36 | }
37 |
38 | pub type ClosureOf = ::Output;
39 |
40 | mod union;
41 | pub use self::union::{Union, UnionOf, UnionClosureOf, UnionPair};
42 |
43 | mod intersection;
44 | pub use self::intersection::{Intersection, IntersectionOf, IntersectionPair};
45 |
--------------------------------------------------------------------------------
/src/ops/union.rs:
--------------------------------------------------------------------------------
1 | use crate::{OrderedSpace, Space};
2 | use super::{OoC, LRB, min_val, max_val, Intersection, IntersectionPair, Closure, ClosureOf};
3 |
4 | fn clip_ooc(x: OoC, y: OoC, cmp: impl Fn(T, T) -> LRB) -> OoC {
5 | use crate::intervals::bounds::OpenOrClosed::*;
6 |
7 | match (x, y) {
8 | (Open(x), Open(y)) => cmp(x, y).translate(Open, Open, Open),
9 | (Open(x), Closed(y)) => cmp(x, y).translate(Open, Closed, Closed),
10 | (Closed(x), Open(y)) => cmp(x, y).translate(Closed, Closed, Open),
11 | (Closed(x), Closed(y)) => cmp(x, y).translate(Closed, Closed, Closed),
12 | }
13 | }
14 |
15 | /// Trait for types that support the union operation.
16 | ///
17 | /// The union of a collection of sets is the set containing all
18 | /// such elements that are present in at least one set within the collection.
19 | pub trait Union: Space {
20 | type Output: Space;
21 |
22 | fn union(self, rhs: Rhs) -> Self::Output;
23 |
24 | /// Compute the union-closure of the space.
25 | fn union_closure(self, rhs: Rhs) -> UnionClosureOf
26 | where
27 | Self: Sized,
28 | Self::Output: Closure,
29 | {
30 | self.union(rhs).closure()
31 | }
32 | }
33 |
34 | pub type UnionOf = >::Output;
35 | pub type UnionClosureOf = ClosureOf>;
36 |
37 | /// Type representing the union of two arbitrary spaces.
38 | #[derive(Copy, Clone, PartialEq, Eq, Debug)]
39 | pub struct UnionPair>(pub A, pub B);
40 |
41 | impl Space for UnionPair
42 | where
43 | A: Space,
44 | B: Space,
45 | {
46 | type Value = A::Value;
47 |
48 | fn is_empty(&self) -> bool { self.0.is_empty() && self.1.is_empty() }
49 |
50 | fn contains(&self, val: &A::Value) -> bool { self.0.contains(val) || self.1.contains(val) }
51 | }
52 |
53 | impl OrderedSpace for UnionPair
54 | where
55 | A: OrderedSpace,
56 | B: OrderedSpace,
57 |
58 | A::Value: PartialOrd,
59 | {
60 | fn inf(&self) -> Option> {
61 | match (self.0.inf(), self.1.inf()) {
62 | (Some(left), Some(right)) => Some(clip_ooc(left, right, min_val)),
63 | _ => None,
64 | }
65 | }
66 |
67 | fn sup(&self) -> Option