Performance
In most Posy2 studies, computation time is dominated by the optimiser rather than by the component builders. The most important performance decisions are therefore the temporal horizon, use of integer variables, interconnection formulation, and solver configuration.
If model construction itself becomes noticeable, the usual Julia advice applies: put model construction and reporting code inside functions, keep input types stable, and avoid repeatedly reconstructing identical intermediate data. See Julia's Performance Tips for general advice.
function build_scenario(optimizer, options)
s = Sim(Model(optimizer); mesh=TimeMesh())
snapshot = Snapshot(s, Dict(:posy => options))
# Build carriers, nodes, and components.
return snapshot
endPosy2 caches workbook reads using the path, filename, and file modification time. Technology sheets are also cached because converting a workbook table to a DataFrame can be noticeable in a large build. Keep scenario workbooks unchanged while a model is being built; editing a file changes its modification time and correctly causes Posy2 to read it again. Supplying explicit builder overrides can avoid lookups for scalar parameters, but profile-driven builders still require their time-series data.
In problems with a very large number of variables, JuMP string names can take a non-negligible time. Names help diagnose infeasibility, but if they are not needed, disable them immediately after creating the simulation and before building components:
import JuMP
s = Sim(Model(HiGHS.Optimizer); mesh=TimeMesh())
JuMP.set_string_names_on_creation(model(s), false)Keep names enabled while developing or when solver conflict reports are part of the workflow.
Constraint Scaling
Nosy applies its constraint-scaling bridge only when Sim receives an optimiser constructor. Pass HiGHS.Optimizer directly when configuring constraint_scaling, scalingtarget, or expthreshold:
s = Sim(
HiGHS.Optimizer;
mesh=TimeMesh(),
objthreshold=1e-8,
constraint_scaling=true,
expthreshold=0.0,
scalingtarget=1.0,
boundthreshold=0.0,
)The default zero thresholds preserve the mathematical model. Positive thresholds may remove small but meaningful terms when model units differ substantially, so validate any cleanup setting before production use.
The sections below mostly target solver time by reducing model size or changing formulation choices in Posy2 builders.
Integer Decisions
Integer unit commitment is usually the largest increase in solve difficulty. Use integer_uc=true only when discrete commitment is important to the study. The relaxed formulation with uc=true, integer_uc=false is faster but is not equivalent to integer commitment.
Integer nuclear capacity decisions (integer_cap=true) also turn the capacity expansion into a MILP. Large unit sizes can make the investment problem combinatorial even when dispatch itself is simple.
For nuclear unit commitment, use startup_mask and shutdown_mask to avoid creating event choices at timesteps where transitions are not allowed. Nuclear refuel masks should similarly reflect the actual set of permissible refuelling windows.
Interconnections
For price and node interconnections, exclusive_direction=true adds one SOS1 relation per timestep so that the two directions cannot be used simultaneously. This can increase solver work.
The optional DC power flow formulation adds one KVL relation per independent cycle of the AC network, not one relation for every possible cycle. Tree networks therefore receive no additional KVL constraints. DC links are excluded from the AC cycle graph.
Storage Simplifications
makebatterystorage and makehydroreservoir expose a simplified=true option that selects Nosy's simpler storage balance formulation. makehydrogenstorage always uses the simplified formulation because it is intended for medium- or long-term inventory.
The simplified formulation changes the temporal representation of storage. Validate it against the default formulation when storage operation is central to the result.
Time Horizon
Posy2 builders require a full non-leap year of 8760 hours and reject any other horizon, so the model cannot be made smaller by shortening the year.
Coarser Time Resolution
Only the number of hours is constrained, not the number of steps inside them, so a mesh that aggregates steps, such as TimeMesh(fill(2 // 1, 4380)), is accepted. This is the main lever on model size: Posy2 writes one set of flow, storage, and balance variables per step, and the count follows the step count exactly. The two-country system of the Coarse Time Mesh example goes from 61320 variables and 26280 constraints on the default hourly mesh to 30660 and 13140 on two-hour steps, which HiGHS solves in about half the time.
Aggregating steps is not a free approximation. Nosy attaches a value to each step boundary instead of averaging it over the step, so an hourly series given to a coarser mesh is sampled at those boundaries and whatever happens between them is dropped. The error is therefore set by the shape. A flat series survives unchanged, but a one-hour peak will have a heavily biased impact on the results.
Reporting
write_results computes a broad collection of annual and hourly tables before writing the workbook. For repeated interactive analysis, query only the required Nosy metrics and generate the full workbook once at the end of the study.
Price and self-system reports require evaluated node duals. If they are not needed, omitting evalprice=true avoids retaining those additional results.