chronoloompy 0.1.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Amine El Ouassouli
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.
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: chronoloompy
3
+ Version: 0.1.1
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: Programming Language :: Rust
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Python :: Implementation :: CPython
11
+ Classifier: Typing :: Typed
12
+ Requires-Dist: pydantic>=2.13.4,<3
13
+ Requires-Dist: numpy>=2.5.2,<3
14
+ License-File: LICENSE
15
+ Summary: Temporal data processing primitives, backed by the chronoloom Rust crate.
16
+ Keywords: time-series,temporal,intervals,streams,rust
17
+ Author-email: Amine El Ouassouli <a.elouassouli@gmail.com>
18
+ License-Expression: MIT
19
+ Requires-Python: >=3.12
20
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
21
+ Project-URL: Changelog, https://github.com/AElOuassouli/chronoloom/blob/main/chronoloompy/CHANGELOG.md
22
+ Project-URL: Homepage, https://github.com/AElOuassouli/chronoloom
23
+ Project-URL: Repository, https://github.com/AElOuassouli/chronoloom
24
+
25
+ # chronoloompy
26
+
27
+ Temporal data processing primitives for Python, backed by the
28
+ [`chronoloom`](../chronoloom) Rust crate.
29
+
30
+ ```sh
31
+ pip install chronoloompy
32
+ ```
33
+
34
+ ```python
35
+ from chronoloompy.models import TimeIntervalEvent
36
+
37
+ event = TimeIntervalEvent(start_timestamp=0, end_timestamp=5)
38
+ ```
39
+
40
+ Intervals are half-open — `start` is included, `end` is excluded.
41
+
42
+ ## Relationship to `chronoloom`
43
+
44
+ `chronoloom` is a standalone Rust crate published to
45
+ [crates.io](https://crates.io/crates/chronoloom) and usable from any Rust
46
+ project. `chronoloompy` wraps it through a thin pyo3 binding
47
+ ([`rust/`](rust)) and adds a Python-native layer on top — pydantic models
48
+ today, more later.
49
+
50
+ Performance-critical algebra should be implemented once in `chronoloom` and
51
+ exposed here, rather than reimplemented in Python.
52
+
53
+ ## Development
54
+
55
+ Requires [uv](https://docs.astral.sh/uv/) and a Rust toolchain (managed
56
+ automatically via the repo's `rust-toolchain.toml` if you have
57
+ [rustup](https://rustup.rs) installed).
58
+
59
+ ```sh
60
+ make install # uv sync -- builds the extension
61
+ make fmt # ruff format + cargo fmt
62
+ make lint # ruff, mypy, cargo fmt --check, clippy
63
+ make test # pytest + cargo test
64
+ make build # release wheel into dist/
65
+ ```
66
+
67
+ The extension rebuilds automatically when `rust/**/*.rs`, `rust/Cargo.toml`,
68
+ or the `../chronoloom` sources change — see `cache-keys` in `pyproject.toml`.
69
+
70
+ Run `make setup` from the repository root once to install the git hooks.
71
+
72
+ ## License
73
+
74
+ MIT — see [LICENSE](LICENSE).
75
+
@@ -0,0 +1,50 @@
1
+ # chronoloompy
2
+
3
+ Temporal data processing primitives for Python, backed by the
4
+ [`chronoloom`](../chronoloom) Rust crate.
5
+
6
+ ```sh
7
+ pip install chronoloompy
8
+ ```
9
+
10
+ ```python
11
+ from chronoloompy.models import TimeIntervalEvent
12
+
13
+ event = TimeIntervalEvent(start_timestamp=0, end_timestamp=5)
14
+ ```
15
+
16
+ Intervals are half-open — `start` is included, `end` is excluded.
17
+
18
+ ## Relationship to `chronoloom`
19
+
20
+ `chronoloom` is a standalone Rust crate published to
21
+ [crates.io](https://crates.io/crates/chronoloom) and usable from any Rust
22
+ project. `chronoloompy` wraps it through a thin pyo3 binding
23
+ ([`rust/`](rust)) and adds a Python-native layer on top — pydantic models
24
+ today, more later.
25
+
26
+ Performance-critical algebra should be implemented once in `chronoloom` and
27
+ exposed here, rather than reimplemented in Python.
28
+
29
+ ## Development
30
+
31
+ Requires [uv](https://docs.astral.sh/uv/) and a Rust toolchain (managed
32
+ automatically via the repo's `rust-toolchain.toml` if you have
33
+ [rustup](https://rustup.rs) installed).
34
+
35
+ ```sh
36
+ make install # uv sync -- builds the extension
37
+ make fmt # ruff format + cargo fmt
38
+ make lint # ruff, mypy, cargo fmt --check, clippy
39
+ make test # pytest + cargo test
40
+ make build # release wheel into dist/
41
+ ```
42
+
43
+ The extension rebuilds automatically when `rust/**/*.rs`, `rust/Cargo.toml`,
44
+ or the `../chronoloom` sources change — see `cache-keys` in `pyproject.toml`.
45
+
46
+ Run `make setup` from the repository root once to install the git hooks.
47
+
48
+ ## License
49
+
50
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,154 @@
1
+ [project]
2
+ name = "chronoloompy"
3
+ version = "0.1.1"
4
+ description = "Temporal data processing primitives, backed by the chronoloom Rust crate."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [{ name = "Amine El Ouassouli", email = "a.elouassouli@gmail.com" }]
10
+ keywords = ["time-series", "temporal", "intervals", "streams", "rust"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Intended Audience :: Developers",
14
+ "Programming Language :: Rust",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Programming Language :: Python :: Implementation :: CPython",
19
+ "Typing :: Typed",
20
+ ]
21
+ dependencies = ["pydantic>=2.13.4,<3", "numpy>=2.5.2,<3"]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/AElOuassouli/chronoloom"
25
+ Repository = "https://github.com/AElOuassouli/chronoloom"
26
+ Changelog = "https://github.com/AElOuassouli/chronoloom/blob/main/chronoloompy/CHANGELOG.md"
27
+
28
+ [build-system]
29
+ # >=1.9.4 sets PYO3_BUILD_EXTENSION_MODULE itself; see [tool.maturin] below.
30
+ requires = ["maturin>=1.14.1,<2.0"]
31
+ build-backend = "maturin"
32
+
33
+ [tool.maturin]
34
+ # Points at the pyo3 binding crate specifically, not a workspace root --
35
+ # maturin needs a concrete [lib] target with crate-type = ["cdylib", ...].
36
+ manifest-path = "rust/Cargo.toml"
37
+ python-source = "src"
38
+ # The last segment must match [lib] name in rust/Cargo.toml and the #[pymodule]
39
+ # fn name, or the built .so cannot be imported under this name.
40
+ module-name = "chronoloompy._core"
41
+ profile = "release"
42
+ # Editable installs (what `uv sync` performs) use the dev profile so the inner
43
+ # loop is not a full optimized Rust build. Requires maturin >= 1.10.
44
+ editable-profile = "dev"
45
+ # NOTE: `features = ["pyo3/extension-module"]` intentionally absent. Since pyo3
46
+ # 0.28 that feature is superseded by PYO3_BUILD_EXTENSION_MODULE, which maturin
47
+ # sets automatically. Re-adding it disables libpython linking for *all* targets
48
+ # and breaks `cargo test` / `cargo clippy --all-targets`.
49
+
50
+ [tool.uv]
51
+ required-version = ">=0.9"
52
+ # uv's default rebuild heuristic only watches pyproject.toml/setup.py/setup.cfg,
53
+ # so a .rs edit would otherwise never trigger a rebuild. Setting cache-keys
54
+ # REPLACES the defaults, hence pyproject.toml is listed again here.
55
+ # Do not use `{ dir = "rust" }` -- it would walk rust/target/ and rebuild always.
56
+ cache-keys = [
57
+ { file = "pyproject.toml" },
58
+ { file = "rust/Cargo.toml" },
59
+ { file = "rust/Cargo.lock" },
60
+ # Scoped to src/, not `rust/**/*.rs` -- that would also match generated
61
+ # build-script output under rust/target/ (e.g. target-lexicon's host.rs),
62
+ # a moving target that self-invalidates the cache on every build.
63
+ { file = "rust/src/**/*.rs" },
64
+ # No entries for ../chronoloom: the binding depends on the released crate
65
+ # from crates.io, not the sibling directory, so a local core edit cannot
66
+ # change this package's compiled extension. Bumping the requirement in
67
+ # rust/Cargo.toml is what invalidates the cache, and that file is listed
68
+ # above.
69
+ ]
70
+
71
+ [dependency-groups]
72
+ dev = [
73
+ "maturin>=1.14.1,<2",
74
+ "mypy>=2.3.1",
75
+ "pre-commit>=4.6.2",
76
+ "pytest>=9.1.1",
77
+ "pytest-cov>=7.0",
78
+ "ruff>=0.16.3",
79
+ ]
80
+
81
+ [tool.ruff]
82
+ target-version = "py312"
83
+ line-length = 88
84
+ src = ["src", "tests"]
85
+ extend-exclude = ["rust/target"]
86
+
87
+ [tool.ruff.lint]
88
+ select = [
89
+ "F", # pyflakes
90
+ "E", # pycodestyle errors
91
+ "W", # pycodestyle warnings
92
+ "I", # isort
93
+ "N", # pep8-naming
94
+ "D", # pydocstyle
95
+ "UP", # pyupgrade
96
+ "ANN", # flake8-annotations
97
+ "S", # flake8-bandit
98
+ "B", # flake8-bugbear
99
+ "A", # flake8-builtins
100
+ "C4", # flake8-comprehensions
101
+ "PT", # flake8-pytest-style
102
+ "RET", # flake8-return
103
+ "SIM", # flake8-simplify
104
+ "TID", # flake8-tidy-imports
105
+ "TC", # flake8-type-checking
106
+ "ARG", # flake8-unused-arguments
107
+ "PTH", # flake8-use-pathlib
108
+ "PL", # pylint
109
+ "NPY", # numpy-specific rules
110
+ "PERF", # perflint
111
+ "FURB", # refurb
112
+ "RUF", # ruff-specific rules
113
+ ]
114
+ ignore = [
115
+ "D203", # conflicts with D211 (blank line before class)
116
+ "D213", # conflicts with D212 (multi-line summary position)
117
+ "ANN401", # explicit Any is meaningful for event payloads
118
+ ]
119
+
120
+ [tool.ruff.lint.per-file-ignores]
121
+ "__init__.py" = ["D104"]
122
+ "tests/**" = ["D", "S101", "ANN", "PLR2004", "ARG"]
123
+
124
+ [tool.ruff.lint.pydocstyle]
125
+ convention = "google"
126
+
127
+ [tool.ruff.format]
128
+ docstring-code-format = true
129
+
130
+ [tool.mypy]
131
+ python_version = "3.12"
132
+ strict = true
133
+ files = ["src", "tests"]
134
+ mypy_path = ["src"]
135
+ plugins = ["pydantic.mypy"]
136
+ warn_unreachable = true
137
+ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
138
+
139
+ [[tool.mypy.overrides]]
140
+ module = ["tests.*"]
141
+ disallow_untyped_defs = false
142
+
143
+ [tool.pytest.ini_options]
144
+ minversion = "9.0"
145
+ testpaths = ["tests"]
146
+ addopts = "-ra --strict-markers --strict-config"
147
+ filterwarnings = ["error"]
148
+
149
+ [tool.coverage.run]
150
+ source = ["chronoloompy"]
151
+ branch = true
152
+
153
+ [tool.coverage.report]
154
+ exclude_also = ["if TYPE_CHECKING:", "raise NotImplementedError"]
@@ -0,0 +1,139 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "chronoloom"
7
+ version = "0.1.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "d6cbfa80d8354d98392aecc2dc769522f2eac756913c36f632e2cf5e1e8a645d"
10
+
11
+ [[package]]
12
+ name = "chronoloompy-ext"
13
+ version = "0.1.0"
14
+ dependencies = [
15
+ "chronoloom",
16
+ "pyo3",
17
+ ]
18
+
19
+ [[package]]
20
+ name = "heck"
21
+ version = "0.5.0"
22
+ source = "registry+https://github.com/rust-lang/crates.io-index"
23
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
24
+
25
+ [[package]]
26
+ name = "libc"
27
+ version = "0.2.189"
28
+ source = "registry+https://github.com/rust-lang/crates.io-index"
29
+ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
30
+
31
+ [[package]]
32
+ name = "once_cell"
33
+ version = "1.21.4"
34
+ source = "registry+https://github.com/rust-lang/crates.io-index"
35
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
36
+
37
+ [[package]]
38
+ name = "portable-atomic"
39
+ version = "1.15.0"
40
+ source = "registry+https://github.com/rust-lang/crates.io-index"
41
+ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
42
+
43
+ [[package]]
44
+ name = "proc-macro2"
45
+ version = "1.0.107"
46
+ source = "registry+https://github.com/rust-lang/crates.io-index"
47
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
48
+ dependencies = [
49
+ "unicode-ident",
50
+ ]
51
+
52
+ [[package]]
53
+ name = "pyo3"
54
+ version = "0.29.2"
55
+ source = "registry+https://github.com/rust-lang/crates.io-index"
56
+ checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
57
+ dependencies = [
58
+ "libc",
59
+ "once_cell",
60
+ "portable-atomic",
61
+ "pyo3-build-config",
62
+ "pyo3-ffi",
63
+ "pyo3-macros",
64
+ ]
65
+
66
+ [[package]]
67
+ name = "pyo3-build-config"
68
+ version = "0.29.2"
69
+ source = "registry+https://github.com/rust-lang/crates.io-index"
70
+ checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
71
+ dependencies = [
72
+ "target-lexicon",
73
+ ]
74
+
75
+ [[package]]
76
+ name = "pyo3-ffi"
77
+ version = "0.29.2"
78
+ source = "registry+https://github.com/rust-lang/crates.io-index"
79
+ checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
80
+ dependencies = [
81
+ "libc",
82
+ "pyo3-build-config",
83
+ ]
84
+
85
+ [[package]]
86
+ name = "pyo3-macros"
87
+ version = "0.29.2"
88
+ source = "registry+https://github.com/rust-lang/crates.io-index"
89
+ checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
90
+ dependencies = [
91
+ "proc-macro2",
92
+ "pyo3-macros-backend",
93
+ "quote",
94
+ "syn",
95
+ ]
96
+
97
+ [[package]]
98
+ name = "pyo3-macros-backend"
99
+ version = "0.29.2"
100
+ source = "registry+https://github.com/rust-lang/crates.io-index"
101
+ checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
102
+ dependencies = [
103
+ "heck",
104
+ "proc-macro2",
105
+ "quote",
106
+ "syn",
107
+ ]
108
+
109
+ [[package]]
110
+ name = "quote"
111
+ version = "1.0.47"
112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
113
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
114
+ dependencies = [
115
+ "proc-macro2",
116
+ ]
117
+
118
+ [[package]]
119
+ name = "syn"
120
+ version = "2.0.119"
121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
122
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
123
+ dependencies = [
124
+ "proc-macro2",
125
+ "quote",
126
+ "unicode-ident",
127
+ ]
128
+
129
+ [[package]]
130
+ name = "target-lexicon"
131
+ version = "0.13.5"
132
+ source = "registry+https://github.com/rust-lang/crates.io-index"
133
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
134
+
135
+ [[package]]
136
+ name = "unicode-ident"
137
+ version = "1.0.24"
138
+ source = "registry+https://github.com/rust-lang/crates.io-index"
139
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
@@ -0,0 +1,39 @@
1
+ [package]
2
+ name = "chronoloompy-ext"
3
+ # NOT the release version. maturin builds the wheel from [project].version in
4
+ # ../pyproject.toml, which is also what the cd-chronoloompy tag guard checks.
5
+ # This one is inert -- the crate is never published anywhere.
6
+ version = "0.1.0"
7
+ edition = "2021"
8
+ rust-version = "1.83"
9
+ # Never published to crates.io: this crate's only export is a #[pymodule], so
10
+ # it is useless to Rust consumers. It ships as a compiled .so inside PyPI
11
+ # wheels, built by maturin. Rust users depend on `chronoloom` directly.
12
+ publish = false
13
+ # Inert given `publish = false`, but `maturin sdist` runs `cargo package --list`,
14
+ # which warns when this metadata is absent. Present only to keep that build
15
+ # output clean. The wheel's real metadata comes from ../pyproject.toml.
16
+ description = "PyO3 bindings for chronoloom. Implementation detail of the chronoloompy Python package."
17
+ license = "MIT"
18
+ repository = "https://github.com/AElOuassouli/chronoloom"
19
+
20
+ [lib]
21
+ # Must exactly match the last segment of `module-name` in ../pyproject.toml and
22
+ # the #[pymodule] fn name in src/lib.rs, or maturin emits a .so that Python
23
+ # cannot import under the expected name.
24
+ name = "_core"
25
+ # rlib is required for `cargo test` / `cargo clippy --all-targets` to link;
26
+ # cdylib is what maturin packages into the Python wheel.
27
+ crate-type = ["cdylib", "rlib"]
28
+
29
+ [dependencies]
30
+ # The released crate, NOT the sibling ../../chronoloom directory. Every wheel
31
+ # therefore contains core code matching a real published version, and the sdist
32
+ # no longer has to vendor a crate from outside this package directory (maturin's
33
+ # most bug-prone path -- PyO3/maturin#579, #838).
34
+ #
35
+ # The trade-off: an edit to ../../chronoloom no longer reaches this binding. To
36
+ # expose new core algebra, release the core to crates.io first, then bump this
37
+ # requirement.
38
+ chronoloom = "0.1"
39
+ pyo3 = { version = "0.29.2", features = ["abi3-py312"] }
@@ -0,0 +1,11 @@
1
+ //! Adapters exposing `chronoloom::algebra` to Python.
2
+
3
+ use pyo3::prelude::*;
4
+
5
+ /// Intersect two half-open intervals `[start, end)`.
6
+ ///
7
+ /// Returns `None` when the intervals do not overlap.
8
+ #[pyfunction]
9
+ pub fn intersection(a: (i64, i64), b: (i64, i64)) -> Option<(i64, i64)> {
10
+ chronoloom::algebra::intersection(a, b)
11
+ }
@@ -0,0 +1,19 @@
1
+ //! PyO3 bindings for the [`chronoloom`] crate.
2
+ //!
3
+ //! This crate is an implementation detail of the `chronoloompy` Python
4
+ //! package rather than a library in its own right: its only export is a
5
+ //! `#[pymodule]`. Rust consumers should depend on [`chronoloom`] directly.
6
+ //!
7
+ //! Everything here should stay a thin adapter — real logic belongs in
8
+ //! [`chronoloom`], so that Rust and Python share one implementation.
9
+
10
+ use pyo3::prelude::*;
11
+
12
+ mod algebra;
13
+
14
+ /// Rust extension module, imported from Python as `chronoloompy._core`.
15
+ #[pymodule]
16
+ fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
17
+ m.add_function(wrap_pyfunction!(algebra::intersection, m)?)?;
18
+ Ok(())
19
+ }
File without changes
@@ -0,0 +1,7 @@
1
+ """Type stubs for the Rust extension module `chronoloompy._core`.
2
+
3
+ Implemented in ../../rust, which is a thin pyo3 adapter over the `chronoloom`
4
+ crate. Keep these signatures in sync with the `#[pyfunction]`s there.
5
+ """
6
+
7
+ def intersection(a: tuple[int, int], b: tuple[int, int]) -> tuple[int, int] | None: ...
@@ -0,0 +1,5 @@
1
+ """Algebraic operations over temporal data."""
2
+
3
+ from chronoloompy.algebra.intervals import interval_union
4
+
5
+ __all__ = ["interval_union"]
@@ -0,0 +1,5 @@
1
+ """Algebraic operations on time intervals."""
2
+
3
+ from chronoloompy.algebra.intervals.union import interval_union
4
+
5
+ __all__ = ["interval_union"]
@@ -0,0 +1,19 @@
1
+ """Set-algebra operations on time intervals."""
2
+
3
+ from chronoloompy.models import TimeIntervalEvent
4
+
5
+
6
+ # TODO: implement this function
7
+ def interval_union(
8
+ interval_a: TimeIntervalEvent, interval_b: TimeIntervalEvent
9
+ ) -> list[TimeIntervalEvent]:
10
+ """Return the union of two time intervals."""
11
+ return [interval_a, interval_b]
12
+
13
+
14
+ # TODO: implement this function
15
+ def interval_intersection(
16
+ interval_a: TimeIntervalEvent, interval_b: TimeIntervalEvent
17
+ ) -> list[TimeIntervalEvent]:
18
+ """Return the intersection of two time intervals."""
19
+ return [interval_a, interval_b]
@@ -0,0 +1,5 @@
1
+ """Data models for temporal events and sequences."""
2
+
3
+ from chronoloompy.models.primitives import TimeIntervalEvent, TimePointEvent
4
+
5
+ __all__ = ["TimeIntervalEvent", "TimePointEvent"]
@@ -0,0 +1,6 @@
1
+ """Primitive event models: single points and intervals in time."""
2
+
3
+ from chronoloompy.models.primitives.interval import TimeIntervalEvent
4
+ from chronoloompy.models.primitives.time_point import TimePointEvent
5
+
6
+ __all__ = ["TimeIntervalEvent", "TimePointEvent"]
@@ -0,0 +1,17 @@
1
+ """The interval event primitive: a value attached to a span of time."""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class TimeIntervalEvent(BaseModel):
9
+ """A half-open time interval, optionally carrying an attribute."""
10
+
11
+ start_timestamp: int
12
+ end_timestamp: int
13
+ attribute: Any | None = None
14
+ left_open: bool = False
15
+ right_open: bool = True
16
+
17
+ ## TODO: add validator to ensure that start_timestamp < end_timestamp
@@ -0,0 +1,13 @@
1
+ """The point event primitive: a value observed at a single instant."""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, PositiveInt
6
+
7
+
8
+ class TimePointEvent(BaseModel):
9
+ """A single timestamped observation, optionally carrying an attribute."""
10
+
11
+ timestamp: PositiveInt
12
+ value: Any
13
+ attribute: str | None = None
@@ -0,0 +1,34 @@
1
+ """Sequence models backed by NumPy structured arrays."""
2
+
3
+ from collections.abc import Sequence
4
+
5
+ import numpy as np
6
+ from pydantic import BaseModel, ConfigDict
7
+
8
+ from chronoloompy.models.primitives import TimePointEvent
9
+
10
+ SEQUENCE_DTYPE = np.dtype([("timestamp", np.int64), ("value", np.float64)])
11
+
12
+
13
+ class TimePointSequence(BaseModel):
14
+ """A time-ordered sequence of point events, stored as a NumPy array."""
15
+
16
+ model_config = ConfigDict(arbitrary_types_allowed=True, frozen=True)
17
+
18
+ sequence: np.ndarray
19
+ attribute: str | None = None
20
+
21
+ @classmethod
22
+ def from_events(
23
+ cls,
24
+ events: Sequence[TimePointEvent],
25
+ attribute: str | None = None,
26
+ ) -> "TimePointSequence":
27
+ """Build a sequence from a list of point events."""
28
+ return cls(
29
+ sequence=np.array(
30
+ [(event.timestamp, event.value) for event in events],
31
+ dtype=SEQUENCE_DTYPE,
32
+ ),
33
+ attribute=attribute,
34
+ )
File without changes