Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ edition = "2024"
rand = "0.10"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
57 changes: 49 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ cargo run --release -- --preset demo
```

```bash
cargo run --release -- --scenario /path/to/scenario.json
cargo run --release -- --scenario /path/to/scenario.toml
```

```bash
Expand Down Expand Up @@ -84,13 +84,15 @@ curl -s "http://127.0.0.1:8080/telemetry?from=4&to=8"

Example scenario file:

```json
{
"houses": 20,
"feeder_kw": 200,
"seed": 42,
"steps_per_day": 24
}
```toml
houses = 20
feeder_kw = 200.0
seed = 42
steps_per_day = 24
solar_kw_peak_per_house = 5.0
dr_start_step = 17
dr_end_step = 21
dr_reduction_kw_per_house = 1.5
```

#### Example output:
Expand Down Expand Up @@ -121,6 +123,45 @@ Notes:
- `--telemetry-out` writes CSV columns:
`timestep,time_hr,target_kw,feeder_kw,tracking_error_kw,baseload_kw,solar_kw,ev_requested_kw,ev_dispatched_kw,battery_kw,battery_soc,dr_requested_kw,dr_achieved_kw,limit_ok`

### Scenario Presets (TOML)

Canonical scenario format is TOML. Built-in presets live under `./scenarios/`:

- `baseline.toml`
- `high_solar.toml`
- `dr_event.toml`

Run them via CLI:

```bash
cargo run --release -- --scenario scenarios/baseline.toml
```

```bash
cargo run --release -- --scenario scenarios/high_solar.toml
```

```bash
cargo run --release -- --scenario scenarios/dr_event.toml
```

Bare filenames are also resolved from `./scenarios`, for example:

```bash
cargo run --release -- --scenario high_solar.toml
```

Scenario schema keys:

- `houses` (u32, > 0)
- `feeder_kw` (f32, > 0)
- `seed` (u64)
- `steps_per_day` (usize, > 0)
- `solar_kw_peak_per_house` (f32, >= 0)
- `dr_start_step` (usize, `< steps_per_day`)
- `dr_end_step` (usize, `<= steps_per_day` and `> dr_start_step`)
- `dr_reduction_kw_per_house` (f32, >= 0)

### HTTP API (schema v1)

The API serves JSON objects using the same schema v1 field names as telemetry CSV.
Expand Down
9 changes: 9 additions & 0 deletions scenarios/baseline.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Baseline scenario: balanced defaults.
houses = 20
feeder_kw = 200.0
seed = 42
steps_per_day = 24
solar_kw_peak_per_house = 5.0
dr_start_step = 17
dr_end_step = 21
dr_reduction_kw_per_house = 1.5
9 changes: 9 additions & 0 deletions scenarios/dr_event.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# DR-event scenario: stronger and earlier demand response window.
houses = 20
feeder_kw = 200.0
seed = 42
steps_per_day = 24
solar_kw_peak_per_house = 5.0
dr_start_step = 10
dr_end_step = 16
dr_reduction_kw_per_house = 3.0
9 changes: 9 additions & 0 deletions scenarios/high_solar.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# High-solar scenario: stronger daytime solar penetration.
houses = 20
feeder_kw = 200.0
seed = 42
steps_per_day = 24
solar_kw_peak_per_house = 12.0
dr_start_step = 17
dr_end_step = 21
dr_reduction_kw_per_house = 0.5
2 changes: 1 addition & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn parse_options(args: &[String]) -> Result<CliOptions, String> {
"--scenario" => {
i += 1;
let path = args.get(i).ok_or_else(|| {
"missing value for --scenario (expected a JSON file path)".to_string()
"missing value for --scenario (expected a .toml scenario file path)".to_string()
})?;
if scenario.replace(PathBuf::from(path)).is_some() {
return Err("--scenario provided more than once".to_string());
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn main() {
};

let scenario = if let Some(path) = opts.scenario.as_deref() {
match ScenarioConfig::from_json_path(path) {
match ScenarioConfig::from_path(path) {
Ok(s) => s,
Err(err) => {
eprintln!("Error: {err}");
Expand Down
20 changes: 12 additions & 8 deletions src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ pub fn run_scenario(config: &ScenarioConfig, print_readable_log: bool) -> Simula
let target_schedule = DayAheadSchedule::flat_target(&load_forecast);

let mut pv = SolarPv::new(
5.0 * houses, /* kw_peak */
steps_per_day, /* steps_per_day */
6, /* sunrise_idx (6 AM) */
18, /* sunset_idx (6 PM) */
0.05, /* noise_std */
config.seed.wrapping_add(1), /* seed */
config.solar_kw_peak_per_house * houses, /* kw_peak */
steps_per_day, /* steps_per_day */
6, /* sunrise_idx (6 AM) */
18, /* sunset_idx (6 PM) */
0.05, /* noise_std */
config.seed.wrapping_add(1), /* seed */
);

let solar_device = pv.device_type();
Expand Down Expand Up @@ -90,8 +90,11 @@ pub fn run_scenario(config: &ScenarioConfig, print_readable_log: bool) -> Simula
config.feeder_kw * 0.8, /* max_export_kw */
);

// Example external DR event: request 1.5kW reduction from hour 17 to 21.
let dr_event = DemandResponseEvent::new(17, 21, 1.5 * houses);
let dr_event = DemandResponseEvent::new(
config.dr_start_step,
config.dr_end_step,
config.dr_reduction_kw_per_house * houses,
);

let controller = NaiveRtController;

Expand Down Expand Up @@ -227,6 +230,7 @@ mod tests {
feeder_kw: 40.0,
seed: 777,
steps_per_day: 24,
..ScenarioConfig::default()
};

let run_a = run_scenario(&scenario, false);
Expand Down
Loading