I am currently using MUIO 5.5 to model a power system with high VRE penetration targets. To align with IEA integration phases, I need to enforce a mandatory ratio between Solar PV installation and BESS deployment starting from a specific year (e.g., 2029).
Specifically, I want to ensure that for every 100 MW of PV installed, the model is forced to install at least 25 MW / 100 MWh of BESS (4-hour duration) to guarantee grid flexibility.
However, I have identified what seems to be a structural limitation in the model’s standard formulation regarding User-Defined Constraints (UDCs):
Technology vs. Storage: In MUIO 5.5, UDCs can easily link two technologies (e.g., TotalCapacityAnnual of PV vs. TotalCapacityAnnual of an Inverter). But UDCs do not seem to support a direct link between a Technology and a Storage entity (the energy reservoir).
Power vs. Energy Decoupling: Even if I use a “Proxy Technology” to represent the BESS inverter (MW) and link it to the PV via a UDC, there is no native parameter in the core OSeMOSYS formulation (within MUIO) to strictly lock the NewStorageCapacity (MWh) to that Technology’s capacity.
Optimization Loophole: Without a formal link, the solver could theoretically fulfill the PV/Inverter capacity ratio (MW) while investing in a near-zero storage volume (MWh) to minimize costs, thus bypassing the stability requirement.
My questions to the community:
Am I correct in concluding that MUIO 5.5 (standard core) cannot natively enforce a ratio between a Technology Capacity (MW) and a Storage Volume (MWh) via UDCs?
Aside from merging costs into a “dummy” technology (which loses the inter-temporal dispatch / SOC tracking benefits of the Storage object), is there a known way to bind these two variables?
You are correct, that that specific constraint isn’t possible with the UDCs available in MUIO. However, I think that this isn’t necessary for including storage in OSeMOSYS.
I see two options/use cases for what you are asking:
You are adding a storage cost to PV and not using the model to optimize the storage size/operation. If you do this the PV/Storage ratio is exogenous to the model and you can simply add the full cost of the storage system to the cost of PV and the optimization works as intended. Since the storage is not optimized in the model there is no need to track the storage in the model and simply adding it externally is good enough.
You are trying to optimize the size/operation of the storage system in connection to the PV system. In this case you want the model to choose the size of storage and linking it directly to the installed PV defeats the purpose of modeling storage. Since we want to optimize storage size and operation one should NOT implement this constraint to allow the model to make the choice of how much storage to include.
In both cases above one does not need to link these parameters for the model to work as intended. Modelling storage is challenging and shouldn’t be taken lightly (see http://dx.doi.org/10.2139/ssrn.5584535 for some of the complexity of modelling storage in OSeMOSYS).
Yes, you’re correct about the limitation. In standard OSeMOSYS:
- Technology (MW) and storage (MWh) are separate decision spaces
-UDC (User-defined constraints) typically operate on TotalCapacityAnnual(technologies) and Activity variables.
-But storage capacity variables for instance, storageCapacity, NewStorageCapacity are not always exposed in UDC structures in tools like MUIO and are not directly linkable to technology capacity via standard parameters.
This means your conclusion is correct: there is no native way to enforce a strict linkage between MW and MWh ratio via UDC in the standard core.
This limitation exists and provides important insight. This is not just a user-interface (UI) issue; its structural:
-Storage in OSeMOSYS is modelled as an energy reservoir (MWh) with charging/discharging flows (MW) coming from technology. But the power component is implicit (via technologies), and the energy component (MW) is explicit (storage object).
There is no built-in coupling constraints, like storageCapacity proportional to technology capacity. This decoupling is intentional for flexibility, but it creates exactly your loophole.
(Explanation for loophole
This loophole means that the model has a structural gap that allows the optimisation to avoid the intended constraint. For instance, in OSeMOSYS modelling, you want PV capacity (MW) to be linked to storage capacity (MWh), but because the model doesn’t enforce that link, it creates a loophole. This means that the solver can install the required PV (MW) to satisfy your UDC constraint (MW-based) but install very little or zero storage (MWh); this is technically valid but physically unrealistic. For simplicity, consider a rule stating that “every car must have an engine”, but without specifying the engine size or fuel; this allows a manufacturer to install a tiny, useless engine just to comply with the rule. This is what a loophole is.
Therefore, your optimisation loophole concern is absolutely right. The server can satisfy MW constraints while minimising MWh. This behaviour is observed when installing a PV+minimal inverter proxy, keeping storage volume approximately zero but still satisfying UDC. This is a well-known modelling pitfall in high-VRE studies.
Practical ways to enforce PV-BESS Coupling
Option 1
Extend the core model (best technical solution)
-You could modify the the model equations
Introduce a custom constraint like NewStorageCapacity (y) greater than or equal or proportional to NewCapacity PV (y).
Where the proportionality is equal to 1 (for MW proportional to MWh), or adjust to 0.25 x 4 h equal to 1. This applies for greater than or equal to 2029. This is the cleanest and most correct solution, but it requires editing the OSeMOSYS code, e.g., in GNU MathProg/Pyomo.
Option B
Proxy coupling via linked technologies
What you described is close but incomplete, but you can improve it:
First, create two technologies: BESS-power (MW) and BESS-Energy-proxy (fake tech representing MWh).
Secondly, enforce ratios via UDCs by making sure that PV is proportional to BESS power (MW ratio) and BESS power is proportional to BESS energy proxy (fixed duration, e.g., 4 hours). Use the energy proxy capacity that is a product of capacity, power and 4.
Thirdly, it is to tie a proxy to storage (indirectly) by assigning a very high penalty cost if storage capacity is less great than the proxy or force storage to be the feasible sink/source. This is still indirect but can be gamed unless tightly constrained.
Option C
Consider minimum storage constraints. This is simpler but less precise. Instead of strict coupling, make the storage capacity by year greater or equal to the product of beta and total PV by year. This is easier to implement if the storage variables are accessible, though it’s not tied to a new build and is less accurate for phased policies.
What is usually done by experts in real-world practice
In studies aligned with international energy agency integration phases, you require modifying the core model, which is preferred, or using soft constraints like penalties instead of hard ratios is preferred. There is also running iterative scenarios by adjusting storage until adequacy is met.
the recommended approach for your case.
Given your goal, strict policy enforcement is recommended.
The best roadmap is:
1. Add a custom constraint in the core model
couple the NewCapacity (PV) and NewStorageCapacity (BESS)
activate from 2029 onwards.
If you can’t modify the core, then use a dual UDC chain by linking PV-BESS power-proxy energy. Then add penalty costs to prevent underinvestment in storage.
Conclusion
Your diagnosis is technically correct
The limitation is structural, not just UI-based
There is no clean solution that exists without either model modification or carefully engineered proxy constraints.
Thank you for your reply, I am currently facing a similar problem, I just want to bound the energy storage to the capacity of the input technology, like, at least 1hr. I asked an IA tool which told me to add the following to the osemosys.txt file :
# Constraint: Minimum 1-hour storage duration (Storage Capacity >= Total Power Capacity) s.t. MinStorageDurationConstraint{r in REGION, s in STORAGE, y in YEAR}: StorageCapacity[r,s,y] >= sum{t in TECHNOLOGY: TechnologyToStorage[r,t,s] != 0} TotalCapacityAnnual[r,t,y];
But I could not find the osemosys.txt file on my computer… Where should it be? (I am using MUIO5.6)
Hi CorentinAmi, thanks for laying out the problem so clearly — this is a great question and a common point of confusion. Let me walk through it with you step by step.
1. First, a quick flag on the AI-generated code
Before hunting for the file, it’s worth double-checking the constraint itself, because it has a subtle issue: TechnologyToStorage[r,t,s] in most OSeMOSYS versions is actually indexed by mode of operation too: TechnologyToStorage[r,t,s,m]. If your version declares it with a mode index (which it almost certainly does; check your model file’s parameter declarations), the sum condition needs to loop over modes as well; otherwise GLPK will throw an index-mismatch error when you try to compile. So the corrected version would look something like:
s.t. MinStorageDurationConstraint{r in REGION, s in STORAGE, y in YEAR}:
StorageCapacity\[r,s,y\] >= sum{t in TECHNOLOGY, m in MODE_OF_OPERATION: TechnologyToStorage\[r,t,s,m\] <> 0} TotalCapacityAnnual\[r,t,y\];
Always verify against the exact parameter declarations already in your model file before pasting AI-suggested code; indexing mismatches are the #1 cause of “why won’t this compile” issues in GLPK/MathProg.
2. Why you can’t find osemosys.txt
This file only exists in the OSeMOSYS_GNU_MathProg flavor of the code; it’s the .txt file containing the model’s sets, parameters, variables, and constraints written in GNU MathProg syntax, and it’s the file you’d edit directly to add a custom constraint like this one.
Since you’re using MUIO 5.6, it’s possible that:
MUIO runs OSeMOSYS through a GUI/database layer, and the underlying .txt model file is bundled somewhere inside the installation folder rather than somewhere obvious, or
MUIO uses a different code flavor entirely (e.g., the Python or GAMS version of OSeMOSYS), in which case there is no osemosys.txt at all, the model structure lives in .py or .gms files instead, and the constraint syntax above (which is MathProg-specific) wouldn’t directly apply.
3. How to confirm which case you’re in
Check MUIO’s documentation or “About” section for what solver/code backend it runs underneath (GLPK+MathProg, CBC, or something else)
Search your MUIO installation directory for any file with a .txt, .gms, or .py extension containing words like STORAGE, TechnologyToStorage, or s.t. (search terms, not code to run); this will tell you the flavor and location
If MUIO doesn’t expose the raw model file at all (some GUI wrappers deliberately hide it to keep the tool “black-box” for less technical users), you may not be able to edit the constraint file directly through MUIO; you’d need to install the standalone OSeMOSYS_GNU_MathProg code separately from GitHub and run your model through that instead
4. If you do find/switch to the standalone GNU MathProg version
The core model file is literally named osemosys.txt (or similarly, depending on release) in the root of that repo
Open it in a text editor, find the section with the other storage-related constraints (search for Storage; there’ll be a cluster of them together), and add your new constraint there, right after the existing storage balance constraints, so it’s grouped logically
Make sure the constraint name (MinStorageDurationConstraint) doesn’t clash with an existing one already in the file
5. A design suggestion before you commit to this approach
Rather than hard-coding a fixed “1 hour” rule into the model code itself, consider whether you can achieve nearly the same effect with input data alone, for example, by setting a suitable StorageMaxChargeRate/StorageMaxDischargeRate ratio relative to your expected StorageCapacity, or by fixing bounds via TotalTechnologyAnnualActivityUpperLimit. Editing the core .txt model file works, but it means every teammate or future-you who reuses this model needs the same modified file, or results won’t reproduce. If a data-only approach can get you close enough, it’s more portable. If you do need the hard constraint (e.g., because it’s a genuine physical/regulatory requirement, not just a rough guideline), then editing the file is the right call; Just document the change clearly in your model’s README so it doesn’t get lost.
Let me know what backend MUIO turns out to use; I can give you more precise file-location guidance once we know that.
Thank you for the detailed response. It turns out I had a unit typo in the storage capital cost (off by a factor of 1,000). With the correct input, the model is now generating 1 to 6 hours of storage as expected.
Anyway, I maintain my position that allowing UDC between storage and technologies should be included in OSeMOSYS
and a more obvious way to find & modifiy osemosys code source used by MUIO (I would imagine an option in MUIO to import/export osemosys source code, and restore back to original installation)
A unit typo throwing off a cost by three orders of magnitude will absolutely make a model swing from “storage never worth building” to “storage overbuilt” or vice versa, so it’s good you tracked it down. Nice bit of debugging.
On your two points:
1. User-Defined Constraints (UDC) for storage-technology relationships: that’s a reasonable ask. As it stands, the standard way to enforce something like a duration ratio is exactly what you ran into: editing the core .txt/.py/.gms file directly, which isn’t sustainable for anyone who isn’t comfortable in the model’s native syntax. A built-in UDC framework that lets users define relationships like “storage capacity ≥ f(technology capacity)” through input data rather than source-code edits would make this kind of customization far more accessible, and more reproducible across teams, since the logic would travel with the data file rather than living in someone’s personally patched copy of the code.
2. Easier access to the OSeMOSYS source inside MUIO, also a fair point. Given how central the model file is to any nontrivial customization (as you just experienced firsthand), a GUI tool built on top of OSeMOSYS ideally should expose an explicit “view/export source” and “restore to default” option, rather than requiring users to dig through installation directories or reinstall from scratch if something breaks.
Both of these sound like solid feature requests. If you haven’t already, you or someone else would raise them on the OSeMOSYS GitHub repo (as issues) and separately flag the MUIO one to whoever maintains that tool; UDC support in particular seems like something other users doing storage work would want too, so it’s worth making the case publicly rather than just here on the forum.