Skip to content

Add non-contiguous timestamp support (multiple time slices)#110

Open
sriharisundar wants to merge 7 commits into
mainfrom
ssh/noncontiguousTS
Open

Add non-contiguous timestamp support (multiple time slices)#110
sriharisundar wants to merge 7 commits into
mainfrom
ssh/noncontiguousTS

Conversation

@sriharisundar

Copy link
Copy Markdown
Member

Summary

Allows a SystemModel time axis to consist of multiple contiguous StepRange "slices" with gaps between them (e.g. a representative summer week + winter week), instead of a single contiguous range.

Design

  • New SlicedTimestamps <: AbstractVector{ZonedDateTime} stores a Vector{StepRange} of slices but presents a flat length-N view (lazy indexing), so the SequentialMonteCarlo engine and all result indexing are unchanged. Per-sample state (storage SoC, Markov availability) carries across slice boundaries as if adjacent.
  • SystemModel.timestamps field widened from StepRange{ZonedDateTime,T} to AbstractVector{ZonedDateTime}. New constructors accept a Vector{StepRange} of slices.
  • Fully backward compatible: passing a single StepRange works exactly as before (still stored as a compact StepRange, no materialization).
  • Result struct timestamps fields widened accordingly.

File format

  • PRASFiles persists/reconstructs slices via new HDF5 metadata n_slices, slice_start_timestamps, slice_lengths — written only for multi-slice systems, so existing single-slice .pras files are byte-identical and pre-0.9 files still read.

Versioning

  • Bumped PRASCore, PRASFiles, PRASCapacityCredits, and PRAS to 0.9.0 and widened inter-package compat pins (registrable in dependency order).

Tests

  • PRASCore 513/513 (incl. new multi-slice construction/printing testset)
  • PRASFiles 32/32 (incl. new file round-trip + assess-across-the-gap testset)
  • PRASCapacityCredits 4/4

🤖 Generated with Claude Code

Allow a SystemModel time axis to consist of multiple contiguous StepRange
"slices" with gaps between them (e.g. a summer week + a winter week),
instead of a single contiguous range.

- New SlicedTimestamps <: AbstractVector{ZonedDateTime} stores a vector of
  StepRange slices but presents a flat length-N view, so the simulation
  engine and result indexing are unchanged. Per-sample state carries across
  slice boundaries as if adjacent.
- SystemModel.timestamps field widened to AbstractVector{ZonedDateTime};
  new constructors accept a Vector{StepRange} of slices. Passing a single
  StepRange is fully backward compatible (still stored as a StepRange).
- Results timestamp fields widened accordingly.
- PRASFiles: persist/reconstruct slices via n_slices, slice_start_timestamps,
  and slice_lengths HDF5 metadata, written only for multi-slice systems so
  existing single-slice .pras files stay byte-identical. Reader remains
  backward compatible with pre-0.9 files.
- Bump PRASCore, PRASFiles, PRASCapacityCredits, and PRAS to 0.9.0 and
  widen inter-package compat pins.
- Add tests for multi-slice construction, file round-trip, and assess.
@sriharisundar sriharisundar requested a review from akrivi June 12, 2026 19:36
@codecov-commenter

codecov-commenter commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.81250% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.15%. Comparing base (11d3003) to head (fd83de3).

Files with missing lines Patch % Lines
PRASCore.jl/src/Systems/utils.jl 73.52% 9 Missing ⚠️
PRASCore.jl/src/Systems/SystemModel.jl 87.50% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #110      +/-   ##
==========================================
+ Coverage   83.13%   83.15%   +0.01%     
==========================================
  Files          45       46       +1     
  Lines        2325     2380      +55     
==========================================
+ Hits         1933     1979      +46     
- Misses        392      401       +9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for non-contiguous timestamp axes by representing a system’s time domain as multiple contiguous StepRange slices with gaps, while preserving existing “flat” indexing semantics across the concatenated timesteps. This spans core modeling (SystemModel + new SlicedTimestamps), result timestamp typing, and PRAS file read/write support for persisting slice metadata.

Changes:

  • Introduces SlicedTimestamps <: AbstractVector{ZonedDateTime} and adds SystemModel constructors that accept a Vector{StepRange} of slices.
  • Updates PRASFiles HDF5 metadata handling to optionally persist and reconstruct multi-slice timestamp axes.
  • Widens result timestamp fields to support non-contiguous axes and adds tests + version bumps to 0.9.0 across packages.

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
PRASFiles.jl/test/runtests.jl Adds a non-contiguous slice round-trip + assess-across-gap testset.
PRASFiles.jl/src/Systems/write.jl Writes slice metadata (n_slices, slice_*) for multi-slice systems.
PRASFiles.jl/src/Systems/read.jl Reads optional slice metadata and rebuilds SlicedTimestamps; broadens supported file-version range.
PRASFiles.jl/src/PRASFiles.jl Imports SlicedTimestamps from PRASCore for I/O logic.
PRASFiles.jl/Project.toml Bumps PRASFiles to 0.9.0 and widens PRASCore compat to 0.9.
PRASCore.jl/test/Systems/SystemModel.jl Adds SystemModel multi-slice construction/validation/printing tests.
PRASCore.jl/src/Systems/utils.jl Introduces SlicedTimestamps and timestep(...) helper(s).
PRASCore.jl/src/Systems/Systems.jl Exports/includes the new sliced-timestamp utilities.
PRASCore.jl/src/Systems/SystemModel.jl Widens timestamps storage type and adds slice-based constructors; updates display and timestep assertion.
PRASCore.jl/src/Results/UtilizationSamples.jl Widens result timestamps field type.
PRASCore.jl/src/Results/Utilization.jl Widens result timestamps field type.
PRASCore.jl/src/Results/SurplusSamples.jl Widens result timestamps field type.
PRASCore.jl/src/Results/Surplus.jl Widens result timestamps field type.
PRASCore.jl/src/Results/StorageEnergySamples.jl Widens result timestamps field type.
PRASCore.jl/src/Results/StorageEnergy.jl Widens result timestamps field type.
PRASCore.jl/src/Results/StorageAvailability.jl Widens result timestamps field type.
PRASCore.jl/src/Results/ShortfallSamples.jl Widens result timestamps field type.
PRASCore.jl/src/Results/Shortfall.jl Widens result timestamps field type and constructor signature.
PRASCore.jl/src/Results/LineAvailability.jl Widens result timestamps field type.
PRASCore.jl/src/Results/GeneratorStorageEnergySamples.jl Widens result timestamps field type.
PRASCore.jl/src/Results/GeneratorStorageEnergy.jl Widens result timestamps field type.
PRASCore.jl/src/Results/GeneratorStorageAvailability.jl Widens result timestamps field type.
PRASCore.jl/src/Results/GeneratorAvailability.jl Widens result timestamps field type.
PRASCore.jl/src/Results/FlowSamples.jl Widens result timestamps field type.
PRASCore.jl/src/Results/Flow.jl Widens result timestamps field type.
PRASCore.jl/src/Results/DemandResponseEnergySamples.jl Widens result timestamps field type.
PRASCore.jl/src/Results/DemandResponseEnergy.jl Widens result timestamps field type.
PRASCore.jl/src/Results/DemandResponseAvailability.jl Widens result timestamps field type.
PRASCore.jl/Project.toml Bumps PRASCore to 0.9.0.
PRASCapacityCredits.jl/Project.toml Bumps PRASCapacityCredits to 0.9.0 and widens PRASCore compat through 0.9.
PRAS.jl/Project.toml Bumps umbrella PRAS to 0.9.0 and pins component packages to 0.9.
Comments suppressed due to low confidence (1)

PRASFiles.jl/src/Systems/write.jl:92

  • User-defined sys.attrs are written after the reserved PRAS metadata attributes. With multi-slice support this now includes n_slices/slice_* keys; if a user supplies any attribute with the same key, it will overwrite the reserved metadata and can make the file unreadable (e.g., n_slices becomes a string, causing parse failures on load). Guard against collisions so reserved metadata cannot be overridden by user attributes.
    # Non-contiguous time axis: persist per-slice (start, length). Written only
    # for multi-slice systems so single-slice .pras files stay byte-identical and
    # remain readable by older PRAS versions.
    if sys.timestamps isa SlicedTimestamps
        slices = sys.timestamps.slices
        attrs["n_slices"] = length(slices)
        attrs["slice_start_timestamps"] = [string(first(s)) for s in slices]
        attrs["slice_lengths"] = [length(s) for s in slices]
    end

    # Existing system attributes
    sys_attributes = sys.attrs
    for (key, value) in sys_attributes
        attrs[key] = value
    end

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +82 to +83
timestep(ts::SlicedTimestamps) = step(first(ts.slices))
timestep(ts::AbstractRange{ZonedDateTime}) = step(ts)
Comment on lines +65 to 68
# Either a contiguous `StepRange` (single time slice) or a `SlicedTimestamps`
# (multiple non-contiguous slices). Both behave as a flat length-N vector.
timestamps::AbstractVector{ZonedDateTime}

Make SlicedTimestamps internal (qualified import in PRASFiles/tests
still works) so it no longer trips Documenter's missing-docs check.
Add a Base.show that displays the underlying StepRange slices, one
per line in the REPL, instead of dumping every timestamp.
Narrow the base SystemModel constructors' timestamps argument to
Union{StepRange{ZonedDateTime}, SlicedTimestamps} so a flat
Vector{ZonedDateTime} fails cleanly at dispatch with a MethodError
listing valid signatures, instead of an opaque timestep() error.

Document the non-contiguous slice-vector constructor and the
flat-vector restriction in the SystemModel docstring.
Restore the concrete field type lost when the axis was broadened for
non-contiguous support: use a 2-member Union of concrete types,
Union{StepRange{ZonedDateTime,T}, SlicedTimestamps{T}}, instead of
AbstractVector{ZonedDateTime}. The compiler union-splits it, so
sys.timestamps access stays type-stable without adding a struct type
parameter.
@hsunnrel hsunnrel requested a review from Copilot June 12, 2026 21:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

PRASFiles.jl/test/runtests.jl:1

  • This test writes toy_noncontig.pras into the repository test directory and does not clean it up, which can leave artifacts locally and can create collisions in parallel test runs/CI caching. Use mktempdir()/tempname() (and joinpath) and ensure the file is removed after the test (e.g., with a try/finally) so the test is hermetic.

Comment thread PRASCore.jl/src/Systems/SystemModel.jl Outdated
Comment thread PRASCore.jl/src/Systems/SystemModel.jl Outdated
Comment thread PRASFiles.jl/src/Systems/read.jl
Comment thread PRASCore.jl/src/Results/Shortfall.jl
Comment thread PRASCore.jl/src/Results/Shortfall.jl
SlicedTimestamps is no longer exported, so `using PRASCore` does not
bring it into scope. The non-contiguous roundtrip tests referenced it
unqualified and errored on CI. Add the qualified import, matching the
fix already applied to the PRASCore test suite.
The base constructors accepted Union{StepRange{ZonedDateTime},
SlicedTimestamps} untied to T, so a range whose step equals T(L) but
has a different period type (e.g. Minute(60) vs Hour(1)) passed the
timestep assertion and then failed opaquely when assigned into the
T-tied timestamps field. Tighten both signatures to
Union{StepRange{ZonedDateTime,T}, SlicedTimestamps{T}} so the mismatch
is a clean dispatch-time MethodError, consistent with the field type.

Add a test covering a Minute(60)-stepped range against an Hour-unit
system.
Describe the optional n_slices/slice_start_timestamps/slice_lengths
HDF5 attributes and their backward-compat rules in the .pras format
spec, add a user-facing slice-vector constructor example to the system
model specification, and add a 0.9.0 changelog entry.

Add a note in the system model spec and the PRAS walkthrough advising
that the time axis comes from sys.timestamps (used directly or via
collect), and must not be reconstructed from first + length + step,
which fills the gaps of a non-contiguous axis with nonexistent times.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 5 comments.

Comment on lines +81 to +86
if sys.timestamps isa SlicedTimestamps
slices = sys.timestamps.slices
attrs["n_slices"] = length(slices)
attrs["slice_start_timestamps"] = [string(first(s)) for s in slices]
attrs["slice_lengths"] = [length(s) for s in slices]
end
Comment on lines +59 to +67
if haskey(metadata, "n_slices") && Int(read(metadata["n_slices"])) > 1
# Non-contiguous time axis: rebuild each slice from its (start, length).
slice_starts = ZonedDateTime.(read(metadata["slice_start_timestamps"]),
dateformat"yyyy-mm-ddTHH:MM:SSz")
slice_lengths = Int.(read(metadata["slice_lengths"]))
slices = [StepRange(s, timestep, s + (len - 1) * timestep)
for (s, len) in zip(slice_starts, slice_lengths)]
timestamps = SlicedTimestamps(slices)
else
# 0.8 reader handles (it falls back to a contiguous range when absent).
systemmodel_0_8(f)
else
error("PRAS file format $versionstring not supported by this version of PRASBase.")
Comment on lines +167 to +186
function SystemModel(
regions::Regions{N,P}, interfaces::Interfaces{N,P},
generators::Generators{N,L,T,P}, region_gen_idxs::Vector{UnitRange{Int}},
storages::Storages{N,L,T,P,E}, region_stor_idxs::Vector{UnitRange{Int}},
generatorstorages::GeneratorStorages{N,L,T,P,E}, region_genstor_idxs::Vector{UnitRange{Int}},
demandresponses::DemandResponses{N,L,T,P,E}, region_dr_idxs::Vector{UnitRange{Int}},
lines::Lines{N,L,T,P}, interface_line_idxs::Vector{UnitRange{Int}},
slices::Vector{<:StepRange{ZonedDateTime}},
attrs::Dict{String, String}=Dict{String, String}()
) where {N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit}

return SystemModel(
regions, interfaces,
generators, region_gen_idxs,
storages, region_stor_idxs,
generatorstorages, region_genstor_idxs,
demandresponses, region_dr_idxs,
lines, interface_line_idxs,
SlicedTimestamps(collect(slices)), attrs)
end
Comment on lines +189 to +207
function SystemModel(
regions::Regions{N,P}, interfaces::Interfaces{N,P},
generators::Generators{N,L,T,P}, region_gen_idxs::Vector{UnitRange{Int}},
storages::Storages{N,L,T,P,E}, region_stor_idxs::Vector{UnitRange{Int}},
generatorstorages::GeneratorStorages{N,L,T,P,E}, region_genstor_idxs::Vector{UnitRange{Int}},
lines::Lines{N,L,T,P}, interface_line_idxs::Vector{UnitRange{Int}},
slices::Vector{<:StepRange{ZonedDateTime}},
attrs::Dict{String, String}=Dict{String, String}()
) where {N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit}

return SystemModel(
regions, interfaces,
generators, region_gen_idxs,
storages, region_stor_idxs,
generatorstorages, region_genstor_idxs,
DemandResponses{N,L,T,P,E}(), repeat([1:0],length(regions)),
lines, interface_line_idxs,
SlicedTimestamps(collect(slices)), attrs)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants