Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
283 commits
Select commit Hold shift + click to select a range
325bfe8
feat: add gas-price field to the Flex-context schema
Ahmad-Wahid Feb 10, 2026
9261629
apply black
Ahmad-Wahid Feb 10, 2026
9dd5802
feat: add a test case for two flexible devices with commodity
Ahmad-Wahid Feb 10, 2026
b22c6d7
use expected datatypes
Ahmad-Wahid Feb 16, 2026
c88af5c
feat: split commitments per commodity
Ahmad-Wahid Feb 16, 2026
c2341f1
feat: split commitments per commodity
Ahmad-Wahid Feb 16, 2026
88d4be0
Merge remote-tracking branch 'origin/feat/multi-commodity' into feat/…
Ahmad-Wahid Feb 16, 2026
be05a19
Revert "use expected datatypes"
Ahmad-Wahid Feb 16, 2026
f4ffd8a
feat: add a test case for different commodities
Ahmad-Wahid Feb 16, 2026
c43d2ad
fix: do not produce gas
Flix6x Feb 20, 2026
4ad215d
feat: add stock-id field in Storage and DB flex model schemas
Ahmad-Wahid Feb 25, 2026
65fc268
feat: build stock groups
Ahmad-Wahid Feb 25, 2026
ef7cf60
feat: get stock groups
Ahmad-Wahid Feb 25, 2026
55ded46
feat: add a test case for multi feed stock
Ahmad-Wahid Feb 25, 2026
0aa5b2b
feat: create a flow commitment for prefering to charge sooner devices
Ahmad-Wahid Feb 26, 2026
a48c6ba
add soc constraints for boiler
Ahmad-Wahid Feb 26, 2026
c58965f
add some assert statments
Ahmad-Wahid Feb 26, 2026
515f34a
update and add new assertions with clear explanation
Ahmad-Wahid Mar 4, 2026
4d77344
update the docstring
Ahmad-Wahid Mar 4, 2026
d740c0d
Merge remote-tracking branch 'origin/feat/multi-commodity' into feat/…
Ahmad-Wahid Mar 4, 2026
8bd859f
feat: add support for shared storage
Ahmad-Wahid Mar 5, 2026
6658803
remove the breakpoint
Ahmad-Wahid Mar 5, 2026
d500052
feat: update the test case for two devices with shared stock
Ahmad-Wahid Mar 5, 2026
09e9780
feat: add assertions with clear reasons
Ahmad-Wahid Mar 5, 2026
2becd02
refactor: move tiny-price-slope decleration out of the for loop
Ahmad-Wahid Mar 6, 2026
d9d4f94
Revert "refactor: move tiny-price-slope decleration out of the for loop"
Ahmad-Wahid Mar 6, 2026
2a22fae
refactor: move tiny-price-slope decleration out of the for loop
Ahmad-Wahid Mar 6, 2026
f38d6d8
fix: add data_key attr
Ahmad-Wahid Mar 6, 2026
007e514
add missing commodity description and it's field in ui flexmodel schema
Ahmad-Wahid Mar 6, 2026
0bff8bc
fix: add missing gas-price field in UI Flexcontext schema
Ahmad-Wahid Mar 6, 2026
d4a15eb
Add support for multi-device charging of shared storage
Ahmad-Wahid Mar 12, 2026
82f807e
fix: wrong timezone; the test relied on the preference to charge soon…
Flix6x Mar 13, 2026
128550f
feat: move preference to charge sooner and discharge later into a Sto…
Flix6x Mar 13, 2026
130b9dd
fix: test case no longer relies on arbitrage opportunity coming from …
Flix6x Mar 13, 2026
1eab828
feat: check for optimal schedule
Flix6x Mar 13, 2026
b9bc4a9
feat: prefer a full storage earlier over later
Flix6x Mar 13, 2026
57df5c3
docs: update commitment name and inline comments
Flix6x Mar 13, 2026
ce71637
docs: touch up test explanation
Flix6x Mar 14, 2026
f6183df
fix: update test case given preference for a full battery
Flix6x Mar 14, 2026
ed471a8
delete: clean up comment
Flix6x Mar 14, 2026
7611fb9
feat: model the preference to curtail later within the same StockComm…
Flix6x Mar 14, 2026
bf16e63
fix: reduce tiny price slope
Flix6x Mar 14, 2026
06c30dc
docs: delete duplicate changelog entry
Flix6x Mar 16, 2026
0125e28
Merge remote-tracking branch 'origin/main' into feat/full-soc-preference
Flix6x Mar 18, 2026
d99089b
docs: fix broken link
Flix6x Mar 18, 2026
cf01f1d
Revert "fix: reduce tiny price slope"
Flix6x Mar 18, 2026
bdbdead
fix: soc unit conversion
Flix6x Mar 18, 2026
f987706
fix: adapt test to check for 1 hour of free energy at 15-min scheduli…
Flix6x Mar 18, 2026
7acbc99
Merge remote-tracking branch 'origin/feat/full-soc-preference' into f…
Flix6x Mar 19, 2026
12cf595
Merge remote-tracking branch 'origin/main' into feat/switching-betwee…
Flix6x Mar 19, 2026
c09371a
Merge branch 'feat/switching-between-gas-and-electricity' into feat/m…
Flix6x Mar 19, 2026
534179a
style: black
Flix6x Mar 19, 2026
19dc4dc
Merge remote-tracking branch 'origin/feat/switching-between-gas-and-e…
Ahmad-Wahid Mar 19, 2026
e4c43ea
fix: check curtailment preference per distinct device
Flix6x Mar 20, 2026
b3138c3
fix: set tight tolerance for HiGHS solver
Flix6x Mar 20, 2026
8eff839
refactor: merge if-blocks
Flix6x Mar 20, 2026
b0074d4
fix: update test_two_flexible_assets_with_commodity
Flix6x Mar 20, 2026
ae23ef6
Merge remote-tracking branch 'origin/feat/multi-commodity' into feat/…
Ahmad-Wahid Mar 20, 2026
05aed7e
fix: check curtailment preference per distinct device
Flix6x Mar 20, 2026
e55f638
fix: set tight tolerance for HiGHS solver
Flix6x Mar 20, 2026
764712f
refactor: merge if-blocks
Flix6x Mar 20, 2026
9070eae
fix: use iloc
Flix6x Mar 23, 2026
857e9c1
fix: diminish tiny price slope by number of planning steps
Flix6x Mar 23, 2026
54c9c36
refactor: always diminish tiny price slope by number of planning step…
Flix6x Mar 23, 2026
158c7a0
chore: increment StorageScheduler version
Flix6x Mar 23, 2026
3df2142
Merge remote-tracking branch 'origin/feat/full-soc-preference' into f…
Flix6x Mar 23, 2026
decf794
Merge remote-tracking branch 'origin/main' into feat/multi-commodity
Flix6x Mar 23, 2026
261ed3a
Merge branch 'feat/full-soc-preference' into feat/switching-between-g…
Flix6x Mar 23, 2026
a16ec74
Merge branch 'feat/switching-between-gas-and-electricity' into feat/m…
Flix6x Mar 23, 2026
26a1993
fix: sum all devices soc contribution, and use individual device effi…
Ahmad-Wahid Mar 23, 2026
c98b178
update test case for multi feed stock
Ahmad-Wahid Mar 13, 2026
358afb8
expect to charge the battery early to see the effect of fully discharge
Ahmad-Wahid Mar 23, 2026
4932cf9
fix: update the assert statements according to the scheduler results
Ahmad-Wahid Mar 23, 2026
021543c
Merge remote-tracking branch 'origin/feat/multi-commodity' into feat/…
Ahmad-Wahid Mar 23, 2026
66e9a8b
Merge remote-tracking branch 'origin/main' into feat/switching-betwee…
Flix6x Mar 23, 2026
eda0ec7
Merge branch 'feat/switching-between-gas-and-electricity' into feat/m…
Flix6x Mar 23, 2026
b8ff719
Merge remote-tracking branch 'origin/feat/multi-commodity' into feat/…
Flix6x Mar 23, 2026
b092aa1
fix: use approximation to compare battery and heat pump costs
Ahmad-Wahid Mar 23, 2026
b20465e
update the assert statements with prefered to charge battery sooner t…
Ahmad-Wahid Mar 23, 2026
29785fa
dev: first step in resolving merge conflicts
Flix6x Mar 23, 2026
cefe507
chore: code annotation
Flix6x Mar 23, 2026
118587b
fix: not all flex-models have sensors
Flix6x Mar 23, 2026
031a6e8
fix: update the expected ev and battery costs
Ahmad-Wahid Mar 24, 2026
de4981e
fix: add device id to get costs for the given device
Ahmad-Wahid Mar 24, 2026
74b665f
fix: static method has no self
Flix6x Mar 31, 2026
fbcf2e5
delete: remove inapplicable fields for stock model
Flix6x Mar 31, 2026
4259ffa
fix: fix interpretation of test results
Flix6x Mar 31, 2026
bc3991a
fix: move initialization of ems_constraints
Flix6x Mar 31, 2026
aefaf0d
fix: resolve merge conflicts on _build_soc_schedule, copied from Ahmad
Flix6x Mar 31, 2026
63b6bd7
fix: remove redundant code block
Flix6x Mar 31, 2026
0be435f
dev: use "state-of-charge" key instead of "sensor" key for stock models
Flix6x Mar 31, 2026
123f543
fix: skip StockCommitment for device models that outsource their stoc…
Flix6x Mar 31, 2026
f02e2ee
fix: old flex models that describe a device that serves both as a fee…
Flix6x Mar 31, 2026
5fb576e
fix: model stock devices using the state-of-charge field instead of t…
Flix6x Mar 31, 2026
cb110a9
fix: identify asset to merge with db flex-model
Flix6x Mar 31, 2026
b5bb77e
fix: validation
Flix6x Mar 31, 2026
816eda7
fix: flex-model setup in test
Flix6x Mar 31, 2026
1d5433f
fix: create stock group
Ahmad-Wahid Apr 4, 2026
eeffbf3
use soc-sensor in case of missing power sensor and also correct stock…
Ahmad-Wahid Apr 4, 2026
d8cab12
fix: create stock model for a model which has itself stock
Ahmad-Wahid Apr 5, 2026
ba7b433
update the assert statements
Ahmad-Wahid Apr 5, 2026
ea96b53
fix: merge conflicts
Ahmad-Wahid Apr 5, 2026
a229502
remove stock-id field
Ahmad-Wahid Apr 5, 2026
8190044
fix: correct the stock groups
Ahmad-Wahid Apr 7, 2026
177154c
refactor: remove unneccessary test function
Ahmad-Wahid Apr 9, 2026
a110f0e
fix: shared soc-gain, soc-usage, soc-minima and soc-maxima
Flix6x Apr 9, 2026
c7679ef
fix: shared StockCommitment for preferring a full SoC
Flix6x Apr 9, 2026
53d27c7
dev: todo
Flix6x Apr 9, 2026
69b5e27
dev: add "test" test case
Flix6x Apr 9, 2026
1ec0b2c
Merge remote-tracking branch 'origin/main' into feat/switching-betwee…
Flix6x Apr 24, 2026
49df3a6
feat: rewrite test to not rely on multi-commodity
Flix6x Apr 24, 2026
a518437
feat: rewrite test to prove that only when both HPs share a single co…
Flix6x Apr 24, 2026
4258636
fix: do not coerce device_group into a time series
Flix6x Apr 24, 2026
b4f8b2b
docs: changelog entry
Flix6x Apr 24, 2026
6e3a5a4
docs: pretty_print is not specifically for FlowCommitments
Flix6x Apr 24, 2026
7eb1319
docs: add (back) inline dev notes
Flix6x Apr 24, 2026
a298dc7
feat: strengthen asserts
Flix6x Apr 24, 2026
7f15c13
feat: add check for exact electricity costs expected
Flix6x Apr 24, 2026
ffd0d86
fix: sum over electricity costs; and move to StockCommitment to model…
Flix6x Apr 24, 2026
a33790a
fix: replace vague assert with explicit asserts
Flix6x Apr 24, 2026
6b6a474
docs: lose confusing comment
Flix6x Apr 24, 2026
70e1af6
fix: backwards compatibility in case no device is specified;
Flix6x Apr 24, 2026
792d2df
fix: when device is present but device_group is absent, fall back to …
Flix6x Apr 25, 2026
a9174a5
dev: update expectations of how costs are shared between EV and battery
Flix6x Apr 26, 2026
b95a594
fix commodity-level commitments by grouping devices and aligning devi…
Ahmad-Wahid Apr 28, 2026
864a98f
fix commodity-level commitments by grouping devices and aligning devi…
Ahmad-Wahid Apr 30, 2026
c885b04
update the test cases for net commodity consumption and production
Ahmad-Wahid Apr 30, 2026
55075d5
fix: merge conflicts
Ahmad-Wahid Apr 30, 2026
0360d01
dev: Support commodity-specific prices and site capacities in storage…
Ahmad-Wahid May 14, 2026
775a52d
dev: Add commodity-specific flex-context schema
Ahmad-Wahid May 14, 2026
b9aece4
dev: Add dynamic commodity prices and split flex-context settings to …
Ahmad-Wahid May 14, 2026
433fe17
feat: create a shared schema for flex-context and commodity-context
Ahmad-Wahid May 26, 2026
85a105f
update the test case to have inflexible-devices-sensors for each comm…
Ahmad-Wahid May 26, 2026
4e5aee1
refactor: loop over flex-context fields and choose all fields except …
Ahmad-Wahid May 26, 2026
4a6910a
fix: add inflexible-device-sensors to the gas commodity model
Ahmad-Wahid May 26, 2026
5bbb515
Merge remote-tracking branch 'origin/feat/multi-commodity' into feat/…
Ahmad-Wahid May 26, 2026
7a0e7fe
fix: use net energy costs instead of individual device costs
Ahmad-Wahid May 26, 2026
c5351e0
fix: comment out the buggy lines
Ahmad-Wahid May 26, 2026
2105d28
Merge remote-tracking branch 'origin/feat/multi-feed-stock' into dev/…
Ahmad-Wahid May 26, 2026
e9c2681
fix: remove self
Ahmad-Wahid May 26, 2026
683fd57
feat: coupling groups for CHP
Flix6x May 29, 2026
391a5d7
feat: test factory model
Flix6x May 29, 2026
7deff15
Merge remote-tracking branch 'origin/main' into feat/switching-betwee…
Flix6x Jun 3, 2026
3e20ce0
Merge remote-tracking branch 'origin/feat/switching-between-gas-and-e…
Flix6x Jun 3, 2026
84ed22b
Merge remote-tracking branch 'origin/feat/multi-commodity' into feat/…
Flix6x Jun 3, 2026
d2b1812
fix: merge conflicts
Flix6x Jun 3, 2026
92c3324
Merge remote-tracking branch 'origin/feat/multi-feed-stock' into dev/…
Flix6x Jun 3, 2026
d5d241b
Merge remote-tracking branch 'origin/dev/split-flexcontext-by-commodi…
Flix6x Jun 3, 2026
afba390
Merge remote-tracking branch 'origin/main' into feat/multi-commodity
Flix6x Jun 3, 2026
2d662b0
Merge remote-tracking branch 'origin/feat/multi-commodity' into feat/…
Flix6x Jun 3, 2026
9b0b9ac
Merge remote-tracking branch 'origin/feat/multi-feed-stock' into dev/…
Flix6x Jun 3, 2026
e84084d
Merge remote-tracking branch 'origin/dev/split-flexcontext-by-commodi…
Flix6x Jun 3, 2026
a7bbe45
refactor: make variables for gas boiler and e-heater capacities
Flix6x Jun 3, 2026
9d090bb
docs: clarify e-heater efficiency assumption
Flix6x Jun 3, 2026
3af4c52
feat: add scenario with merit order: gas boiler ≪ e-heater ≪ CHP
Flix6x Jun 3, 2026
e4f8fc9
feat: support flex-model coupling constraint in StorageScheduler
Flix6x Jun 3, 2026
1662283
docs: clarify calculation of coupling coefficients
Flix6x Jun 3, 2026
0d92dc4
feat: invert interpretation of coefficients to better match thermal a…
Flix6x Jun 3, 2026
a12032f
feat: support multiple inputs to coupling point
Flix6x Jun 3, 2026
ee33e27
feat: stop collapsing the heat buffer and steam node in the factory test
Flix6x Jun 3, 2026
094cd5d
tests/planning: align storage CHP coupling test with current coeffici…
Flix6x Jun 3, 2026
d119391
planning/coupling: infer internal sign from directional capacities
Flix6x Jun 3, 2026
a86f6a7
tests/planning: clarify signed internal CHP coefficients in storage d…
Flix6x Jun 3, 2026
7e60e2d
fix: update the test cases according device level costs
Ahmad-Wahid Jun 4, 2026
6c82e1c
docs/scheduling: add COMMODITY and GAS_PRICE metadata field documenta…
Ahmad-Wahid Jun 4, 2026
6fea3e7
Merge remote-tracking branch 'origin/feat/multi-commodity' into feat/…
Ahmad-Wahid Jun 4, 2026
5e76191
fix: add device-model in groups if it's missing
Ahmad-Wahid Jun 4, 2026
55dbced
fix: restore SOC constraints and state-of-charge handling broken by m…
Ahmad-Wahid Jun 4, 2026
9d6986d
Merge remote-tracking branch 'origin/feat/multi-feed-stock' into dev/…
Ahmad-Wahid Jun 4, 2026
5d02af4
fix: fall back to deprecated price fields
Flix6x Jun 8, 2026
abb41ff
fix: typo
Flix6x Jun 8, 2026
134563e
feat: store commitment costs on job meta
Flix6x Jun 8, 2026
dba828e
refactor: clarify which job is which
Flix6x Jun 8, 2026
02cfbb6
fix: update test expectation: the battery could save more?
Flix6x Jun 8, 2026
b7b21c2
dev: add todo
Flix6x Jun 8, 2026
193bca4
fix: price window should match scheduling window
Flix6x Jun 8, 2026
76b5fca
fix: comment out unreasoned check
Flix6x Jun 8, 2026
b13e239
fix: update test expectation; apparently the battery could save more?
Flix6x Jun 8, 2026
478c348
dev: add todo
Flix6x Jun 8, 2026
1dd6e80
chore: flake8
Flix6x Jun 8, 2026
e4d9c67
dev: exclude commodities field from flex-context schema referencing a…
Flix6x Jun 8, 2026
48bfe1c
fix: only save commitment costs on job if we have a job to save it on
Flix6x Jun 8, 2026
455f495
fix: inflexible devices are electricity devices by default
Flix6x Jun 8, 2026
e19984c
delete: no more need for backwards-compatibility of the temporary gas…
Flix6x Jun 8, 2026
e17a570
Merge remote-tracking branch 'origin/dev/split-flexcontext-by-commodi…
Flix6x Jun 8, 2026
bc6a88f
chore: black
Flix6x Jun 8, 2026
c1c7d96
chore: black
Flix6x Jun 8, 2026
a7773a8
fix: optional dict key
Flix6x Jun 8, 2026
ae42d6e
Merge remote-tracking branch 'origin/dev/split-flexcontext-by-commodi…
Flix6x Jun 8, 2026
229349d
fix: keep ems-constraints and fix the test cases (#2233)
Ahmad-Wahid Jun 12, 2026
bf09a81
fix: keep ems-constraints and fix the test cases (#2233)
Ahmad-Wahid Jun 12, 2026
926941a
fix: only raise in case of multiple EMS constraint DataFrames
Flix6x Jun 12, 2026
9f5d342
Merge remote-tracking branch 'origin/dev/split-flexcontext-by-commodi…
Flix6x Jun 12, 2026
953d3f1
chore: the wait for https://github.com/marshmallow-code/apispec/pull/…
Flix6x Jun 12, 2026
c6b8223
feat: allow any commodity, with electricity and gas serving as examples
Flix6x Jun 12, 2026
b706f79
delete: remove unreleased flex-context field for gas price
Flix6x Jun 12, 2026
b5f2c27
fix: add all relaxation fields to the list of fields to ignore when m…
Flix6x Jun 12, 2026
bd972aa
delete: gas_price is no longer a field (remove reference to unrelease…
Flix6x Jun 12, 2026
bcd54de
delete: just treat the whole old flex-context as the electricity flex…
Flix6x Jun 12, 2026
ed17a93
chore: update openapi-specs.json
Flix6x Jun 12, 2026
28d4529
Merge branch 'dev/split-flexcontext-by-commodity' into feat/chp
Flix6x Jun 12, 2026
afa5eb5
feat: list the commodity field first rather than last
Flix6x Jun 12, 2026
bf22eb8
feat: commodity is a field in both flex-model and flex-context
Flix6x Jun 12, 2026
710bf22
feat: flex-model commodity can also be more than just electricity and…
Flix6x Jun 12, 2026
410bb4c
docs: remove mention of gas-price field
Flix6x Jun 12, 2026
e994767
docs: adjust scheduling section for multi-commodity
Flix6x Jun 12, 2026
12ff212
docs: adjust field descriptions for multi-commodity
Flix6x Jun 12, 2026
c664e2a
docs: add type annotation
Flix6x Jun 16, 2026
fd66372
fix: keep track of inflexible device sensors per commodity, too
Flix6x Jun 16, 2026
7d99f1a
refactor: place all group args at the end
Flix6x Jun 16, 2026
73f8004
dev: add todos for checking prices
Flix6x Jun 16, 2026
e3f7cbd
fix: test should exclude COMMODITY_FLEX_CONTEXT and COMMODITY_FLEX_MO…
Flix6x Jun 30, 2026
2410764
merge: bring in origin/main (multi-commodity hardening #2271, soft-co…
Flix6x Jul 10, 2026
ecf10b0
feat: require an unambiguous flow direction for coupled devices
Flix6x Jul 10, 2026
79d45fd
feat: balance internal commodity nodes with first-class balance groups
Flix6x Jul 10, 2026
71ffaa8
docs: point balance-groups changelog entry at PR #2279
Flix6x Jul 10, 2026
25ebe88
fix: balance internal commodity nodes on power flows, not stock-side …
Flix6x Jul 10, 2026
78ef050
Merge main (typed DeviceInventory, #2321) into feat/chp
Flix6x Jul 16, 2026
e3cb732
Merge feat/chp (rebased on main's typed DeviceInventory, #2321) into …
Flix6x Jul 16, 2026
df2fd03
docs: point the balance-groups changelog entry at PR #2289
Flix6x Jul 16, 2026
854fd4c
Merge remote-tracking branch 'origin/main' into chore/merge-main-into…
Flix6x Jul 17, 2026
8a7e528
fix: preserve overlapping stock-group membership for commodity conver…
Flix6x Jul 17, 2026
5d74d13
Merge remote-tracking branch 'origin/feat/chp' into chore/merge-chp-i…
Flix6x Jul 17, 2026
ba9d7bd
fix: keep internal-node detection working alongside smart commodity d…
Flix6x Jul 17, 2026
f9a43e1
style: fix flake8 F811 and black formatting in test_storage.py
Flix6x Jul 18, 2026
96b7f7c
docs: add a converter + internal-node example flex-model
Flix6x Jul 18, 2026
592de3f
Merge origin/main into feat/chp
Flix6x Jul 19, 2026
85c9cd4
Merge updated feat/chp into feat/internal-commodity-balance
Flix6x Jul 19, 2026
385f942
style: drop duplicate GenericAsset import in test_storage.py (F811)
Flix6x Jul 19, 2026
f7d87eb
fix(schema): support coupling in db-stored flex-models and reject bla…
Flix6x Jul 19, 2026
c0eefc5
fix(scheduling): treat capacity-only commodity as grid-connected, not…
Flix6x Jul 19, 2026
266e92e
fix(ui): add coupling fields to UI_FLEX_MODEL_SCHEMA
Flix6x Jul 19, 2026
d30d070
Merge branch 'feat/chp' into feat/internal-commodity-balance
Flix6x Jul 19, 2026
1f514aa
feat(scheduling): smart-default coupled device flow direction
Flix6x Jul 19, 2026
b0d422d
Merge commit '1f514aa5e2312f76751637ccbbcf4e08241ad6a4' into HEAD
Flix6x Jul 19, 2026
3dfa23b
Merge origin/main into feat/chp
Flix6x Aug 2, 2026
3b727e1
Merge updated feat/chp into feat/internal-commodity-balance
Flix6x Aug 2, 2026
b6257e7
Merge main into feat/chp, and teach the direct HiGHS backend about co…
Flix6x Aug 3, 2026
1db34e3
Merge feat/chp, and teach the direct HiGHS backend about balance groups
Flix6x Aug 3, 2026
8c8be35
style: reflow coupling docstrings to break only after punctuation
Flix6x Aug 3, 2026
37ec0ae
review: use timezone-aware datetimes in the coupling schema tests
Flix6x Aug 3, 2026
a4bff51
Merge commit '74806667f' into HEAD
Flix6x Aug 3, 2026
7205ba9
Merge commit 'a4bff51d2c013080ff0b94a35178edf0e97b0ebc' into HEAD
Flix6x Aug 3, 2026
9465ae6
Merge remote-tracking branch 'origin/main' into HEAD
Flix6x Aug 3, 2026
27cf444
Accept arbitrary commodities in a stored flex-model
Flix6x Aug 3, 2026
8179fd9
docs: use inflexible-consumption in the internal-node guidance
Flix6x Aug 4, 2026
1757f6e
review: drop a stray file, clarify the tutorial, quieten the internal…
Flix6x Aug 4, 2026
0b96299
review: reflow this PR's docstrings and comments
Flix6x Aug 4, 2026
bcdea9e
docs: give inflexible devices in the flex-model their own section
Flix6x Aug 4, 2026
7b92f1f
docs: hint at larger sites rather than implying we walked through one
Flix6x Aug 4, 2026
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
1 change: 1 addition & 0 deletions documentation/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ New features
* In the UI, the flex-context editor supports editing commitments (name, baseline and deviation prices, each accepting a fixed value or a sensor), also within each commodity context; the commitment's commodity follows the commodity tab being edited, and new commitments start with zero-valued baseline and prices [see `PR #2287 <https://www.github.com/FlexMeasures/flexmeasures/pull/2287>`_]
* A commitment in the flex-context now requires a ``baseline`` and at least one deviation price (``up-price`` and/or ``down-price``), as already documented; commitment costs are reported in the scheduling results under the user-given name (with a ``(custom)`` suffix in the rare case the name collides with a scheduler-internal commitment name) [see `PR #2287 <https://www.github.com/FlexMeasures/flexmeasures/pull/2287>`_]
* Support commodity-converting devices, such as a CHP, e-boiler or heat pump, by describing each of the device's commodity ports as a flex-model entry sharing one ``coupling`` group with fixed flow ratios (``coupling-coefficient``) [see `PR #2218 <https://www.github.com/FlexMeasures/flexmeasures/pull/2218>`_]
* Commodities without energy prices in the flex-context (e.g. a heat or steam network without a grid connection) are scheduled as internal nodes whose devices must balance each other at every time step [see `PR #2289 <https://www.github.com/FlexMeasures/flexmeasures/pull/2289>`_]
* Commodity contexts that omit grid-connection fields (prices and site capacities) now get smart defaults instead of failing or silently leaving the grid unconstrained — for instance, a bare ``{"commodity": "gas"}`` is treated as having no grid connection; see :ref:`commodity_context_defaults` for the full rules [see `PR #2272 <https://www.github.com/FlexMeasures/flexmeasures/pull/2272>`_]
* CLI support for adding/editing account attributes [see `PR #2242 <https://www.github.com/FlexMeasures/flexmeasures/pull/2242>`_]
* Improve chart axis domain for event values not around zero, with a per-sub-chart ``y-axis`` option in ``sensors_to_show`` (default ``zero``, which pads the axis out to include zero) that can be set to ``data`` to fit a sub-chart's y-axis to the values shown, to an explicit ``[min, max]`` domain that the axis will cover at least (expanding to fit data beyond it), or to a strict ``{"min": min, "max": max}`` domain that the axis will never exceed (clamping data beyond it, with a warning when that happens), editable from the graph editor [see `PR #2244 <https://www.github.com/FlexMeasures/flexmeasures/pull/2244>`_]
Expand Down
12 changes: 12 additions & 0 deletions documentation/features/scheduling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ The ``flex-context`` is independent of the type of flexible device that is optim
With the flexibility context, we aim to describe the system in which the flexible assets operate, such as its physical and contractual limitations.
For multi-commodity scheduling problems, the flex-context can be defined separately per commodity (e.g. electricity and gas). See :ref:`tut_multi_commodity` for a hands-on example.

A *non-electricity* commodity that defines no energy prices and no capacity (grid-connection) fields in the flex-context (e.g. a heat or steam network without a grid connection) is treated as an internal node:
its devices must balance each other at every time step, so everything produced into the node is consumed from it within the same time step.
Electricity is the exception: it is always assumed to be grid-connected, so electricity without a price raises an error rather than becoming an internal node.
Devices that convert between commodities (such as a CHP unit, gas boiler or electric heater) are described in the flex-model, one entry per commodity port, tied together by a ``coupling`` group. See :ref:`tut_converters` for a worked example flex-model.

Fields can have fixed values, but some fields can also point to sensors, so they will always represent the dynamics of the asset's environment (as long as that sensor has current data).
The full list of flex-context fields follows below.
For more details on the possible formats for field values, see :ref:`variable_quantities`.
Expand Down Expand Up @@ -361,8 +366,15 @@ The sensor-referenced form is convenient when you pass the whole flex-model in o

Here, the battery and PV installation may each individually schedule up to 2 kW, but their combined power flowing through the shared inverter is hard-limited to 2.5 kW.

.. _inflexible_devices_in_flex_model:

Inflexible devices in the flex-model
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Inflexible (measured) devices can be modelled in the flex-model too — for example, an unschedulable base load. To do so, model the inflexible device as its own asset and give its flex-model entry a single ``inflexible-consumption`` or ``inflexible-production`` reference to the sensor recording its power (the field name sets the sign convention, and source filters may be added). Such an entry carries no schedulable-device fields; it simply declares a fixed device whose power is accounted for. Like any device entry, it may set a ``commodity`` (defaulting to electricity), and its fixed power is then netted into that commodity's grid connection.

There are two places to declare an inflexible device, and the choice is about *where* it belongs rather than *what* it does. Listing its sensor in the flex-context's ``inflexible-consumption``/``inflexible-production`` fields describes plain site base load, which is a property of the connection. Giving it its own flex-model entry describes a device that sits somewhere specific in the asset tree — under a particular inverter, feeder or commodity — which is a property of the device. Both net the same fixed power into the grid connection.

The ``group`` field is optional on such an entry. Without it, the device is simply accounted for under the grid connection (just like listing its sensor in the flex-context's ``inflexible-consumption``/``inflexible-production`` fields, only declared on the asset instead). With it, the device *also* joins that group through the ordinary ``group`` field, exactly like a flexible member (the group's own flex-model entry, defining its capacities, must still be present), so that its fixed load or supply additionally counts towards the group's intermediate power constraint — for example, a base load sitting behind the same inverter or feeder as a battery.


Expand Down
41 changes: 41 additions & 0 deletions documentation/tut/multi-commodity.rst
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,47 @@ The commitment-cost result keeps these as separate entries — ``electricity net

.. note:: This same pattern extends to more devices and more commodities. Add further entries to the ``flex-model`` list (each with its ``commodity``) and a matching entry in the ``flex-context`` ``commodities`` list. As long as all commodities share one currency, FlexMeasures optimises them together and reports each commodity's cost on its own.


.. _tut_converters:

Converters between commodities
==============================

A **converter** turns one commodity into another — a CHP unit (gas → electricity + steam), a gas boiler (gas → heat) or an electric heater (electricity → heat).
In the ``flex-model`` a converter is not a single device but **one entry per commodity port**, tied together by a shared ``coupling`` name.
The ``coupling-coefficient`` on each port fixes the conversion ratio relative to the shared coupling variable, so the ports always move together.

For example, a CHP that turns gas into steam and electricity:

.. code-block:: json

[
{"sensor": 6, "commodity": "gas", "coupling": "chp", "coupling-coefficient": 1.0, "consumption-capacity": "20 kW"},
{"sensor": 7, "commodity": "steam", "coupling": "chp", "coupling-coefficient": 0.5, "production-capacity": "10 kW"},
{"sensor": 8, "commodity": "electricity", "coupling": "chp", "coupling-coefficient": 0.3, "production-capacity": "6 kW"}
]

Here each kW of gas input produces 0.5 kW of steam and 0.3 kW of electricity.
Each port gives exactly one directional capacity, and that is what marks its direction:
the gas port only consumes, and the steam and electricity ports only produce.
The opposite direction defaults to zero, so it does not need to be written out.

Note that the capacities agree with the coefficients:
20 kW of gas is the most the unit can burn, which is what caps steam at 10 kW and electricity at 6 kW.

**Internal nodes.** A *non-electricity* commodity that lists no energy price and no capacity (grid-connection) field in the ``flex-context`` (e.g. a steam or heat network with no grid connection) is treated as an **internal node**: its devices must balance each other at every time step, so everything converters produce into the node must be consumed from it within the same step.
You can still create a flex-context entry for such a commodity to define ``inflexible-consumption`` and/or ``inflexible-production`` (its fixed demand and supply) without FlexMeasures assuming a grid connection is available.
Alternatively, omit it from the ``flex-context`` entirely (fixed demand and supply can also be set up with flex-models of their own, see :ref:`inflexible_devices_in_flex_model`).
Electricity is the exception: it is always assumed to be grid-connected, so missing electricity prices raise an error rather than turning electricity into an internal node.

Coupled converters and internal nodes compose, which is what lets these two features reach beyond the single unit shown above.
Chain a few of them and you can describe an industrial site: converters feeding internal nodes, those nodes feeding further converters, and only the priced commodities meeting the grid.
Nothing extra is needed to get there — no new fields, just more entries of the kinds already shown.
We do not walk through such a site here, and yours will not look like anyone else's, but the pieces are the ones on this page.

**Pricing what flows through a node.** An internal node's flows sum to zero by construction, so a commitment scoped to the node itself would bind nothing: there is no net flow to price.
To put a cost on throughput — pipe wear, say, or a conversion levy — scope the commitment to the devices *producing* into the node, whose summed flow is what actually passes through it.

We hope this demonstration helped to illustrate multi-commodity scheduling.
To revisit scheduling several devices that share a single commodity and stock, head back to :ref:`tut_multi_feed_storage`.
Next, in :ref:`tut_toy_schedule_process`, we'll turn to something different: the optimal timing of processes with fixed energy work and duration.
15 changes: 15 additions & 0 deletions flexmeasures/data/models/planning/highspy_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ def device_scheduler_highspy( # noqa C901
ems_constraint_groups: list[list[int]] | None = None,
device_power_bands: list[list[tuple[float, float]] | None] | None = None,
coupling_groups: dict[str, list[tuple[int, float]]] | None = None,
balance_groups: dict[str, list[int]] | None = None,
) -> tuple[list[pd.Series], float, HighspySolverResults, HighspyModel]:
"""Direct HiGHS implementation of ``device_scheduler``.

Expand Down Expand Up @@ -255,6 +256,7 @@ def device_scheduler_highspy( # noqa C901
ems_constraint_groups=ems_constraint_groups,
device_power_bands=device_power_bands,
coupling_groups=coupling_groups,
balance_groups=balance_groups,
)

# Local aliases, so that the model below reads as it did before the (solver-agnostic)
Expand All @@ -272,6 +274,7 @@ def device_scheduler_highspy( # noqa C901
Md, Mc = problem.Md, problem.Mc
band_lookup = problem.band_lookup
coupling_device_specs = problem.coupling_device_specs
balance_group_specs = problem.balance_group_specs
_initial_stock_of = problem.initial_stock_of

# ---------------------------------------------------------------
Expand Down Expand Up @@ -649,6 +652,18 @@ def _add_commitment_rows(c, quantity, jj, lb, ub, devices, is_stock) -> None:
np.tile([1.0, -float(coeff)], (T, 1)),
)

# node_balance_constraints: an internal commodity node stores nothing,
# so everything produced into it is consumed from it within the same time step,
# i.e. sum_d(ems_power[d, j]) == 0.
# Groups with no devices were dropped during preparation, so they add no row here,
# matching the Pyomo path's Constraint.Skip for that case.
for devices_in_node in balance_group_specs:
js = np.arange(T)
idx = np.column_stack([col_ems + int(d) * T + js for d in devices_in_node])
rows.add_uniform_rows(
np.zeros(T), np.zeros(T), idx, np.ones_like(idx, dtype=float)
)

# ---------------------------------------------------------------
# Build and solve the HiGHS model
# ---------------------------------------------------------------
Expand Down
34 changes: 34 additions & 0 deletions flexmeasures/data/models/planning/linear_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def device_scheduler( # noqa C901
initial_stock: float | list[float] = 0,
stock_groups: dict[int, list[int]] | None = None,
coupling_groups: dict[str, list[tuple[int, float]]] | None = None,
balance_groups: dict[str, list[int]] | None = None,
ems_constraint_groups: list[list[int]] | None = None,
device_power_bands: list[list[tuple[float, float]] | None] | None = None,
) -> tuple[list[pd.Series], float, SolverResults, ConcreteModel]:
Expand Down Expand Up @@ -188,6 +189,16 @@ def device_scheduler( # noqa C901
binary variables (one per device per band per time step). Use None (per device
or for the whole argument) for devices without band restrictions.

:param balance_groups: Flow-balance constraints for internal commodity nodes (e.g. a heat or steam network without a grid connection).
Each entry maps a node name to a list of device indices whose commodity-side flows must balance at every time step:
``sum_d(ems_power[d, j]) == 0``.
In other words, everything produced into the node is consumed from it within the same time step;
the node itself stores nothing.
Derivative efficiencies and stock deltas describe each device's own stock-side conversion,
and do not enter this commodity-side balance.
To add storage to a node, include a storage device in the group:
its flow absorbs the imbalance, and its stock is bounded by its own device constraints.

Potentially deprecated arguments:
commitment_quantities: amounts of flow specified in commitments (both previously ordered and newly requested)
- e.g. in MW or boxes/h
Expand Down Expand Up @@ -237,6 +248,7 @@ def device_scheduler( # noqa C901
ems_constraint_groups=ems_constraint_groups,
device_power_bands=device_power_bands,
coupling_groups=coupling_groups,
balance_groups=balance_groups,
)

# Local aliases,
Expand All @@ -255,6 +267,7 @@ def device_scheduler( # noqa C901
band_lookup = problem.band_lookup
_initial_stock_of = problem.initial_stock_of
coupling_device_specs = problem.coupling_device_specs
balance_group_specs = problem.balance_group_specs

# Add indices for devices (d), datetimes (j) and commitments (c)
model.d = RangeSet(0, len(device_constraints) - 1, doc="Set of devices")
Expand Down Expand Up @@ -675,6 +688,27 @@ def flow_coupling_rule(m, c, j):
model.coupling_device_range, model.j, rule=flow_coupling_rule
)

if balance_group_specs:
model.balance_group_range = RangeSet(0, len(balance_group_specs) - 1)

def node_balance_rule(m, b, j):
"""Balance the power flows of an internal commodity node at every time step.

Everything produced into the node must be consumed from it within the same time step.
The balance sums the devices' commodity-side flows (ems_power);
derivative efficiencies and stock deltas describe each device's own stock-side conversion (e.g. of a shared buffer),
and do not enter the commodity balance.
"""
return (
0,
sum(m.ems_power[d, j] for d in balance_group_specs[b]),
0,
)

model.node_balance_constraints = Constraint(
model.balance_group_range, model.j, rule=node_balance_rule
)

# Power bands (S2 operation modes): a banded device must operate within
# exactly one of its declared signed power ranges at every time step.
# Which band it runs in (per time step) is a free binary decision variable;
Expand Down
12 changes: 12 additions & 0 deletions flexmeasures/data/models/planning/scheduling_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ class SchedulingProblem:
#: (group index, device index, coefficient) triples for hard flow-coupling constraints
coupling_device_specs: list[tuple[int, int, float]]

#: device lists of the balance groups (internal commodity nodes), empty groups dropped
balance_group_specs: list[list[int]]

initial_stock: float | list[float]

#: The commitments as passed in, before the sub-commitment split.
Expand Down Expand Up @@ -272,6 +275,7 @@ def prepare_scheduling_problem( # noqa C901
ems_constraint_groups: list[list[int]] | None = None,
device_power_bands: list[list[tuple[float, float]] | None] | None = None,
coupling_groups: dict[str, list[tuple[int, float]]] | None = None,
balance_groups: dict[str, list[int]] | None = None,
) -> SchedulingProblem:
"""Normalise and validate ``device_scheduler``'s arguments into a SchedulingProblem.

Expand Down Expand Up @@ -369,6 +373,13 @@ def prepare_scheduling_problem( # noqa C901
for d_idx, coeff in members:
coupling_device_specs.append((g_idx, d_idx, coeff))

# Collect the device lists of the balance groups (internal commodity nodes).
balance_group_specs: list[list[int]] = []
if balance_groups:
balance_group_specs = [
list(devices) for devices in balance_groups.values() if devices
]

# Move commitments from old structure to new
if commitments is None:
commitments = []
Expand Down Expand Up @@ -546,6 +557,7 @@ def prepare_scheduling_problem( # noqa C901
Mc=Mc,
band_lookup=band_lookup,
coupling_device_specs=coupling_device_specs,
balance_group_specs=balance_group_specs,
initial_stock=initial_stock,
original_commitments=original_commitments,
)
Expand Down
Loading
Loading