Updated the parameter names contained within the matlab controller david1606 #1
15 changed files with 2485 additions and 5 deletions
11
.gitignore
vendored
11
.gitignore
vendored
|
|
@ -23,3 +23,14 @@ slprj/
|
|||
# Personal data — recruitment notes on named candidates. Kept locally, never committed.
|
||||
# Root-anchored so it cannot catch the generic question template in docs/.
|
||||
/Interview Questions-*.md
|
||||
|
||||
# Python — the shared environment is rebuilt from uv.lock with `uv sync`; never commit it
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
*.egg-info/
|
||||
|
||||
# Experiment logs and local data downloads
|
||||
wandb/
|
||||
**/data/raw/
|
||||
|
|
|
|||
1
.python-version
Normal file
1
.python-version
Normal file
|
|
@ -0,0 +1 @@
|
|||
3.12
|
||||
66
AICONTROL/README.md
Normal file
66
AICONTROL/README.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# AICONTROL — the AI & Control cluster's folder
|
||||
|
||||
Everything our cluster writes lives here: the placeholder environment, the forecasting
|
||||
harness, the training rig, the evaluation pipeline, the safety layer and the dashboard. The
|
||||
plan we work from is [`docs/01-project/ai-control-cluster-plan-2026-2027.md`](../docs/01-project/ai-control-cluster-plan-2026-2027.md).
|
||||
|
||||
## Clone and run
|
||||
|
||||
The whole team shares one Python environment, pinned at the repository root. You need Python
|
||||
3.12 and [uv](https://docs.astral.sh/uv/) (one-off: `pip install uv`).
|
||||
|
||||
```bash
|
||||
git clone https://git.teamshiftenergy.com/pepe/ALLSHIFT.git
|
||||
cd ALLSHIFT
|
||||
uv sync # first time: creates .venv/ with everyone's tools, a few minutes
|
||||
uv run pytest # runs our tests; all green means your setup works
|
||||
```
|
||||
|
||||
`uv sync` reads `uv.lock`, so everyone gets exactly the same versions. Never commit `.venv/`.
|
||||
If you add a package, add it to the root `pyproject.toml`, run `uv lock`, and commit the
|
||||
updated `uv.lock` with your change.
|
||||
|
||||
## What is where
|
||||
|
||||
```
|
||||
AICONTROL/
|
||||
├── aicontrol/ ← Python package (import aicontrol)
|
||||
│ └── env/
|
||||
│ └── spaces.py ← what the agent sees and controls, built from configs/env.yaml
|
||||
├── configs/
|
||||
│ └── env.yaml ← plant sizes, observation ranges, forecast layout, PLACEHOLDER reward weights
|
||||
├── docs/
|
||||
│ └── interface-draft.md← the Simulations → AI handover, drafted for the November session
|
||||
├── tests/ ← pytest; run from the repository root with `uv run pytest`
|
||||
└── README.md
|
||||
```
|
||||
|
||||
Folders that will appear as the work does: `env/placeholder.py` (the placeholder
|
||||
environment), `forecast/`, `train/`, `evaluate/`, `safety/`, `dashboard/`.
|
||||
|
||||
## Who does what
|
||||
|
||||
| Person | Seats |
|
||||
|---|---|
|
||||
| Lead | RL Environment + Safety |
|
||||
| Person 2 | RL Training |
|
||||
| Person 3 | RL Evaluation + Explainability & Dashboard |
|
||||
| Person 4 | Forecasting |
|
||||
|
||||
## Q1 goal — what runs on Friday 16 October
|
||||
|
||||
1. The placeholder environment, with a determinism test and an energy test.
|
||||
2. A training script (PPO or SAC on the placeholder, three seeds, logged to Weights & Biases).
|
||||
3. The results pipeline: any controller × any scenario → one row; the comparison table; the
|
||||
column list for Business. Controller names reserved: `rule_based`, `mpc`,
|
||||
`perfect_knowledge`, `agent`.
|
||||
4. The forecasting harness with "same as yesterday" baselines and a skill score.
|
||||
5. The interface document, ready for the joint session with Simulations.
|
||||
|
||||
## Rules we keep
|
||||
|
||||
- Never commit `.venv/`, Weights & Biases run folders, or raw data downloads (the `.gitignore`
|
||||
covers them).
|
||||
- Machine-made tables are Parquet; hand-written settings are YAML; documents are Markdown.
|
||||
- The placeholder environment never gets better physics. The day the twin runs, we point at it
|
||||
and delete the placeholder.
|
||||
3
AICONTROL/aicontrol/__init__.py
Normal file
3
AICONTROL/aicontrol/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
"""AI & Control cluster package for Team SHIFT."""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
1
AICONTROL/aicontrol/env/__init__.py
vendored
Normal file
1
AICONTROL/aicontrol/env/__init__.py
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
"""The environment side: spaces now, the placeholder environment next week."""
|
||||
147
AICONTROL/aicontrol/env/spaces.py
vendored
Normal file
147
AICONTROL/aicontrol/env/spaces.py
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
"""Observation and action spaces for the SHIFT hospital microgrid: the sketch.
|
||||
|
||||
Week-1 work for the RL Environment seat. This fixes the *shape* of what the agent sees and
|
||||
what it controls, built from ``configs/env.yaml``, so that the placeholder environment (next
|
||||
week) and later the real twin from Simulations both expose exactly this interface.
|
||||
|
||||
Conventions
|
||||
-----------
|
||||
* One observation is one flat float32 vector. Every slot has a name, a unit and a range.
|
||||
* One action is four floats: electrolyser, fuel cell, battery, shedding level.
|
||||
* Names follow the Simulator I/O sheet where it has one (``P_PV``, ``SoC``, ``H2_level``,
|
||||
``p_tank``, ``price``, ``CO2_int``, ``grid_on``); the Project Manual's interface list
|
||||
decides what is in and what is out.
|
||||
* Ranges are bounds for scaling and sanity checks, not physical guarantees.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import yaml
|
||||
from gymnasium import spaces
|
||||
|
||||
DEFAULT_CONFIG = Path(__file__).resolve().parents[2] / "configs" / "env.yaml"
|
||||
|
||||
# Shedding levels, as the manual lists them.
|
||||
SHEDDING_NONE = 0 # nothing shed
|
||||
SHEDDING_TIER3 = 1 # Tier 3 shed
|
||||
SHEDDING_TIERS_2_3 = 2 # Tiers 2 and 3 shed; Tier 1 is never shed
|
||||
|
||||
ACTION_NAMES = ("u_ele", "u_fc", "u_batt", "shed")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Slot:
|
||||
"""One named block of the observation vector."""
|
||||
|
||||
name: str
|
||||
unit: str
|
||||
low: float
|
||||
high: float
|
||||
size: int = 1
|
||||
source: str = "" # where the value comes from: twin state, data, calendar, forecaster
|
||||
|
||||
|
||||
def load_config(path: str | Path | None = None) -> dict[str, Any]:
|
||||
"""Read the environment settings (defaults to ``AICONTROL/configs/env.yaml``)."""
|
||||
with open(path or DEFAULT_CONFIG, encoding="utf-8") as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
|
||||
def observation_slots(cfg: dict[str, Any]) -> list[Slot]:
|
||||
"""The observation layout, in order. Change the config, not this list, to resize things."""
|
||||
plant = cfg["plant"]
|
||||
obs = cfg["observation"]
|
||||
fc = cfg["forecast"]
|
||||
price_low, price_high = obs["price_range"]
|
||||
co2_low, co2_high = obs["co2_range"]
|
||||
|
||||
slots = [
|
||||
Slot("time_of_day", "sin, cos", -1.0, 1.0, 2, "calendar"),
|
||||
Slot("time_of_year", "sin, cos", -1.0, 1.0, 2, "calendar"),
|
||||
Slot("P_PV", "kW", 0.0, plant["pv_kw"], 1, "twin: solar output now"),
|
||||
Slot("L_tier1", "kW", 0.0, plant["load_max_kw"], 1, "data: critical load now"),
|
||||
Slot("L_tier2", "kW", 0.0, plant["load_max_kw"], 1, "data: essential load now"),
|
||||
Slot("L_tier3", "kW", 0.0, plant["load_max_kw"], 1, "data: non-critical load now"),
|
||||
Slot("SoC", "fraction 0-1", 0.0, 1.0, 1, "twin: battery state of charge"),
|
||||
Slot("H2_level", "kg", 0.0, plant["h2_capacity_kg"], 1, "twin: hydrogen in the tank"),
|
||||
Slot("p_tank", "bar", 0.0, plant["tank_p_max_bar"], 1, "twin: tank pressure"),
|
||||
Slot("price", "currency/kWh", price_low, price_high, 1, "data: electricity price now"),
|
||||
Slot("CO2_int", "kg CO2/kWh", co2_low, co2_high, 1, "data: grid carbon intensity now"),
|
||||
Slot("grid_on", "0/1", 0.0, 1.0, 1, "data: grid available"),
|
||||
Slot("ele_on", "0/1", 0.0, 1.0, 1, "twin: electrolyser running"),
|
||||
Slot("fc_on", "0/1", 0.0, 1.0, 1, "twin: fuel cell running"),
|
||||
]
|
||||
quantiles = ", ".join(fc["quantiles"])
|
||||
for quantity in fc["quantities"]:
|
||||
high = plant["pv_kw"] if quantity == "solar" else plant["load_max_kw"]
|
||||
for horizon in fc["horizons_min"]:
|
||||
slots.append(
|
||||
Slot(
|
||||
f"fcst_{quantity}_{horizon}min",
|
||||
"kW",
|
||||
0.0,
|
||||
high,
|
||||
len(fc["quantiles"]),
|
||||
f"forecaster: {quantiles} at +{horizon} min",
|
||||
)
|
||||
)
|
||||
return slots
|
||||
|
||||
|
||||
def observation_size(cfg: dict[str, Any]) -> int:
|
||||
return sum(slot.size for slot in observation_slots(cfg))
|
||||
|
||||
|
||||
def build_observation_space(cfg: dict[str, Any]) -> spaces.Box:
|
||||
slots = observation_slots(cfg)
|
||||
low = np.concatenate([np.full(s.size, s.low, dtype=np.float32) for s in slots])
|
||||
high = np.concatenate([np.full(s.size, s.high, dtype=np.float32) for s in slots])
|
||||
return spaces.Box(low=low, high=high, dtype=np.float32)
|
||||
|
||||
|
||||
def build_action_space(cfg: dict[str, Any]) -> spaces.Box:
|
||||
"""Four floats.
|
||||
|
||||
``u_ele`` in [0, 1]: fraction of electrolyser rated power (0 = off)
|
||||
``u_fc`` in [0, 1]: fraction of fuel cell rated power (0 = off)
|
||||
``u_batt`` in [-1, 1]: fraction of battery max power; positive discharges, negative charges
|
||||
``shed`` in [0, L-1]: rounded to a shedding level (0 nothing, 1 Tier 3, 2 Tiers 2 and 3)
|
||||
|
||||
Stable-Baselines3 algorithms take either all-continuous or all-discrete actions, so the
|
||||
three-way shedding choice rides along as a continuous number and is rounded inside the
|
||||
environment. To confirm at the joint session with Simulations.
|
||||
"""
|
||||
levels = cfg["action"]["shedding_levels"]
|
||||
low = np.array([0.0, 0.0, -1.0, 0.0], dtype=np.float32)
|
||||
high = np.array([1.0, 1.0, 1.0, float(levels - 1)], dtype=np.float32)
|
||||
return spaces.Box(low=low, high=high, dtype=np.float32)
|
||||
|
||||
|
||||
def shedding_level(action: np.ndarray, cfg: dict[str, Any]) -> int:
|
||||
"""Turn the fourth action number into a shedding level, clipped to the allowed ones."""
|
||||
levels = cfg["action"]["shedding_levels"]
|
||||
return int(np.clip(np.rint(action[3]), 0, levels - 1))
|
||||
|
||||
|
||||
def describe(cfg: dict[str, Any]) -> str:
|
||||
"""Markdown table of the observation layout, for the interface document."""
|
||||
lines = ["| # | name | size | unit | range | source |", "|---|---|---|---|---|---|"]
|
||||
index = 0
|
||||
for s in observation_slots(cfg):
|
||||
lines.append(
|
||||
f"| {index} | `{s.name}` | {s.size} | {s.unit} | {s.low:g} … {s.high:g} | {s.source} |"
|
||||
)
|
||||
index += s.size
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
config = load_config()
|
||||
print(describe(config))
|
||||
print(f"\nobservation size: {observation_size(config)}")
|
||||
print(f"action space: {build_action_space(config)}")
|
||||
40
AICONTROL/configs/env.yaml
Normal file
40
AICONTROL/configs/env.yaml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Environment settings — the shape of the Simulations → AI handover.
|
||||
#
|
||||
# This file feeds aicontrol/env/spaces.py now and the placeholder environment next week.
|
||||
# Values marked PLACEHOLDER are ours until: the joint session with Simulations (November)
|
||||
# for sizes, ranges and the forecast layout; the reward-weight conversation with Energy
|
||||
# Management and Business (Q2) for the weights. Same YAML style as Simulations' scenario
|
||||
# files, to be aligned with their core developer.
|
||||
|
||||
step_minutes: 5 # the manual's timestep; 105,120 steps per year
|
||||
|
||||
plant: # PLACEHOLDER: the Rijnstate-type pilot plant from the cost register
|
||||
pv_kw: 460 # 2,300 m² of panels
|
||||
electrolyser_kw: 118 # H2B2 EL20N datasheet, found in the live Simulink model
|
||||
fuel_cell_kw: 100
|
||||
battery_kwh: 500
|
||||
battery_kw: 250
|
||||
h2_capacity_kg: 200
|
||||
tank_p_max_bar: 30 # 30 bar storage, from the cost register
|
||||
load_max_kw: 1000 # upper bound for scaling load observations, PLACEHOLDER
|
||||
|
||||
observation:
|
||||
price_range: [-1.0, 5.0] # currency per kWh; negative prices happen
|
||||
co2_range: [0.0, 1.5] # kg CO2 per kWh
|
||||
|
||||
forecast: # the block of forecasts the agent sees; layout to agree with Simulations
|
||||
quantities: [solar, tier1, tier2, tier3]
|
||||
horizons_min: [60, 180, 360, 1440]
|
||||
quantiles: [p10, p50, p90] # best / likely / worst, as the manual asks
|
||||
|
||||
action:
|
||||
shedding_levels: 3 # 0 nothing, 1 Tier 3, 2 Tiers 2 and 3
|
||||
|
||||
reward: # PLACEHOLDER weights — not ours to set; a proposal for the Q2 conversation
|
||||
cost_per_currency: -1.0 # energy cost this step
|
||||
co2_per_kg: -0.05 # emissions this step
|
||||
tier1_unserved_per_kwh: -1000.0 # must dominate everything else
|
||||
tier2_shed_per_kwh: -2.0
|
||||
tier3_shed_per_kwh: -0.5
|
||||
limit_violation: -10.0 # battery or tank pushed past a safe limit
|
||||
switching: -0.1 # equipment switched on or off this step
|
||||
103
AICONTROL/docs/interface-draft.md
Normal file
103
AICONTROL/docs/interface-draft.md
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# The Simulations → AI interface — draft for the November session
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **What this is** | Our side of the handover the Project Manual calls "the environment interface": what the agent sees, what it controls, how it is scored. Written by the RL Environment seat; to be finished jointly with Simulations' Simulation Core Developer early in Q2 and frozen before Christmas |
|
||||
| **Where it lives in code** | `aicontrol/env/spaces.py` builds the spaces from `configs/env.yaml`. Run `uv run python -m aicontrol.env.spaces` to print the current layout |
|
||||
| **Grown from** | The manual's interface list (section 3.2) and our older Simulator I/O sheet, whose names we keep where they exist |
|
||||
| **Status** | Draft, 11 Sep 2026. Everything marked *to agree* is open until the joint session |
|
||||
|
||||
## The idea in one paragraph
|
||||
|
||||
The agent and the simulated hospital talk through a Gymnasium environment: `reset()` starts a
|
||||
run, `step(action)` advances five minutes and returns what the agent may see next, plus a
|
||||
score. Simulations owns everything inside `step` (the physics, the KPIs); we own everything
|
||||
outside it (the agent, the safety layer, the evaluation). The two only have to agree on three
|
||||
lists, below. Until the twin exists, a placeholder environment with toy physics exposes exactly
|
||||
these lists so our side can be built now.
|
||||
|
||||
## 1. What the agent sees
|
||||
|
||||
One flat vector of 64 numbers per step, laid out from `configs/env.yaml`. The first sixteen are
|
||||
the present; the remaining 48 are the forecasts.
|
||||
|
||||
| # | name | size | unit | range | source |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 | `time_of_day` | 2 | sin, cos | −1 … 1 | calendar |
|
||||
| 2 | `time_of_year` | 2 | sin, cos | −1 … 1 | calendar |
|
||||
| 4 | `P_PV` | 1 | kW | 0 … 460 | twin: solar output now |
|
||||
| 5 | `L_tier1` | 1 | kW | 0 … 1000 | data: critical load now |
|
||||
| 6 | `L_tier2` | 1 | kW | 0 … 1000 | data: essential load now |
|
||||
| 7 | `L_tier3` | 1 | kW | 0 … 1000 | data: non-critical load now |
|
||||
| 8 | `SoC` | 1 | fraction 0–1 | 0 … 1 | twin: battery state of charge |
|
||||
| 9 | `H2_level` | 1 | kg | 0 … 200 | twin: hydrogen in the tank |
|
||||
| 10 | `p_tank` | 1 | bar | 0 … 30 | twin: tank pressure |
|
||||
| 11 | `price` | 1 | currency/kWh | −1 … 5 | data: electricity price now |
|
||||
| 12 | `CO2_int` | 1 | kg CO₂/kWh | 0 … 1.5 | data: grid carbon intensity now |
|
||||
| 13 | `grid_on` | 1 | 0/1 | 0 … 1 | data: grid available |
|
||||
| 14 | `ele_on` | 1 | 0/1 | 0 … 1 | twin: electrolyser running |
|
||||
| 15 | `fc_on` | 1 | 0/1 | 0 … 1 | twin: fuel cell running |
|
||||
| 16–63 | `fcst_<quantity>_<horizon>min` | 3 each | kW | 0 … max | forecaster: p10, p50, p90 for solar, tier1, tier2, tier3 at +60, +180, +360, +1440 min |
|
||||
|
||||
Why sin and cos for time: a clock hand, not a number that jumps from 23:59 to 00:00. Why
|
||||
ranges: they scale the numbers for the network and let a test catch nonsense; they are not
|
||||
physical guarantees.
|
||||
|
||||
*To agree with Simulations:* which of these the twin reports directly (tank pressure or only
|
||||
kilograms; equipment state as on/off or as a mode); the forecast horizons and whether four are
|
||||
enough; the load upper bound once the dataset scale is known; the currency.
|
||||
|
||||
## 2. What the agent controls
|
||||
|
||||
Four floats per step.
|
||||
|
||||
| name | range | meaning |
|
||||
|---|---|---|
|
||||
| `u_ele` | 0 … 1 | fraction of electrolyser rated power; 0 is off, anything below the minimum load becomes off |
|
||||
| `u_fc` | 0 … 1 | fraction of fuel cell rated power |
|
||||
| `u_batt` | −1 … 1 | fraction of battery max power; positive discharges, negative charges |
|
||||
| `shed` | 0 … 2 | rounded to a shedding level: 0 nothing, 1 Tier 3 shed, 2 Tiers 2 and 3 shed. Tier 1 is never an option |
|
||||
|
||||
The grid is not an action: it absorbs whatever is left after the four above, within its
|
||||
connection limit. Load shedding *is* an action here, as the manual lists it, but the safety
|
||||
layer will refuse anything that would touch Tier 1.
|
||||
|
||||
*To agree with Simulations:* the shedding encoding (a continuous number rounded inside the
|
||||
environment is the simplest thing that works with Stable-Baselines3, which cannot mix
|
||||
continuous and discrete actions; the alternative is making all four discrete); whether the
|
||||
twin wants setpoints in kW or in fractions; ramp limits applied inside the twin or reported
|
||||
back as a clipped action.
|
||||
|
||||
## 3. How it is scored
|
||||
|
||||
The score per step is a weighted sum of the terms the manual lists. The *terms* are ours to
|
||||
propose; the *weights* are agreed with Energy Management and Business, because they say how
|
||||
much a euro, a kilo of CO₂ and a shed ward are worth relative to each other. The numbers in
|
||||
`configs/env.yaml` are placeholders so that code can run.
|
||||
|
||||
| term | sign | what it measures |
|
||||
|---|---|---|
|
||||
| energy cost | − | money paid for grid electricity this step, minus money earned exporting |
|
||||
| CO₂ | − | grid electricity used × carbon intensity |
|
||||
| Tier 1 unserved | − − | any critical load not supplied; must dominate every other term |
|
||||
| Tier 2 / Tier 3 shed | − | non-critical load cut, priced far below Tier 1 |
|
||||
| limit violation | − | battery or tank pushed past a safe limit |
|
||||
| switching | − | equipment turned on or off this step, to stop chattering |
|
||||
|
||||
*To agree:* whether a clipped action (the twin refused part of it) counts as a violation or
|
||||
only as a reported difference; whether the perfect-knowledge benchmark and MPC are scored on
|
||||
exactly this sum, so the comparison is fair.
|
||||
|
||||
## 4. What the twin must return so we can compute all of the above
|
||||
|
||||
Per step: served load per tier, grid import and export, PV used and curtailed, battery power,
|
||||
electrolyser and fuel cell power, hydrogen produced and consumed, any limit clipping, and the
|
||||
KPI increments. The manual's four KPIs (Self-Sufficiency Rate, Grid Dependency Ratio, Critical
|
||||
Load Uptime, SoC Violation Rate) are computed by Simulations' KPI calculator; we read them,
|
||||
we do not recompute them.
|
||||
|
||||
## 5. What happens when the twin replaces the placeholder
|
||||
|
||||
Nothing on our side, if the three lists hold. The training rig, the evaluation pipeline, the
|
||||
safety wrapper and the dashboard all read the lists above, never the physics. That is the whole
|
||||
point of drafting this now.
|
||||
131
AICONTROL/docs/results-file.md
Normal file
131
AICONTROL/docs/results-file.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# The AI → Business results file — draft v0.1
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **What this is** | The third handover in the Project Manual: "one file with a fixed set of columns: one row per controller, per scenario, per run. Business builds the euro comparison straight from it. Agree the columns before the first benchmark is run" |
|
||||
| **Owner** | RL Evaluation seat (person 3). Reviewed by the lead 11 Sep 2026 from person 3's spreadsheet draft |
|
||||
| **Next** | Encode as `aicontrol/evaluate/results_schema.py` (week 4), show to Business's Commercial Model Analyst, agree the open questions at the bottom, then freeze |
|
||||
| **Format** | Parquet is the version of record; a CSV is written beside it so Business can double-click it. Rows are appended, never edited |
|
||||
|
||||
## Rules that make the file usable by a script
|
||||
|
||||
- Column names are lowercase `snake_case`, no spaces, no hyphens, and carry the unit where one
|
||||
exists (`_kwh`, `_kg`, `_kw`). Fractions are stored as 0–1, never as percentages.
|
||||
- One row is one run: one controller, on one scenario, with one seed. A controller is a *value*
|
||||
in the `controller` column, not a row label, so the file grows to hundreds of rows.
|
||||
- Controller names are fixed strings: `rule_based`, `mpc`, `perfect_knowledge`, `agent`, and
|
||||
the Q1 stand-ins `random` and `do_nothing`. Business's S0 baseline (the hospital without the
|
||||
hydrogen system) is not a controller but a plant configuration, so it is a *scenario* from
|
||||
Simulations' library, appearing here as `scenario = no_hydrogen_<year>` with
|
||||
`controller = rule_based`.
|
||||
- A column may be empty for a run (for example, `wandb_run` for a rule-based run). A column
|
||||
that is empty for every run at the freeze is dropped.
|
||||
|
||||
## Columns
|
||||
|
||||
### Identity — which run this row is
|
||||
|
||||
| column | type | meaning |
|
||||
|---|---|---|
|
||||
| `controller` | string | one of the fixed names above |
|
||||
| `controller_version` | string | git commit for rule-based / MPC; model checkpoint tag for the agent |
|
||||
| `scenario` | string | name of the scenario YAML file, e.g. `baseline_2018`, `blackout_72h`, `dark_december_week` |
|
||||
| `data_year` | int | which year of data the run used |
|
||||
| `held_out` | bool | true if the agent never saw this year in training; the manual's proof of generalisation |
|
||||
| `run` | int | repetition index within a setup (1, 2, 3, …) |
|
||||
| `seed` | int | the random seed of that repetition |
|
||||
| `steps` | int | number of steps simulated |
|
||||
| `step_minutes` | int | 5 |
|
||||
| `twin_version` | string | version of the Simulations package that produced the physics |
|
||||
| `parameter_book_version` | string | version of Energy Management's Parameter Book |
|
||||
| `dataset_version` | string | version of the clean hospital dataset |
|
||||
| `run_at` | timestamp | when the run finished (UTC) |
|
||||
| `wandb_run` | string | Weights & Biases run id, if any |
|
||||
|
||||
The three `*_version` columns are the manual's rule that "every result records which versions
|
||||
produced it".
|
||||
|
||||
### KPIs — the manual's four, plus money and carbon
|
||||
|
||||
Names are fixed now; the exact equations come from Energy Management's Dispatch & Grid
|
||||
Engineer and are applied by Simulations' KPI calculator. We copy the numbers; we do not
|
||||
recompute them.
|
||||
|
||||
| column | type | meaning |
|
||||
|---|---|---|
|
||||
| `self_sufficiency_rate` | fraction | share of load served without the grid |
|
||||
| `grid_dependency_ratio` | fraction | share of load that came from the grid |
|
||||
| `critical_load_uptime` | fraction | share of steps with Tier 1 fully served; must be 1.0 |
|
||||
| `soc_violation_rate` | fraction | share of steps with the battery outside its window |
|
||||
| `energy_cost` | float | net cost of grid electricity over the run (import cost minus export revenue) |
|
||||
| `currency` | string | `USD` or `EUR`; see open questions |
|
||||
| `co2_kg` | float | grid electricity used × carbon intensity, summed |
|
||||
|
||||
### Energy totals — the evidence behind the KPIs, and what Business's register needs
|
||||
|
||||
All summed over the run.
|
||||
|
||||
| column | type | meaning |
|
||||
|---|---|---|
|
||||
| `load_kwh` | float | total hospital demand |
|
||||
| `load_served_kwh` | float | demand actually supplied |
|
||||
| `tier1_unserved_kwh` | float | **must be 0** in every run that counts |
|
||||
| `tier2_shed_kwh` | float | essential load cut |
|
||||
| `tier3_shed_kwh` | float | non-critical load cut |
|
||||
| `pv_kwh` | float | solar available |
|
||||
| `pv_curtailed_kwh` | float | solar thrown away |
|
||||
| `grid_import_kwh` | float | |
|
||||
| `grid_export_kwh` | float | |
|
||||
| `grid_import_peak_kwh` | float | import during tariff peak hours |
|
||||
| `grid_import_offpeak_kwh` | float | import during off-peak hours |
|
||||
| `peak_import_kw` | float | highest grid import in any step; drives demand charges |
|
||||
| `battery_charge_kwh` | float | |
|
||||
| `battery_discharge_kwh` | float | |
|
||||
| `battery_full_cycles` | float | throughput ÷ capacity; the wear proxy |
|
||||
| `electrolyser_kwh` | float | electricity into the electrolyser |
|
||||
| `electrolyser_offpeak_kwh` | float | the part of it in off-peak hours |
|
||||
| `h2_produced_kg` | float | |
|
||||
| `h2_consumed_kg` | float | |
|
||||
| `fuel_cell_kwh` | float | electricity out of the fuel cell |
|
||||
| `fuel_cell_heat_kwh` | float | recovered heat, if the twin models it; else empty |
|
||||
| `chp_kwh` | float | electricity from the CHP plant |
|
||||
| `electrolyser_starts` | int | on/off switches |
|
||||
| `fuel_cell_starts` | int | on/off switches |
|
||||
| `limit_violations` | int | steps where the twin clipped an action against a limit |
|
||||
| `notes` | string | free text |
|
||||
|
||||
Why the peak / off-peak and hydrogen columns: Business's cost register has seven rows marked
|
||||
"ModelOutput / Missing" — off-peak share of hydrogen production, MWh shifted from peak to
|
||||
off-peak, peak shaving, effective cost of electrolysis, cost per kWh from hydrogen, gas
|
||||
displaced by fuel-cell heat, and the CO₂ change — and each is a ratio or difference of the
|
||||
columns above between the no-hydrogen scenario, the `rule_based` rows and the `agent` rows.
|
||||
The peak / off-peak definition is the tariff's, which Business and Energy Management own.
|
||||
|
||||
## The companion file
|
||||
|
||||
Business's register also asks for "simulation of net grid imports" as a series. That does not
|
||||
fit one row per run. Each run therefore also writes its per-step table (the same quantities,
|
||||
one row per 5-minute step) as Parquet under `results/runs/<run_id>.parquet`. The summary file
|
||||
above is the one they open; the per-step file is there when they need a chart.
|
||||
|
||||
## Changes from person 3's spreadsheet draft
|
||||
|
||||
- Added the `controller` header (the draft's column A had none) and made controller a column
|
||||
value rather than a row label.
|
||||
- Fixed `KPI_Grid_Dependecy_Ratio` → `grid_dependency_ratio`; dropped the `KPI_` prefix,
|
||||
hyphens and capitals from every name so the file loads without renaming.
|
||||
- Replaced `cost_eur` with `energy_cost` + `currency`, because the data source is ISO New
|
||||
England, which prices in dollars, while the business case is in euros.
|
||||
- Added identity columns (`held_out`, `data_year`, the three versions, `run_at`), the energy
|
||||
totals and the switching counts.
|
||||
- Kept `scenario`, `run`, `seed`, the four KPIs and `co2_kg` as drafted.
|
||||
|
||||
## Open questions for Business (person 3 brings these)
|
||||
|
||||
1. Currency: do they want dollars as recorded, or euros converted at a fixed rate they choose?
|
||||
2. Peak / off-peak: their tariff hours, so the split columns match their model.
|
||||
3. Are the seven register quantities computed by us (extra columns) or by them from these
|
||||
columns? Either works; theirs keeps one owner per number.
|
||||
4. What exactly their S0 baseline contains (no hydrogen only, or no battery either), so we can
|
||||
ask Simulations for that scenario file.
|
||||
5. Anything they need per run that is not here.
|
||||
17
AICONTROL/pyproject.toml
Normal file
17
AICONTROL/pyproject.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[project]
|
||||
name = "aicontrol"
|
||||
version = "0.1.0"
|
||||
description = "AI & Control cluster: placeholder environment, forecasting harness, training rig, evaluation pipeline, safety layer, dashboard"
|
||||
requires-python = ">=3.12,<3.13"
|
||||
dependencies = [
|
||||
"numpy>=2.0",
|
||||
"gymnasium>=1.0",
|
||||
"pyyaml>=6.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["aicontrol"]
|
||||
34
AICONTROL/tests/test_spaces.py
Normal file
34
AICONTROL/tests/test_spaces.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
"""The observation and action spaces build from the config and mean what they say."""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from aicontrol.env import spaces as S
|
||||
|
||||
|
||||
def test_observation_layout_matches_config():
|
||||
cfg = S.load_config()
|
||||
space = S.build_observation_space(cfg)
|
||||
fixed = 4 + 12 # calendar (2 + 2) plus the twelve single-value slots
|
||||
fc = cfg["forecast"]
|
||||
forecast = len(fc["quantities"]) * len(fc["horizons_min"]) * len(fc["quantiles"])
|
||||
assert space.shape == (fixed + forecast,)
|
||||
assert S.observation_size(cfg) == space.shape[0]
|
||||
assert space.contains(space.sample())
|
||||
|
||||
|
||||
def test_action_space_and_shedding_level():
|
||||
cfg = S.load_config()
|
||||
space = S.build_action_space(cfg)
|
||||
assert space.shape == (len(S.ACTION_NAMES),)
|
||||
assert space.contains(space.sample())
|
||||
assert S.shedding_level(np.array([0, 0, 0, 0.2]), cfg) == S.SHEDDING_NONE
|
||||
assert S.shedding_level(np.array([0, 0, 0, 1.4]), cfg) == S.SHEDDING_TIER3
|
||||
assert S.shedding_level(np.array([0, 0, 0, 2.0]), cfg) == S.SHEDDING_TIERS_2_3
|
||||
assert S.shedding_level(np.array([0, 0, 0, 9.0]), cfg) == S.SHEDDING_TIERS_2_3 # clipped
|
||||
|
||||
|
||||
def test_describe_lists_every_named_slot():
|
||||
cfg = S.load_config()
|
||||
table = S.describe(cfg)
|
||||
for name in ("P_PV", "SoC", "H2_level", "p_tank", "grid_on", "fcst_solar_60min", "fcst_tier3_1440min"):
|
||||
assert f"`{name}`" in table
|
||||
112
CLAUDE.md
Normal file
112
CLAUDE.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## What this repository is
|
||||
|
||||
ALLSHIFT is Team SHIFT's single shared repository (TU/e student team; an AI-managed hospital
|
||||
hydrogen microgrid, Beth Israel Deaconess Boston as the case). It is public on Gitea
|
||||
(`git.teamshiftenergy.com/pepe/ALLSHIFT`). Four kinds of content live side by side:
|
||||
|
||||
- `docs/` — the documentation hub. Every project document exists here as a markdown report;
|
||||
`docs/README.md` is the index and ends with the project-wide open-questions list. Retired
|
||||
material goes to `superseded/` with a stated reason.
|
||||
- `Shift Matlab Drive/` — a mirror of the team's MATLAB Drive: Simulink models and raw data.
|
||||
The Simulink work belongs to the Simulations cluster (see
|
||||
`docs/04-simulations/simulink-model-inventory.md`; the live framework needs R2026a). No
|
||||
Python goes here.
|
||||
- `website/` — the public Astro site, with its own `website/CLAUDE.md` and `pnpm` commands.
|
||||
No Python goes here either.
|
||||
- Root `pyproject.toml` + `uv.lock` — the one pinned Python environment for every cluster.
|
||||
Cluster code lives in top-level folders registered as uv workspace members; `AICONTROL/`
|
||||
(AI & Control cluster) is the first.
|
||||
|
||||
## Commands (from the repository root)
|
||||
|
||||
```bash
|
||||
uv sync # create/refresh .venv from uv.lock (Python 3.12; ~1.3 GB, torch included)
|
||||
uv run pytest # all tests (pytest testpaths = AICONTROL/tests)
|
||||
uv run pytest AICONTROL/tests/test_spaces.py::test_action_space_and_shedding_level # one test
|
||||
uv run python -m aicontrol.env.spaces # print the current observation/action layout as a table
|
||||
uv lock # after editing dependencies; commit the updated uv.lock
|
||||
```
|
||||
|
||||
Adding a dependency: edit `dependencies` in the root `pyproject.toml`, run `uv lock`, commit
|
||||
`uv.lock`. Adding a cluster folder: give it its own `pyproject.toml` (hatchling,
|
||||
`packages = [...]`), add the folder to `[tool.uv.workspace] members`, and add its package name
|
||||
to the root `dependencies` and to `[tool.uv.sources]` as `{ workspace = true }`.
|
||||
|
||||
Website: `cd website && pnpm dev | pnpm build | pnpm preview` — details in `website/CLAUDE.md`.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Clusters and the three handovers
|
||||
|
||||
The team leader's *Project Manual* (not in the repo) fixes four clusters and exactly three
|
||||
written handovers between them:
|
||||
|
||||
1. **Energy Management → Simulations:** the Parameter Book (a spreadsheet of every rating and
|
||||
efficiency, versioned) and the clean 5-minute BIDMC dataset with Tier 1 / 2 / 3 load
|
||||
assigned, as Parquet.
|
||||
2. **Simulations → AI:** a Gymnasium environment. Simulations owns everything inside `step()`
|
||||
— the twin's physics, the KPI calculator, and the benchmark controllers (rule-based, MPC on
|
||||
forecasts, perfect knowledge; Pyomo + HiGHS). AI owns everything outside it: the agent, the
|
||||
safety layer, evaluation, dashboard. Written jointly early in Q2, frozen before Christmas.
|
||||
3. **AI → Business:** one results file, one row per controller × scenario × run, Parquet with a
|
||||
CSV beside it.
|
||||
|
||||
Fixed by the manual, not up for local decision: 5-minute timestep (105,120 steps per year); a
|
||||
full simulated year must run in under a minute; Boston with ISO New England prices and carbon;
|
||||
Tier 1 load is never shed; reward weights are agreed across clusters and never tuned by code.
|
||||
|
||||
### `AICONTROL/` — the AI & Control cluster package
|
||||
|
||||
- Everything is built from `AICONTROL/configs/env.yaml`, so the joint session with Simulations
|
||||
can change a number without touching code. Values marked `PLACEHOLDER` there (plant sizes,
|
||||
load bound, reward weights) are stand-ins owned by other clusters; keep the marker until the
|
||||
owning cluster confirms a value.
|
||||
- `aicontrol/env/spaces.py` is the single source of the interface shape. `observation_slots(cfg)`
|
||||
defines the flat 64-value observation in order (calendar 4, present-state 12, forecasts
|
||||
= quantities × horizons × quantiles = 48); `build_action_space` gives a `Box(4)`: `u_ele`
|
||||
[0, 1], `u_fc` [0, 1], `u_batt` [−1, 1] with positive = discharge, `shed` [0, 2] rounded by
|
||||
`shedding_level()` because Stable-Baselines3 cannot mix continuous and discrete actions.
|
||||
Names follow the Simulator I/O sheet (`P_PV`, `SoC`, `H2_level`, `p_tank`, `price`,
|
||||
`CO2_int`, `grid_on`).
|
||||
- `AICONTROL/docs/interface-draft.md` is the handover document; keep its tables in step with
|
||||
`spaces.py` (regenerate the observation table with `describe()`).
|
||||
- The placeholder environment (next: `aicontrol/env/placeholder.py`) exposes exactly these
|
||||
spaces with toy physics. It never gets better physics and is deleted the day the twin runs.
|
||||
Do not build a competing simulator in this folder.
|
||||
- Planned subpackages: `forecast/`, `train/`, `evaluate/`, `safety/`, `dashboard/`. Tooling is
|
||||
fixed by the manual: LightGBM / scikit-learn / Optuna for forecasting, Gymnasium +
|
||||
Stable-Baselines3 for RL, Weights & Biases for logging (not MLflow), SHAP + Streamlit.
|
||||
|
||||
### Documentation conventions
|
||||
|
||||
- A markdown report of a non-markdown source opens with a provenance table (Source, Format,
|
||||
MD5, Status, Report generated). Reconstructed tables are labelled as such; observations that
|
||||
go beyond the source are attributed to the audit, not the author.
|
||||
- `docs/02-specifications/` is owned by the AI cluster. The RL state / action / reward
|
||||
documents there are superseded by the manual's interface list but kept for the rationale
|
||||
behind individual terms.
|
||||
- `docs/01-project/ai-control-cluster-plan-2026-2027.md` is the AI cluster's plan: seats,
|
||||
Q1 week by week, later blocks, open items for the leader.
|
||||
|
||||
## Data: what actually exists
|
||||
|
||||
Under `Shift Matlab Drive/Shift Matlab Drive/Energy_Managment/`: hospital load is the NREL
|
||||
ComStock building `89993-0.parquet` (2018, 15-minute, simulated; `MA_hourly_load.csv` is a
|
||||
mis-named numerical twin of it). Weather is NSRDB 2019, hourly, timestamps in UTC. PV is a
|
||||
PVWatts 4 kW typical-year run that aligns with neither. There is no price, carbon-intensity,
|
||||
outage or measured-load series. `hospital_communication_energy_system.csv` is synthetic noise;
|
||||
derive nothing from it. Provenance and licences: `docs/06-data/dataset-inventory.md`.
|
||||
|
||||
## Things to know
|
||||
|
||||
- Formats: machine-made tables are Parquet, hand-written settings are YAML, documents are
|
||||
Markdown.
|
||||
- `.venv/`, `wandb/` run folders and `**/data/raw/` are gitignored; `uv sync` rebuilds the
|
||||
environment from `uv.lock` on any machine.
|
||||
- `website/CLAUDE.md` embeds live third-party keys that are due for rotation; do not copy them
|
||||
anywhere.
|
||||
- `Interview Questions-*.md` at the root is personal recruitment data, gitignored on purpose.
|
||||
|
|
@ -93,7 +93,7 @@ Set once per simulation run.
|
|||
|---|---|---|---|
|
||||
| `N_c_ele` | Number of cells | — | Usually difficult to find |
|
||||
| `mu_F` | Faraday efficiency | — | Fall back to literature average (~0.95–0.99 for PEM) |
|
||||
| `P_ele_max` | Rated / max power | W | |
|
||||
| `P_Electro_max` | Rated / max power | W | |
|
||||
| `P_ele_min` | Minimum operating power | W | Below this the electrolyser shuts off (efficiency cliff) |
|
||||
| `I_ele_min` | Minimum operating current | A | Alternative to `P_ele_min` |
|
||||
| `cal_H2` | H₂ production calibration factor | — | If we end up reading H₂ flow from measured data |
|
||||
|
|
@ -104,13 +104,14 @@ Set once per simulation run.
|
|||
|---|---|---|---|
|
||||
| `N_c_fc` | Number of cells | — | |
|
||||
| `utilisation_fc` | H₂ utilisation | % | |
|
||||
| `P_fc_max` | Maximum output power | W | **Project spec: 100 kW** |
|
||||
| `P_FuelCell_max` | Maximum output power | W | **Project spec: 100 kW** |
|
||||
| `V_fc_min`, `V_fc_max` | Operating voltage range | V | |
|
||||
|
||||
### 1.3 Hydrogen tank
|
||||
|
||||
| Symbol | Name | Unit | Notes |
|
||||
|---|---|---|---|
|
||||
| `E_H2_max` | Maximum electrical energy capacity of the storage tank | J | |
|
||||
| `V_H2_max` | Maximum stored volume | L (or kg) | **Project spec: up to 200 kg total across two tanks** |
|
||||
| `V_H2_init` | Initial fill level | L (or kg) | |
|
||||
| `T_tank` | Operating temperature | K | TBD — isothermal assumption likely fine |
|
||||
|
|
@ -129,9 +130,11 @@ Set once per simulation run.
|
|||
| Symbol | Name | Unit | Notes |
|
||||
|---|---|---|---|
|
||||
| `E_rated` | Rated energy capacity | Wh | |
|
||||
| `E_battery_max` | Maximum energy capacity | Wh | |
|
||||
| `Q_rated` | Rated charge capacity | Ah | |
|
||||
| `P_batt_charge_max` | Max charge power | W | |
|
||||
| `P_batt_discharge_max` | Max discharge power | W | |
|
||||
| `P_battery_max` | Max (dis)charge power (assuming both are identical - this is the current assumption in the Simulink Controller) | W | |
|
||||
| `P_battery_charge_max` | Max charge power | W | |
|
||||
| `P_battery_discharge_max` | Max discharge power | W | |
|
||||
| `SoC_init` | Initial state of charge | — (0–1) | |
|
||||
| `SoC_min`, `SoC_max` | Operating window | — (0–1) | E.g. 0.1–0.9 |
|
||||
| `eta_batt_ch`, `eta_batt_dis` | Round-trip efficiencies | — | Often split into charge & discharge |
|
||||
|
|
@ -195,6 +198,8 @@ Carried forward to the next step.
|
|||
| Symbol | Name | Unit |
|
||||
|---|---|---|
|
||||
| `SoC(t)` | Battery state of charge | — (0–1) |
|
||||
| `E_battery_SOC(t)` | Energy currently present in battery | J |
|
||||
| `E_H2_SOC(t)` | Electrical Energy that can be currently extracted from the Hydrogen Storage Tank | J |
|
||||
| `H2_level(t)` | H₂ stored in tank | mol (or kg) |
|
||||
| `T_tank(t)` | Tank temperature | K (only if non-isothermal model) |
|
||||
| `p_tank(t)` | Tank pressure | bar (if modelled) |
|
||||
|
|
@ -203,7 +208,7 @@ Carried forward to the next step.
|
|||
|
||||
| Symbol | Name | Unit |
|
||||
|---|---|---|
|
||||
| `P_PV_available(t)` | PV power available given irradiance | W |
|
||||
| `P_PV(t)` | PV power available given irradiance | W |
|
||||
| `P_PV_used(t)` | PV power actually consumed | W |
|
||||
| `P_PV_curtailed(t)` | PV potential that was thrown away | W |
|
||||
| `P_ele(t)` | Actual electrolyser consumption | W |
|
||||
|
|
@ -212,6 +217,12 @@ Carried forward to the next step.
|
|||
| `P_grid(t)` | Actual grid flow (signed) | W |
|
||||
| `P_load_served_crit(t)` | Critical load served | W |
|
||||
| `P_load_served_noncrit(t)` | Non-critical load served | W |
|
||||
| `P_load_P(t)` | Power to be covered after PV (+ve = remaining power shortage, -ve = surplus to be used) | W |
|
||||
| `P_load_PB(t)` | Power to be covered after PV and Battery (same convention as P_load_P) | W |
|
||||
| `P_load_PBH(t)` | Power to be covered after PV, Battery and Hydrogen stroage tank (same convention as P_load_P(B)) | W |
|
||||
| `P_battery_cont(t)` | Power amount the controller determines the battery should (dis)charge at (accounting for battery properties and demand) | W |
|
||||
| `P_FuelCell_cont(t)` | Power amount the controller determines the fuel cell should provide (accounting for it's properties and demand) | W |
|
||||
| `P_Electro_cont(t)` | Power amount the controller determines the electrolyser should extract (accounting for it's properties and demand) | W |
|
||||
|
||||
### 3.3 Mass flows (hydrogen)
|
||||
|
||||
|
|
|
|||
52
pyproject.toml
Normal file
52
pyproject.toml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Team SHIFT — one pinned Python environment for every cluster.
|
||||
#
|
||||
# The Project Manual asks that everyone technical works in one repository from one pinned
|
||||
# Python environment. This file is that environment. The exact versions live in uv.lock;
|
||||
# `uv sync` at the repository root creates .venv/ with all of them, on any machine.
|
||||
#
|
||||
# Each cluster keeps its code in its own top-level folder (AICONTROL/, ...) as a workspace
|
||||
# member: add the folder to [tool.uv.workspace] members and its package name to
|
||||
# dependencies, and `uv sync` installs it in editable mode for everyone.
|
||||
|
||||
[project]
|
||||
name = "shift"
|
||||
version = "0.1.0"
|
||||
description = "AI-managed hospital hydrogen microgrid — shared environment for all clusters"
|
||||
requires-python = ">=3.12,<3.13"
|
||||
dependencies = [
|
||||
# tables, maths, plots, tests — everyone
|
||||
"numpy>=2.0",
|
||||
"pandas>=2.2",
|
||||
"pyarrow>=17", # Parquet, the agreed format for machine-made tables
|
||||
"scipy>=1.14",
|
||||
"matplotlib>=3.9",
|
||||
"pyyaml>=6.0", # YAML settings and scenario files
|
||||
"pytest>=8.3",
|
||||
# Simulations cluster
|
||||
"pvlib>=0.11", # solar array model
|
||||
"pyomo>=6.8", # optimisation problems for the benchmark controllers
|
||||
"highspy>=1.8", # the HiGHS solver
|
||||
# AI cluster
|
||||
"gymnasium>=1.0", # the environment interface
|
||||
"stable-baselines3>=2.4",# ready-made RL algorithms (pulls in torch)
|
||||
"lightgbm>=4.5", # main forecasting model
|
||||
"scikit-learn>=1.5", # forecast baselines, sensor checks
|
||||
"optuna>=4.0", # settings search
|
||||
"wandb>=0.18", # experiment logging (Weights & Biases)
|
||||
"shap>=0.46", # explanations
|
||||
"streamlit>=1.39", # dashboard
|
||||
# cluster packages from this repository (editable, see [tool.uv.sources])
|
||||
"aicontrol",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
package = false # the root is an environment, not a library
|
||||
|
||||
[tool.uv.sources]
|
||||
aicontrol = { workspace = true }
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["AICONTROL"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["AICONTROL/tests"]
|
||||
Loading…
Add table
Reference in a new issue