Skip to content

Commit e1dfba2

Browse files
authored
Merge pull request #12 from UtrechtUniversity/feat_general_simulator
Generalized free variables in simulator
2 parents 7c739e4 + 60c3021 commit e1dfba2

3 files changed

Lines changed: 51 additions & 34 deletions

File tree

mimosa/core/simulation/helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def sort_equations(equations_dict, return_graph=False):
8383
G_full.add_edge(prev_dep, node, type="prev_time_dependency")
8484

8585
cycles = list(nx.simple_cycles(G))
86+
free_variables = []
8687
if cycles:
8788
error_msg = "Circular dependencies found:\n\n"
8889
for cycle in cycles:
@@ -98,9 +99,10 @@ def sort_equations(equations_dict, return_graph=False):
9899
except KeyError:
99100
# print(f"Warning: no equation found for {node}, skipping.")
100101
G_full.nodes[node]["has_equation"] = False
102+
free_variables.append(node)
101103

102104
if return_graph:
103-
return equations_sorted, G_full
105+
return equations_sorted, G_full, free_variables
104106
return equations_sorted
105107

106108

mimosa/core/simulation/simulator.py

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Simulator:
2626
equations: list
2727
equations_sorted: list
2828
equations_graph: nx.DiGraph
29+
free_variables: list
2930

3031
def __init__(self):
3132
pass
@@ -46,52 +47,65 @@ def prepare_simulation(self, equations, concrete_model):
4647
equations_dict = {eq.name: eq for eq in equations}
4748
calc_dependencies(equations_dict, concrete_model)
4849
# Perform topological sort of equations based on dependencies
49-
self.equations_sorted, self.equations_graph = sort_equations(
50-
equations_dict, return_graph=True
50+
self.equations_sorted, self.equations_graph, self.free_variables = (
51+
sort_equations(equations_dict, return_graph=True)
5152
)
5253

53-
def run(
54-
self,
55-
relative_abatement=None,
56-
simulation_model=None,
57-
):
54+
def run(self, simulation_model=None, **free_variables_kwargs):
5855
"""
5956
Runs MIMOSA as simulation.
6057
6158
It first sets the "free" variables (relative_abatement), then runs the simulation
6259
6360
Args:
64-
relative_abatement (array of n_timesteps x n_regions):
65-
Relative abatement values for each region and time step.
66-
If None, it defaults to a zero abatement scenario.
61+
simulation_model (SimulationObjectModel): The simulation model to run.
62+
If None, a new SimulationObjectModel will be created.
63+
free_variables_kwargs: set of keyword arguments to optionally set free variables to a value. Can be:
64+
* None (equivalent to 0)
65+
* A float value: every region and time step will get this value
66+
* A numpy array of shape (n_timesteps, n_regions): each region and time step will get the corresponding value
6767
"""
6868

69+
if simulation_model is None:
70+
# Create a SimulationObjectModel object that will be used to run the simulation
71+
simulation_model = SimulationObjectModel(self.concrete_model)
72+
6973
n_timesteps = len(self.concrete_model.t)
7074
n_regions = len(self.concrete_model.regions)
7175

72-
# TODO: make dynamic list of "free" variables
73-
# For now, we only have relative_abatement as a free variable
76+
# Check if there are no variables provided in kwargs that are not in the free variables:
77+
for var in free_variables_kwargs:
78+
if var not in self.free_variables:
79+
raise ValueError(
80+
f"Variable '{var}' is not a free variable. "
81+
f"Available free variables: {self.free_variables}"
82+
)
83+
84+
for var in self.free_variables:
85+
# Check if the variable is provided in the kwargs, otherwise set to None
86+
value = free_variables_kwargs.get(var, None)
87+
88+
if value is None:
89+
# If no relative abatement is provided, set it to zero
90+
value = 0
7491

75-
if relative_abatement is None:
76-
# If no relative abatement is provided, set it to zero
77-
relative_abatement = np.zeros((n_timesteps, n_regions))
78-
else:
92+
# TODO: check if variable is region/time dependent
7993
# First check if it is given as a dictionary with (time, region) keys
80-
if isinstance(relative_abatement, dict):
94+
if isinstance(value, dict):
8195
# Convert the dictionary to a numpy array
82-
relative_abatement = self._dict_values_to_numpy(relative_abatement)
96+
value = self._dict_values_to_numpy(value)
97+
# If it is a single float value, convert it to a numpy array
98+
elif isinstance(value, (int, float)):
99+
value = np.full((n_timesteps, n_regions), value)
100+
83101
# Check that the dimensions are correct
84-
assert relative_abatement.shape == (n_timesteps, n_regions), (
85-
f"relative_abatement must be of shape (n_timesteps, n_regions), "
86-
f"but is {relative_abatement.shape}."
102+
assert value.shape == (n_timesteps, n_regions), (
103+
f"{var} must be of shape (n_timesteps, n_regions), "
104+
f"but is {value.shape}."
87105
)
88106

89-
if simulation_model is None:
90-
# Create a SimulationObjectModel object that will be used to run the simulation
91-
simulation_model = SimulationObjectModel(self.concrete_model)
92-
93-
# Set the relative abatement for all regions and time periods
94-
simulation_model.relative_abatement.values = relative_abatement
107+
# Set the relative abatement for all regions and time periods
108+
getattr(simulation_model, var).values = value
95109

96110
self._simulate(simulation_model)
97111

@@ -118,7 +132,7 @@ def f(x, objective_only=True):
118132
# Set global relative abatement (x) to regional abatement in simulation
119133
relative_abatement = x.reshape((n_t, 1)).repeat(n_r, axis=1)
120134
# Do simulation
121-
self.run(relative_abatement, sim_m)
135+
self.run(sim_m, relative_abatement=relative_abatement)
122136
# Return the value to minimise (we try to maximise the final NPV)
123137
if objective_only:
124138
return -sim_m.NPV[n_t - 1]

mimosa/mimosa.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,19 @@ def prerun_simulation(self):
9898
# Set the best guess as initial values for the concrete model
9999
self.simulator.initialize_pyomo_model(self.concrete_model, sim_m_best_guess)
100100

101-
def run_simulation(self, relative_abatement=None):
101+
def run_simulation(self, **free_variables_kwargs):
102102
"""
103103
Runs MIMOSA as simulation.
104104
105105
It first sets the "free" variables (relative_abatement), then runs the simulation.
106106
107107
Args:
108-
relative_abatement (array of n_timesteps x n_regions):
109-
Relative abatement values for each region and time step.
110-
If None, it defaults to a zero abatement scenario.
108+
free_variables_kwargs: A set of keyword arguments to optionally set free variables to a value. Can be:
109+
* None (equivalent to 0)
110+
* A float value: every region and time step will get this value
111+
* A numpy array of shape (n_timesteps, n_regions): each region and time step will get the corresponding value
111112
"""
112-
return self.simulator.run(relative_abatement)
113+
return self.simulator.run(**free_variables_kwargs)
113114

114115
def run_nopolicy_baseline(self):
115116
"""Runs the no-policy baseline simulation with relative abatement set to 0."""

0 commit comments

Comments
 (0)