Skip to main content

scientific_workflow/
configuration.rs

1//! Strict study settings, named paths, and phase-scoped parameter expansion.
2//!
3//! [`StudySettings`] loads the process-level replicate policy and preserves one
4//! opaque application-owned object from `study.json`. It remains separate from
5//! the scientific parameter registry:
6//!
7//! ```json
8//! {
9//!   "replicate_settings": {
10//!     "replicates": 1,
11//!     "scheduling": "sequential",
12//!     "failure_policy": "fail_fast",
13//!     "base_seed": 1101
14//!   },
15//!   "application": {
16//!     "protocol": "my-application-study"
17//!   }
18//! }
19//! ```
20//!
21//! A study stores scientific parameters in `config/parameters.json` and named
22//! paths independently in `config/paths.json`. Parameter configuration has one
23//! global scope and one or more string-keyed components. Each component
24//! contains shared parameters and string-keyed workloads:
25//!
26//! ```text
27//! global
28//! components
29//! └── <component key>
30//!     ├── shared
31//!     └── workloads
32//!         └── <workload key>
33//! ```
34//!
35//! [`StudyConfiguration`] validates the complete registry. Calling
36//! [`StudyConfiguration::workload`] returns a [`WorkloadConfiguration`], whose
37//! combinations are the Cartesian composition of global, component-shared,
38//! and workload-local selections. There is deliberately no component-level
39//! combination API: components share values but are not expandable spaces.
40//!
41//! Ordinary JSON values, including arrays, are literal. An object containing
42//! exactly `"$sweep"` declares independent Cartesian choices. A scope-level
43//! `"$cases"` array declares correlated alternatives. One scope cannot mix the
44//! two forms.
45//!
46//! ```json
47//! {
48//!   "global": {
49//!     "temperature": {"$sweep": [280.0, 300.0]},
50//!     "lattice_shape": [64]
51//!   },
52//!   "components": {
53//!     "models": {
54//!       "shared": {"seed": {"$sweep": [7, 11]}},
55//!       "workloads": {
56//!         "glv": {"solver": {"step": 0.01}},
57//!         "analysis": {"include_space": true}
58//!       }
59//!     }
60//!   }
61//! }
62//! ```
63//!
64//! ```no_run
65//! use scientific_workflow::configuration::{ProjectPaths, StudyConfiguration};
66//!
67//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
68//! let study = StudyConfiguration::load("scientific-study")?;
69//! let models = study.workload("models", "glv")?;
70//! let paths = ProjectPaths::load("scientific-study")?;
71//!
72//! for configuration in models.combinations() {
73//!     let (temperature, seed): (f64, u64) =
74//!         configuration.decode_values(("/temperature", "/seed"))?;
75//!     println!("temperature={temperature} seed={seed}");
76//! }
77//! println!("recordings={}", paths.resolve_path("recordings")?.display());
78//! # Ok(())
79//! }
80//! ```
81//!
82//! Configuration loading is immutable and side-effect free beyond reading its
83//! source. It validates execution policy but does not enact it, create tasks,
84//! resolve model semantics, create output, or inspect path targets.
85//! Applications pass [`ReplicateSettings`] to the execution module and map
86//! [`ResolvedConfiguration`] values into their own workloads.
87
88mod error;
89mod parameter_key_tuple;
90mod parameter_path;
91mod parameter_tree;
92mod parameters;
93mod paths;
94mod settings;
95pub(crate) mod source;
96mod sweep;
97
98pub use error::ConfigurationError;
99#[doc(hidden)]
100pub use parameter_key_tuple::ParameterKeyTuple;
101pub use parameters::{
102    ConfigurationIter, ResolvedConfiguration, StudyConfiguration, WorkloadConfiguration,
103};
104pub use paths::ProjectPaths;
105pub use settings::{ReplicateFailurePolicy, ReplicateScheduling, ReplicateSettings, StudySettings};