Skip to content

Commit 3c3ca05

Browse files
author
Bryn Pickering
authored
Release v0.6.8
1 parent 88bd2fe commit 3c3ca05

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+4078
-3997
lines changed

.azure-pipelines.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,11 @@ steps:
5050
conda info -a
5151
displayName: Configure and update conda
5252
53-
- bash: |
53+
- bash: | # cbc -quit may be required to make sure that cbc is 'reset' such that its timeout time fits within pyomo's strict limit (see: https://github.com/Pyomo/pyomo/issues/2102)
5454
python utils/conda_create.py requirements.yml --python_version=$(PYTHON_VERSION) --channels conda-forge defaults --run
5555
source activate calliope
5656
conda install -y -c conda-forge coincbc
57+
cbc -quit
5758
pip install --no-cache-dir --verbose -e .
5859
py.test -n 2 --junitxml=junit/test-results.xml --cov=calliope --cov-report=term-missing --cov-report=xml -W ignore::FutureWarning
5960
displayName: Set up environment and run tests (UNIX)

calliope/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.6.7"
1+
__version__ = "0.6.8"

calliope/backend/checks.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def _get_param(loc_tech, var):
5050
if _is_in(loc_tech, var) and not model_data[var].loc[loc_tech].isnull().any():
5151
param = model_data[var].loc[loc_tech].values
5252
else:
53-
param = defaults[var]
53+
param = defaults.get(var, None)
5454
return param
5555

5656
def _is_in(loc_tech, set_or_var):
@@ -208,7 +208,7 @@ def _set_inf_and_warn(loc_tech, var, warnings, warning_text):
208208
if _is_in(loc_tech, "loc_techs_store"):
209209
storage_cap = model_data.storage_cap.loc[loc_tech].item()
210210
if not pd.isnull(storage_cap) and not pd.isnull(energy_cap):
211-
if _get_param(loc_tech, "charge_rate") is not False:
211+
if _get_param(loc_tech, "charge_rate") is not None:
212212
charge_rate = model_data["charge_rate"].loc[loc_tech].item()
213213
if storage_cap * charge_rate < energy_cap:
214214
errors.append(
@@ -217,7 +217,7 @@ def _set_inf_and_warn(loc_tech, var, warnings, warning_text):
217217
loc_tech
218218
)
219219
)
220-
if _get_param(loc_tech, "energy_cap_per_storage_cap_max") is not False:
220+
if not np.isinf(_get_param(loc_tech, "energy_cap_per_storage_cap_max")):
221221
energy_cap_per_storage_cap_max = (
222222
model_data["energy_cap_per_storage_cap_max"]
223223
.loc[loc_tech]
@@ -230,7 +230,7 @@ def _set_inf_and_warn(loc_tech, var, warnings, warning_text):
230230
loc_tech
231231
)
232232
)
233-
if _get_param(loc_tech, "energy_cap_per_storage_cap_min") is not False:
233+
if _get_param(loc_tech, "energy_cap_per_storage_cap_min") != 0:
234234
energy_cap_per_storage_cap_min = (
235235
model_data["energy_cap_per_storage_cap_min"]
236236
.loc[loc_tech]

calliope/backend/pyomo/constraints/capacity.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
import pyomo.core as po # pylint: disable=import-error
1313
import numpy as np
1414

15-
from calliope.backend.pyomo.util import get_param, split_comma_list
15+
from calliope.backend.pyomo.util import (
16+
apply_equals,
17+
get_param,
18+
split_comma_list,
19+
invalid,
20+
)
1621
from calliope import exceptions
1722

1823
ORDER = 10 # order in which to invoke constraints relative to other constraint files
@@ -103,28 +108,21 @@ def get_capacity_constraint(
103108

104109
decision_variable = getattr(backend_model, parameter)
105110

106-
if not _equals:
111+
if _equals is None:
107112
_equals = get_param(backend_model, parameter + "_equals", loc_tech)
108-
if not _max:
113+
if _max is None:
109114
_max = get_param(backend_model, parameter + "_max", loc_tech)
110-
if not _min:
115+
if _min is None:
111116
_min = get_param(backend_model, parameter + "_min", loc_tech)
112-
if po.value(_equals) is not False and po.value(_equals) is not None:
113-
if np.isinf(po.value(_equals)):
114-
e = exceptions.ModelError
115-
raise e(
116-
"Cannot use inf for {}_equals for loc:tech `{}`".format(
117-
parameter, loc_tech
118-
)
119-
)
120-
if scale:
117+
if apply_equals(_equals):
118+
if scale is not None:
121119
_equals *= scale
122120
return decision_variable[loc_tech] == _equals
123121
else:
124122
if po.value(_min) == 0 and np.isinf(po.value(_max)):
125123
return po.Constraint.NoConstraint
126124
else:
127-
if scale:
125+
if scale is not None:
128126
_max *= scale
129127
_min *= scale
130128
return (_min, decision_variable[loc_tech], _max)
@@ -236,10 +234,15 @@ def energy_capacity_storage_equals_constraint_rule(backend_model, loc_tech):
236234
\\forall loc::tech \\in loc::techs_{store}
237235
238236
"""
239-
return backend_model.energy_cap[loc_tech] == (
240-
backend_model.storage_cap[loc_tech]
241-
* get_param(backend_model, "energy_cap_per_storage_cap_equals", loc_tech)
237+
energy_cap_per_storage_cap = get_param(
238+
backend_model, "energy_cap_per_storage_cap_equals", loc_tech
242239
)
240+
if apply_equals(energy_cap_per_storage_cap):
241+
return backend_model.energy_cap[loc_tech] == (
242+
backend_model.storage_cap[loc_tech] * energy_cap_per_storage_cap
243+
)
244+
else:
245+
return po.Constraint.Skip
243246

244247

245248
def resource_capacity_constraint_rule(backend_model, loc_tech):
@@ -321,7 +324,7 @@ def resource_area_constraint_rule(backend_model, loc_tech):
321324
backend_model, "resource_area_per_energy_cap", loc_tech
322325
)
323326

324-
if po.value(energy_cap_max) == 0 and not po.value(area_per_energy_cap):
327+
if po.value(energy_cap_max) == 0 and invalid(area_per_energy_cap):
325328
# If a technology has no energy_cap here, we force resource_area to zero,
326329
# so as not to accrue spurious costs
327330
return backend_model.resource_area[loc_tech] == 0
@@ -443,16 +446,12 @@ def energy_capacity_systemwide_constraint_rule(backend_model, tech):
443446
max_systemwide = get_param(backend_model, "energy_cap_max_systemwide", tech)
444447
equals_systemwide = get_param(backend_model, "energy_cap_equals_systemwide", tech)
445448

446-
if np.isinf(po.value(max_systemwide)) and not equals_systemwide:
449+
if np.isinf(po.value(max_systemwide)) and not apply_equals(equals_systemwide):
447450
return po.Constraint.NoConstraint
448-
elif equals_systemwide and np.isinf(po.value(equals_systemwide)):
449-
raise exceptions.ModelError(
450-
"Cannot use inf for energy_cap_equals_systemwide for tech `{}`".format(tech)
451-
)
452451

453452
sum_expr = sum(backend_model.energy_cap[loc_tech] for loc_tech in all_loc_techs)
454453

455-
if equals_systemwide:
454+
if not invalid(equals_systemwide):
456455
return sum_expr == equals_systemwide * multiplier
457456
else:
458457
return sum_expr <= max_systemwide * multiplier

calliope/backend/pyomo/constraints/conversion.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def cost_var_conversion_constraint_rule(backend_model, cost, loc_tech, timestep)
9696

9797
cost_om_prod = get_param(backend_model, "cost_om_prod", (cost, loc_tech, timestep))
9898
cost_om_con = get_param(backend_model, "cost_om_con", (cost, loc_tech, timestep))
99-
if po.value(cost_om_prod):
99+
if po.value(cost_om_prod) != 0:
100100
cost_prod = (
101101
cost_om_prod
102102
* weight
@@ -105,7 +105,7 @@ def cost_var_conversion_constraint_rule(backend_model, cost, loc_tech, timestep)
105105
else:
106106
cost_prod = 0
107107

108-
if po.value(cost_om_con):
108+
if po.value(cost_om_con) != 0:
109109
cost_con = (
110110
cost_om_con
111111
* weight

calliope/backend/pyomo/constraints/conversion_plus.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
get_param,
1616
split_comma_list,
1717
get_conversion_plus_io,
18+
invalid,
1819
)
1920

2021
ORDER = 20 # order in which to invoke constraints relative to other constraint files
@@ -237,7 +238,7 @@ def cost_var_conversion_plus_constraint_rule(backend_model, cost, loc_tech, time
237238
cost_om_prod = get_param(
238239
backend_model, "cost_om_prod", (cost, loc_tech, timestep)
239240
)
240-
if cost_om_prod:
241+
if not invalid(cost_om_prod):
241242
var_cost += (
242243
cost_om_prod
243244
* weight
@@ -248,7 +249,7 @@ def cost_var_conversion_plus_constraint_rule(backend_model, cost, loc_tech, time
248249
cost_om_con = get_param(
249250
backend_model, "cost_om_con", (cost, loc_tech, timestep)
250251
)
251-
if cost_om_con:
252+
if not invalid(cost_om_con):
252253
var_cost += (
253254
cost_om_con
254255
* weight

calliope/backend/pyomo/constraints/energy_balance.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ def balance_supply_constraint_rule(backend_model, loc_tech, timestep):
211211
else:
212212
available_resource = resource * resource_scale
213213

214-
if po.value(force_resource):
214+
# NOTE: this is a boolean comparison, generalised to account for possible floatification of the input data
215+
if po.value(force_resource) == 1:
215216
return carrier_prod == available_resource
216217
elif min_use:
217218
return min_use * available_resource <= carrier_prod <= available_resource
@@ -286,7 +287,8 @@ def balance_demand_constraint_rule(backend_model, loc_tech, timestep):
286287
# e.g. in the group constraints
287288
backend_model.required_resource[loc_tech, timestep] = required_resource
288289

289-
if po.value(force_resource):
290+
# NOTE: this is a boolean comparison, generalised to account for possible floatification of the input data
291+
if po.value(force_resource) == 1:
290292
return carrier_con == backend_model.required_resource[loc_tech, timestep]
291293
else:
292294
return carrier_con >= backend_model.required_resource[loc_tech, timestep]
@@ -351,8 +353,8 @@ def resource_availability_supply_plus_constraint_rule(
351353
)
352354
else:
353355
available_resource = resource * resource_scale
354-
355-
if po.value(force_resource):
356+
# NOTE: this is a boolean comparison, generalised to account for possible floatification of the input data
357+
if po.value(force_resource) == 1:
356358
return backend_model.resource_con[loc_tech, timestep] == available_resource
357359
else:
358360
return backend_model.resource_con[loc_tech, timestep] <= available_resource
@@ -474,7 +476,7 @@ def balance_supply_plus_constraint_rule(backend_model, loc_tech, timestep):
474476
timestep
475477
]
476478
elif current_timestep == 0 and run_config["cyclic_storage"]:
477-
previous_step = backend_model.timesteps[-1]
479+
previous_step = backend_model.timesteps.at(-1)
478480
else:
479481
previous_step = get_previous_timestep(backend_model.timesteps, timestep)
480482
storage_loss = get_param(backend_model, "storage_loss", loc_tech)
@@ -540,7 +542,7 @@ def balance_storage_constraint_rule(backend_model, loc_tech, timestep):
540542
):
541543
previous_step = model_data_dict["lookup_cluster_last_timestep"][timestep]
542544
elif current_timestep == 0 and run_config["cyclic_storage"]:
543-
previous_step = backend_model.timesteps[-1]
545+
previous_step = backend_model.timesteps.at(-1)
544546
else:
545547
previous_step = get_previous_timestep(backend_model.timesteps, timestep)
546548
storage_loss = get_param(backend_model, "storage_loss", loc_tech)
@@ -641,7 +643,7 @@ def storage_initial_rule(backend_model, loc_tech):
641643
time_resolution = 24
642644
else:
643645
storage = backend_model.storage
644-
final_step = backend_model.timesteps[-1]
646+
final_step = backend_model.timesteps.at(-1)
645647
time_resolution = backend_model.timestep_resolution[final_step]
646648

647649
return (

calliope/backend/pyomo/constraints/group.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ def load_constraints(backend_model):
5959
),
6060
)
6161

62+
if "group_storage_cap_{}".format(sense) in model_data_dict:
63+
setattr(
64+
backend_model,
65+
"group_storage_cap_{}_constraint".format(sense),
66+
po.Constraint(
67+
getattr(backend_model, "group_names_storage_cap_{}".format(sense)),
68+
[sense],
69+
rule=storage_cap_constraint_rule,
70+
),
71+
)
72+
6273
if "group_resource_area_{}".format(sense) in model_data_dict:
6374
setattr(
6475
backend_model,
@@ -198,14 +209,27 @@ def load_constraints(backend_model):
198209
)
199210

200211
if "group_demand_share_per_timestep_decision" in model_data_dict:
201-
backend_model.group_demand_share_per_timestep_decision_main_constraint = (
202-
po.Constraint(
203-
backend_model.group_names_demand_share_per_timestep_decision,
204-
backend_model.loc_tech_carriers_demand_share_per_timestep_lhs,
205-
backend_model.timesteps,
206-
rule=demand_share_per_timestep_decision_main_constraint_rule,
212+
relaxation = backend_model.__calliope_run_config["relax_constraint"][
213+
"demand_share_per_timestep_decision_main_constraint"
214+
]
215+
if relaxation == 0:
216+
sense_scale = [("equals", 1)]
217+
else:
218+
sense_scale = [("min", 1 - relaxation), ("max", 1 + relaxation)]
219+
for group_name in backend_model.group_names_demand_share_per_timestep_decision:
220+
setattr(
221+
backend_model,
222+
f"group_demand_share_per_timestep_decision_{group_name}_constraint",
223+
po.Constraint(
224+
[group_name],
225+
get_group_lhs_and_rhs_loc_tech_carriers(backend_model, group_name)[
226+
0
227+
],
228+
backend_model.timesteps,
229+
sense_scale,
230+
rule=demand_share_per_timestep_decision_main_constraint_rule,
231+
),
207232
)
208-
)
209233
backend_model.group_demand_share_per_timestep_decision_sum_constraint = (
210234
po.Constraint(
211235
backend_model.group_names_demand_share_per_timestep_decision,
@@ -334,7 +358,7 @@ def demand_share_per_timestep_constraint_rule(
334358

335359

336360
def demand_share_per_timestep_decision_main_constraint_rule(
337-
backend_model, group_name, loc_tech_carrier, timestep
361+
backend_model, group_name, loc_tech_carrier, timestep, sense, scale
338362
):
339363
"""
340364
Allows the model to decide on how a fraction demand for a carrier is met
@@ -368,6 +392,7 @@ def demand_share_per_timestep_decision_main_constraint_rule(
368392
lhs = backend_model.carrier_prod[loc_tech_carrier, timestep]
369393
rhs = (
370394
-1
395+
* scale
371396
* sum(
372397
backend_model.required_resource[
373398
rhs_loc_tech_carrier.rsplit("::", 1)[0], timestep
@@ -377,7 +402,7 @@ def demand_share_per_timestep_decision_main_constraint_rule(
377402
* backend_model.demand_share_per_timestep_decision[loc_tech_carrier]
378403
)
379404

380-
return equalizer(lhs, rhs, "min")
405+
return equalizer(lhs, rhs, sense)
381406

382407

383408
def demand_share_per_timestep_decision_sum_constraint_rule(backend_model, group_name):
@@ -686,8 +711,7 @@ def energy_cap_constraint_rule(backend_model, constraint_group, what):
686711

687712
def storage_cap_constraint_rule(backend_model, constraint_group, what):
688713
"""
689-
Enforce upper and lower bounds for storage_cap of storage_cap
690-
for groups of technologies and locations.
714+
Enforce upper and lower bounds of storage_cap for groups of technologies and locations.
691715
692716
.. container:: scrolling-wrapper
693717

calliope/backend/pyomo/constraints/milp.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
get_loc_tech,
1919
split_comma_list,
2020
loc_tech_is_in,
21+
apply_equals,
2122
)
2223

2324
from calliope.backend.pyomo.constraints.capacity import get_capacity_constraint
@@ -447,7 +448,7 @@ def energy_capacity_max_purchase_milp_constraint_rule(backend_model, loc_tech):
447448
energy_cap_equals = get_param(backend_model, "energy_cap_equals", loc_tech)
448449
energy_cap_scale = get_param(backend_model, "energy_cap_scale", loc_tech)
449450

450-
if po.value(energy_cap_equals):
451+
if apply_equals(energy_cap_equals):
451452
return backend_model.energy_cap[loc_tech] == (
452453
energy_cap_equals * energy_cap_scale * backend_model.purchased[loc_tech]
453454
)
@@ -505,7 +506,7 @@ def storage_capacity_max_purchase_milp_constraint_rule(backend_model, loc_tech):
505506
storage_cap_max = get_param(backend_model, "storage_cap_max", loc_tech)
506507
storage_cap_equals = get_param(backend_model, "storage_cap_equals", loc_tech)
507508

508-
if po.value(storage_cap_equals):
509+
if apply_equals(storage_cap_equals):
509510
return backend_model.storage_cap[loc_tech] == (
510511
storage_cap_equals * backend_model.purchased[loc_tech]
511512
)
@@ -648,12 +649,8 @@ def unit_capacity_systemwide_milp_constraint_rule(backend_model, tech):
648649
max_systemwide = get_param(backend_model, "units_max_systemwide", tech)
649650
equals_systemwide = get_param(backend_model, "units_equals_systemwide", tech)
650651

651-
if np.isinf(po.value(max_systemwide)) and not equals_systemwide:
652+
if np.isinf(po.value(max_systemwide)) and not apply_equals(equals_systemwide):
652653
return po.Constraint.NoConstraint
653-
elif equals_systemwide and np.isinf(po.value(equals_systemwide)):
654-
raise ValueError(
655-
"Cannot use inf for energy_cap_equals_systemwide for tech `{}`".format(tech)
656-
)
657654

658655
sum_expr_units = sum(
659656
backend_model.units[loc_tech]
@@ -666,7 +663,7 @@ def unit_capacity_systemwide_milp_constraint_rule(backend_model, tech):
666663
if loc_tech_is_in(backend_model, loc_tech, "loc_techs_purchase")
667664
)
668665

669-
if equals_systemwide:
666+
if apply_equals(equals_systemwide):
670667
return sum_expr_units + sum_expr_purchase == equals_systemwide * multiplier
671668
else:
672669
return sum_expr_units + sum_expr_purchase <= max_systemwide * multiplier

0 commit comments

Comments
 (0)