Integer ids for components & supplemental attributes#589
Open
daniel-thom wants to merge 23 commits into
Open
Conversation
…store Wire InfrastructureSystems to delegate SingleTimeSeries data + metadata to the external Rust time-series-store engine behind `backend=:rust`. Forecast types remain on the legacy HDF5 path. - New src/rust_time_series_store.jl: RustTimeSeriesStore <: TimeSeriesStorage, a self-contained ccall wrapper (no dependency on the registered TimeSeries package). Data identity is the array content hash, not a UUID. - Route SingleTimeSeries through the real public API, guarded by `data_store isa RustTimeSeriesStore` (legacy path untouched): add_time_series!, get_time_series (full + start_time/len slice), has_time_series, remove_time_series!, get_time_series_counts, clear. - SystemData/TimeSeriesManager gain a `backend` selector. - serialize/deserialize(SystemData) write/read a .nc + standalone .sqlite pair (time_series_storage_type="RustTimeSeriesStore"); no HDF5, no embedded blob. - Tests: in-memory public-API round-trip (15) + System save/reload (8) + direct-store round-trip (17) + on-disk persistence (4). KNOWN: the on-disk path needs HDF5.jl to share the Rust dylib's libhdf5 (one libhdf5 per process); configure via LocalPreferences.toml (not committed). The hdf5_conflict_probe.jl test documents the conflict empirically. This goes away once all time series types move to Rust and IS drops HDF5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…store - Add forecast ccall wrappers (add_forecast!, get_forecast_metadata, has_typed, remove_typed) and window flatten/reshape logic in rust_time_series_store.jl. - Route add/get/has/remove for AbstractDeterministic through the manager/public API; reads reconstruct the STORED type (Deterministic windows, or a DST wrapping the reconstructed underlying SingleTimeSeries). - Fix get_num_time_series/isempty to count forecasts too (otherwise a forecast-only System serialized as empty). - Tests: Deterministic + DST in-memory round-trip and System save/reload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add Probabilistic ccall wrappers (add_probabilistic!, get_probabilistic_metadata returning percentiles) and 3-D window flatten/reshape in rust_time_series_store.jl. - Widen forecast routing predicates from AbstractDeterministic to Forecast so Probabilistic is handled by add/get/has/remove; reconstruct the stored type. - Test: Probabilistic in-memory round-trip + System save/reload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes Rust-backed support for all five time series types. Scenarios reuses the generic forecast FFI (ts_store_add_forecast / ts_store_get_forecast_metadata, TimeSeriesType=Scenarios) with no ts-store change: scenario_count is derived on read as length / (horizon_count * count). Stores the flattened 3-D (scenario_count, horizon_count, count) array by content hash. - Add Scenarios branches to add/get/has/remove routing (predicates already widened to Forecast); reconstruct the stored type. - Test: Scenarios in-memory round-trip + System save/reload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All five time series types now run on the Rust backend, so the legacy HDF5 storage is dead code. Removing it eliminates the in-process libhdf5 conflict permanently: IS.jl no longer loads HDF5, so the Rust on-disk NetCDF path works with no LocalPreferences/libhdf5-sharing workaround. - Delete src/hdf5_time_series_storage.jl (the Hdf5TimeSeriesStorage struct and all its methods) and remove HDF5 + H5Zblosc from Project.toml. - Remove the HDF5 SQLite-blob hack (to_h5_file / from_h5_file) and the InMemory→HDF5 conversion (convert_to_hdf5, the from-Hdf5 constructor). - make_time_series_storage now returns InMemoryTimeSeriesStorage (the only pure-Julia backend); on-disk persistence is provided exclusively by the Rust backend (`time_series_backend = :rust`). Add a generic no-op open_store!. - System serialize/deserialize: drop the HDF5 paths; non-empty non-Rust stores and legacy .h5 systems now raise a clear error directing to the Rust backend. - Remove the obsolete hdf5_conflict_probe test and the stale libhdf5 notes. In-memory and all Rust backend tests pass; HDF5 is no longer loaded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Remove HDF5 from the test harness (`import HDF5`) and test/Project.toml; Aqua stale-deps/compat checks now pass with HDF5 gone. - Rewrite test_time_series_storage.jl to cover the in-memory backend only; drop the HDF5-specific data-format-version and on-disk compression testsets (NetCDF + SQLite persistence is covered by the Rust integration tests). - Remove dead HDF5 tests from test_time_series.jl (copy_h5_file, deepcopy-on-HDF5) and fix the storage-type assertion (InMemoryTimeSeriesStorage). - Move the standalone Rust integration tests to test/rust/ so the ReTest harness (which auto-includes test_*.jl and has no cdylib) doesn't pull them in; run them directly, e.g. `julia --project=. test/rust/rust_time_series_store.jl`. Harness loads cleanly; storage + in-memory tests pass. Disk-serialization round-trip tests still require the Rust backend (cdylib) and remain to migrate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`rust_time_series_store.jl` now delegates all low-level calls to the `TimeSeriesStore` binding package (the renamed standalone Julia binding); it keeps only the IS-specific glue: owner/feature conversion, the SingleTimeSeries and forecast window flatten/reshape, and the TimeSeriesManager routing. `RustTimeSeriesStore` wraps a `TimeSeriesStore.Store`. `RustTimeSeriesNotFound` is now an alias for `TimeSeriesStore.NotFoundError`. Adds TimeSeriesStore to [deps]/[compat] and `import TimeSeriesStore` to the module. Until the package is registered it must be `Pkg.develop`ed locally (Manifest is gitignored). All five Rust integration tests pass unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
System time series serialization now goes through the Rust backend (.nc arrays + standalone .sqlite metadata next to the JSON; no HDF5). Brings the existing serialization tests onto it: - validate_serialization moves the recorded storage file(s) — the Rust backend's .nc plus its sibling .sqlite — rather than the old single .h5. - The three time-series serialization testsets build :rust-backed systems, gated on the cdylib/JLL being available (rust_ts_available()); they @test_skip when it is absent (e.g. CI without the binary). - compare_values(::RustTimeSeriesStore, ::RustTimeSeriesStore) compares by counts (handle/path differ across a round-trip; element equality is covered by the Rust integration tests). With the cdylib present: system-data (2), read-only (3), mutable (2) all pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
serialize_single! keeps the value element type (Float64, Int64, …) instead of
forcing Vector{Float64}, and passes string(eltype) as the logical-type tag.
get_single reconstructs with the stored dtype (get_metadata now returns it;
get_array_by_hash takes the element type). An Int64 SingleTimeSeries now
round-trips through the Rust backend with its eltype intact; the Float64 path is
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`SingleTimeSeries{T}` carries the value element type T (inferred from the data,
so callers never spell it out); the `data` field stays an untyped TimeArray.
This enables the parametric API:
ts = SingleTimeSeries(name="active_power", data=[1.2, 3.4, 5.6]) # {Float64}
add_time_series!(sys, component, ts)
ts2 = get_time_series(SingleTimeSeries{Float64}, component, "active_power")
- get_time_series(::Type{T}) where T<:TimeSeriesData already dispatches on the
concrete `SingleTimeSeries{Float64}`; the Rust get reconstructs with the
requested element type (falling back to the stored dtype).
- Relaxed `time_series_data_to_metadata` and `check_consistency` to
`::Type{<:SingleTimeSeries}` so the concrete parametric type dispatches.
- Added `get_data_type(::SingleTimeSeries{T}) = string(T)` (fixes the previously
undefined-symbol FunctionData cost tests).
SingleTimeSeries structs aren't serialized to JSON (only metadata + data are), so
serialization is unaffected. Validated: the parametric example end-to-end on the
Rust backend; Int64 + Float64 + Linear/Quadratic/PiecewiseLinear FunctionData
element types; serialization, transforms, consistency, and rust integration tests
all pass. (Pre-existing unrelated failure: bulk-add's in_memory=false expectation,
HDF5-removal fallout.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_rust_add_forecast! passes each forecast's native array — Deterministic (horizon_count, count), Probabilistic/Scenarios 3-D — instead of flattening to 1-D. _rust_get_forecast reconstructs via TimeSeriesStore.get_array_nd with dims derived from the forecast parameters (horizon÷resolution, count, percentiles), keeping the per-window extraction unchanged. Removed the now-unused _flatten_deterministic helper. All four forecast round-trip + system integration tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`time_series_in_memory=false` no longer selects an on-disk backend (HDF5 is gone; on-disk persistence is `time_series_backend=:rust`), so the non-Rust store is always in-memory. Assert `stores_time_series_in_memory(sys)` is true instead of `== in_memory`. Both bulk-add testsets pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixed-size FunctionData element types now round-trip through the Rust store:
serialize_single! encodes a Vector{LinearFunctionData}/Vector{QuadraticFunctionData}
as a (length, 2)/(length, 3) Float64 array tagged with its logical type; reads
key on the stored logical_type (from get_metadata) to rebuild the FunctionData,
so the non-parametric get_time_series(SingleTimeSeries, ...) returns the right
type. Scalar dtypes (Float64, Int64, …) are unchanged. PiecewiseLinearData
(ragged) raises a clear "not supported yet" error.
Added a rust test covering Int64, Quadratic (non-parametric get), and Linear
(parametric get); FunctionData also verified to persist to .nc/.sqlite and reload
with its element type. Removed the now-unused _rust_sts_eltype helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each step has a variable number of (x, y) points, so it's stored as a padded (len, 1 + 2*max_points) Float64 matrix: column 1 of each row is the point count, keeping shape[0] = the timestep count. Reads derive the per-step width from the array size and rebuild each PiecewiseLinearData from its row. Completes FunctionData support on the Rust backend (Linear/Quadratic/PiecewiseLinear). Rust test extended with a ragged 2/3/2-point series. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gleTimeSeries
Fixes surfaced by a full suite run with the cdylib present:
Parametric SingleTimeSeries{T} fallout (it's now a UnionAll, not a DataType):
- TimeSeriesFileMetadata.time_series_type and the parser assignment Set use Type
(not DataType); _get_all_concrete_subtypes records parametric leaf types via
their UnionAll body; add_serialization_metadata! skips .parameters for UnionAll;
added a SingleTimeSeries{T}(metadata, data) forwarding constructor for the
deserialize path.
Name-less has_time_series routing: has_time_series(owner) and
has_time_series(owner, T) now route to the Rust store (via has_for_owner /
_rust_has_any) instead of the dummy metadata store; previously returned false.
HDF5-removal fallout: bulk-add + compression assertions updated (in-memory is the
only non-Rust backend); supplemental-attribute + deserialized-system serialization
tests gated on the Rust backend (the latter skipped pending
scaling_factor_multiplier support); the custom-directory test uses the Rust
backend's .nc path; removed the obsolete transform/retransform test file (the
HDF retransform helper was removed).
Pre-existing in-memory bug: forecast row-slicing linearized matrix-valued windows
(Probabilistic/Scenarios); fixed with selectdim.
Suite: 8027 passed, 1 skipped, 0 failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Rust backend already honors a custom directory for its .nc/.sqlite files (via the `time_series_directory` kwarg, the SIENNA_TIME_SERIES_DIRECTORY env var, or tempdir()). On HPC a per-job scratch directory may not exist yet, so mkpath it before creating the store instead of failing with "unable to open database file". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The TimeSeriesStore.jl binding consolidated its forecast API and moved the association `name` / `scaling_factor_multiplier` onto the time series structs. Update the Rust-backed store glue accordingly: - SingleTimeSeries: pass `name` (and scaling, logical_type) on the struct constructor; drop them from add_time_series!. - Forecast writes: build TSS.Deterministic / TSS.Probabilistic / TSS.Scenarios and route through the generic add_time_series! instead of the removed add_forecast! / add_probabilistic! transports. - DeterministicSingleTimeSeries: the binding derives it from a stored SingleTimeSeries via transform_single_time_series! rather than a direct forecast write, so persist the underlying series (if absent) and transform. - Forecast reads: use get_time_series(T, store, owner, name; ...) (returns the decoded array + params) in place of the removed get_*_metadata + get_array_nd. All standalone Rust integration tests pass (test/rust/*.jl, 129 assertions). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plumb CompressionSettings from SystemData/TimeSeriesManager into RustTimeSeriesStore so the time-series-store NetCDF backend honors the requested compression. DEFLATE maps to the backend's level/shuffle; disabled compression maps to no filter; BLOSC raises an explicit error (unsupported by the Rust backend). open_rust_store now queries the persisted policy back over the FFI (get_compression) so get_compression_settings reports the real settings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the SQLite TimeSeriesMetadataStore and InMemoryTimeSeriesStorage and make RustTimeSeriesStore the sole time-series backend (clean break: no time_series_backend kwarg, no metadata_store field). - src/rust_time_series_store.jl: full parity glue derived from TSS.list_metadata (metadata reconstruction incl. scaling_factor_multiplier, keys, multiple, resolutions, counts-by-type, summary tables, forecast parameters, owner-uuid listing, consistency check, replace_component_uuid!, per-owner clear). time_series_uuid is derived from the content hash. - time_series_manager.jl: struct is (data_store, read_only); Rust-only add/clear/remove/list; SingleTimeSeries-attached-to-DST removal guard. - system_data.jl: Rust-only serialize/deserialize; getters routed to glue; transform_single_time_series! uses the store-level transform. - time_series_interface.jl / component.jl / time_series_storage.jl / deterministic_single_time_series.jl: drop legacy paths and dead code. - tests: default to Rust; delete legacy-only tests (storage, SQLite migrations, optimize_database!, to_dataframe) and redundant test/rust POCs. Status: full suite ~green; one serialization round-trip test failing (double round-trip of a deserialized system). See HANDOFF_drop_legacy_backend.md for the exact remaining work and run steps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the Rust-only backend to feature parity with the removed legacy backend and get the full test suite passing (7952 pass, 0 fail/error, 3 documented @test_broken for store-model gaps). IS glue (rust_time_series_store.jl): - Partial (subset) feature/resolution matching for get/has/remove. - Forecast start_time/count/len window slicing + validation; Deterministic query also matches DeterministicSingleTimeSeries. - scaling_factor_multiplier threaded through add/read; content-hash time_series_uuid assigned on add. - FunctionData-valued Deterministic forecasts (encode/decode via logical_type). - Distinct-array counts, forecast-parameter compatibility checks, hash-based STS-attached-to-DST removal guard, begin_time_series_update rollback, component-only / resolution-filtered transforms. Tests: re-enabled the deserialized-system round-trip; marked two store-model gaps @test_broken with TODOs — irregular (Month/Year) resolutions and multiple-interval forecasts (the store key omits interval). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Design change: scaling_factor_multiplier is no longer supported. Remove the field from all time series types (SingleTimeSeries, Probabilistic, Scenarios, Deterministic) and their metadata descriptors (regenerated), along with the get_/set_ accessors. Also remove the now-meaningless read-time plumbing: the ignore_scaling_factors keyword across the public read API and cache, the scaling_factor_multiplier_mapping keyword in copy_time_series!, and the multiplier application in _make_time_array. Drop the parser fields and stop passing/reading the attribute at the Rust store boundary. Tests and docs updated; full suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the integer-id identity model so components and supplemental attributes are
identified by small integer ids assigned by SystemData when attached, instead of
UUIDs, while keeping the Rust time-series backend. Mirrors the IS2.jl model.
- InfrastructureSystemsInternal gains id::Int (UNASSIGNED_ID until attached);
identity goes through get_id/set_id!. Time series keep their UUIDs.
- SystemData tracks two independent id streams (next_component_id,
next_supplemental_attribute_id, each from 1); component_uuids -> component_ids,
subsystems -> Set{Int}; ComponentUUIDs -> ComponentIDs. ids are preserved
across serialization (assign_id! advances the counter past restored ids).
- Supplemental attribute associations use integer component_id/attribute_id.
- Rust time-series glue threads (owner_id, owner_category) to the category-aware
TimeSeriesStore.jl binding; assign_new_id! re-keys time series via replace_owner!.
- Tests ported; forecast tests fixed to use strictly-increasing percentiles
(the store validates this).
Full InfrastructureSystems.jl test suite passes against the Rust backend.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the integer-id model to parity with PR #587 (the pure-Julia integer-id PR), porting the review-driven fixes that were missing here: - assign_new_id_internal!: also remap the component's id in subsystem membership sets (previously left stale, breaking subsystem lookups after reassignment). - Guard against UNASSIGNED_ID on the manager-direct path when attaching a supplemental attribute and when adding an association, with actionable errors instead of colliding at id 0. - Fix "subystem" typos in system_data.jl and subsystems.jl. - Tests: assert subsystem membership tracks the new id after assign_new_id!; assign ids in the manager-direct supplemental attribute tests. (PR #587's f896870 put owner_category in the Julia metadata-store unique index; here that lives in the Rust store instead — NatLabRockies/infrastore#4.) Full test suite passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Identify components and supplemental attributes by small integer ids (assigned by
SystemDatawhen attached) instead of UUIDs, while keeping the Rust time-series backend. Mirrors the IS2.jl identity model.Integer-id changes
InfrastructureSystemsInternalgainsid::Int(UNASSIGNED_IDuntil attached); identity goes throughget_id/set_id!. Time series keep their UUIDs.SystemDatatracks two independent id streams (next_component_id,next_supplemental_attribute_id, each from 1);component_uuids→component_ids,subsystems→Set{Int};ComponentUUIDs→ComponentIDs. Ids are preserved across serialization (assign_id!advances the counter past restored ids).component_id/attribute_id.(owner_id, owner_category)to the category-awareTimeSeriesStore.jlbinding;assign_new_id!re-keys time series viareplace_owner!.Depends on the category-aware
TimeSeriesStore.jl/time-series-storechanges (NatLabRockies/infrastore#4).Verification
Full
InfrastructureSystems.jltest suite passes against the Rust backend (Pkg.test()→tests passed, exit 0).🤖 Generated with Claude Code