Skip to content

Commit

Permalink
source_max etc. -> source_use_max etc.; merge purchased and units (#533)
Browse files Browse the repository at this point in the history
* source_max etc. -> source_use_max etc.; 
* merge `purchased` and `units` into `purchased_units`
* move `available_flow_cap` decision variable to base math so that binary purchases can be linked to per-timestep binary `operating_units` variable (can have a continuous `flow_cap` while still allowing `flow_out` to be switched on/off in each timestep by `operating_units`).
  • Loading branch information
brynpickering committed Jan 8, 2024
1 parent a53bd28 commit 3dc7597
Show file tree
Hide file tree
Showing 35 changed files with 309 additions and 400 deletions.
2 changes: 1 addition & 1 deletion doc/_static/custom_math/annual_energy_balance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ constraints:
- expression: "sum(source_use, over=[nodes, timesteps]) <= annual_source_max"

annual_energy_balance_total_sink_availability:
description: Limit total flow out of the system into a sink that is not pinned by `sink_equals`.
description: Limit total flow out of the system into a sink that is not pinned by `sink_use_equals`.
foreach: [techs]
where: parent=demand AND annual_sink_max
equations:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Allows the model to decide on how a fraction of demand for a carrier is met by the given group of technologies, which will each have the same share in each timestep.
# Variables and constraints defined here could be extended to iterate over nodes and over carriers if desired.
# If summing over nodes, remove the summation over nodes in the constraints and add it into the list in `demand_share_per_timestep_decision_sum` (if using).
# If summing over carriers, the slicing of `sink_equals` will need to change per carrier by using a where statement in `slices: ...`.
# If summing over carriers, the slicing of `sink_use_equals` will need to change per carrier by using a where statement in `slices: ...`.

# The share is relative to the flow `sink` from a specified `demand` technology (or group thereof) only.

Expand Down Expand Up @@ -37,7 +37,7 @@ constraints:
equations:
- expression: >
flow_out[carriers=$carrier] >=
(1 - $relaxation) * select_from_lookup_arrays(sink_equals, techs=decide_demand_share) * demand_share_per_timestep_decision
(1 - $relaxation) * select_from_lookup_arrays(sink_use_equals, techs=decide_demand_share) * demand_share_per_timestep_decision
sub_expressions:
# 0 == no relaxation, 0.01 == 1% relaxation (lhs == rhs -> lhs >= 0.99 * rhs & lhs <= 1.01 * rhs)
relaxation: &relaxation_component
Expand All @@ -56,7 +56,7 @@ constraints:
equations:
- expression: >
flow_out[carriers=$carrier] <=
(1 + $relaxation) * select_from_lookup_arrays(sink_equals, techs=decide_demand_share) * demand_share_per_timestep_decision
(1 + $relaxation) * select_from_lookup_arrays(sink_use_equals, techs=decide_demand_share) * demand_share_per_timestep_decision
sub_expressions:
relaxation: *relaxation_component
slices: *slice
Expand Down
4 changes: 2 additions & 2 deletions doc/_static/custom_math/piecewise_linear_costs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ variables:
piecewise_cost_investment:
description: Investment cost that increases monotonically
foreach: [nodes, techs, costs]
where: cost_flow_cap_piecewise_slopes AND cost_flow_cap_piecewise_intercept AND purchased
where: cost_flow_cap_piecewise_slopes AND cost_flow_cap_piecewise_intercept AND purchased_units
bounds:
min: 0
max: .inf
Expand All @@ -25,7 +25,7 @@ constraints:
equations:
- expression: >
piecewise_cost_investment >=
sum(cost_flow_cap_piecewise_slopes * flow_cap, over=carriers) + cost_flow_cap_piecewise_intercept * purchased
sum(cost_flow_cap_piecewise_slopes * flow_cap, over=carriers) + cost_flow_cap_piecewise_intercept * purchased_units
# We inject this new source of into the `cost` global expression
global_expressions:
Expand Down
31 changes: 0 additions & 31 deletions doc/_static/custom_math/piecewise_linear_efficiency.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,6 @@
# flow_eff_piecewise_slopes (defining the new parameter `pieces`)
# flow_eff_piecewise_intercept (defining the new parameter `pieces`)

variables:
available_flow_cap:
description: Flow capacity that will be set to zero if the technology is not operating in a given timestep and will be set to the value of the decision variable `flow_cap` otherwise.
unit: power
foreach: [nodes, techs, carriers, timesteps]
where: flow_cap AND carrier_out AND operating_units AND NOT flow_cap_per_unit
bounds:
min: 0
max: flow_cap_max

constraints:
piecewise_efficiency:
description: >
Expand All @@ -28,24 +18,3 @@ constraints:
sum(flow_in, over=carriers) >=
flow_eff_piecewise_slopes * sum(flow_out, over=carriers)
+ flow_eff_piecewise_intercept * sum(available_flow_cap, over=carriers)
available_flow_cap_binary:
description: Limit flow capacity to zero if the technology is not operating in a given timestep.
foreach: [nodes, techs, carriers, timesteps]
where: available_flow_cap
equations:
- expression: available_flow_cap <= flow_cap_max * operating_units

available_flow_cap_continuous:
description: Limit flow capacity to the value of the `flow_cap` decision variable when the technology is operating in a given timestep.
foreach: [nodes, techs, carriers, timesteps]
where: available_flow_cap
equations:
- expression: available_flow_cap <= flow_cap

available_flow_cap_binary_continuous_switch:
description: Force flow capacity to equal the value of the `flow_cap` decision variable if the technology is operating in a given timestep, zero otherwise.
foreach: [nodes, techs, carriers, timesteps]
where: available_flow_cap
equations:
- expression: available_flow_cap >= flow_cap + ((operating_units - 1) * flow_cap_max)
2 changes: 1 addition & 1 deletion docs/custom_math/annual_energy_balance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ constraints:
- expression: "sum(source_use, over=[nodes, techs, timesteps]) <= annual_source_max"

annual_energy_balance_total_sink_availability:
description: Limit total flow out of the system into a sink that is not pinned by `sink_equals`.
description: Limit total flow out of the system into a sink that is not pinned by `sink_use_equals`.
foreach: [techs]
where: inheritance(demand) AND annual_sink_max
equations:
Expand Down
6 changes: 3 additions & 3 deletions docs/custom_math/demand_share_per_timestep_decision.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Allows the model to decide on how a fraction of demand for a carrier is met by the given group of technologies, which will each have the same share in each timestep.
# Variables and constraints defined here could be extended to iterate over nodes and over carriers if desired.
# If summing over nodes, remove the summation over nodes in the constraints and add it into the list in `demand_share_per_timestep_decision_sum` (if using).
# If summing over carriers, the slicing of `sink_equals` will need to change per carrier by using a where statement in `slices: ...`.
# If summing over carriers, the slicing of `sink_use_equals` will need to change per carrier by using a where statement in `slices: ...`.
#
# The share is relative to the flow `sink` from a specified `demand` technology (or group thereof) only.
#
Expand Down Expand Up @@ -43,7 +43,7 @@ constraints:
equations:
- expression: >
flow_out[carriers=$carrier] >=
(1 - $relaxation) * select_from_lookup_arrays(sink_equals, techs=decide_demand_share) * demand_share_per_timestep_decision
(1 - $relaxation) * select_from_lookup_arrays(sink_use_equals, techs=decide_demand_share) * demand_share_per_timestep_decision
sub_expressions:
# 0 == no relaxation, 0.01 == 1% relaxation (lhs == rhs -> lhs >= 0.99 * rhs & lhs <= 1.01 * rhs)
relaxation: &relaxation_component
Expand All @@ -62,7 +62,7 @@ constraints:
equations:
- expression: >
flow_out[carriers=$carrier] <=
(1 + $relaxation) * select_from_lookup_arrays(sink_equals, techs=decide_demand_share) * demand_share_per_timestep_decision
(1 + $relaxation) * select_from_lookup_arrays(sink_use_equals, techs=decide_demand_share) * demand_share_per_timestep_decision
sub_expressions:
relaxation: *relaxation_component
slices: *slice
Expand Down
24 changes: 23 additions & 1 deletion docs/pre_build/generate_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,27 @@ def generate_model_config() -> dict[str, dict]:
defaults = schema.extract_from_schema(
schema.MODEL_SCHEMA, "default", subset_top_level="techs"
)
milp_params = {
"flow_cap_per_unit": 1,
"storage_cap_per_unit": 1,
"cap_method": "integer",
"integer_dispatch": True,
"purchased_units_max": 2,
"purchased_units_min": 1,
"purchased_units_systemwide_max": 2,
"purchased_units_systemwide_min": 1,
}
dummy_techs = {
"demand_tech": {
"parent": "demand",
"carrier_in": "electricity",
"sink_equals": "df=ts",
"sink_use_equals": "df=ts",
},
"conversion_tech": {
"parent": "conversion",
"carrier_in": "gas",
"carrier_out": ["electricity", "heat"],
**milp_params,
},
"supply_tech": {"parent": "supply", "carrier_out": "gas"},
"storage_tech": {
Expand All @@ -98,7 +109,18 @@ def generate_model_config() -> dict[str, dict]:

for tech_group in NONDEMAND_TECHGROUPS:
for k, v in defaults.items():
if k in milp_params:
continue
if "flow_cap_per_unit" in dummy_techs[f"{tech_group}_tech"] and k in [
"flow_cap_max",
"storage_cap_max",
"flow_cap_min",
"storage_cap_min",
]:
continue

dummy_techs[f"{tech_group}_tech"][k] = _add_data(k, v)

techs_at_nodes = {k: None for k in dummy_techs.keys() if k != "transmission_tech"}
return {
"nodes": {
Expand Down
29 changes: 14 additions & 15 deletions src/calliope/backend/backend_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,7 @@ def _check_inputs(self):
data_checks = AttrDict.from_yaml(
importlib.resources.files("calliope") / "config" / "model_data_checks.yaml"
)
errors = []
warnings = []
check_results = {"fail": [], "warn": []}
parser_ = parsing.where_parser.generate_where_string_parser()
eval_kwargs = {
"equation_name": "",
Expand All @@ -193,19 +192,19 @@ def _check_inputs(self):
"helper_functions": helper_functions._registry["where"],
"apply_where": True,
}
for failure_check in data_checks["fail"]:
parsed_ = parser_.parse_string(failure_check["where"], parse_all=True)
failed = parsed_[0].eval("array", **eval_kwargs)
if failed.any():
errors.append(failure_check["message"])

for warning_check in data_checks["warn"]:
parsed_ = parser_.parse_string(warning_check["where"], parse_all=True)
warned = parsed_[0].eval("array", **eval_kwargs)
if warned.any():
warnings.append(warning_check["message"])

exceptions.print_warnings_and_raise_errors(warnings, errors)
for check_type, check_list in check_results.items():
for check in data_checks[check_type]:
parsed_ = parser_.parse_string(check["where"], parse_all=True)
failed = (
parsed_[0].eval("array", **eval_kwargs)
& self.inputs.definition_matrix
)
if failed.any():
check_list.append(check["message"])

exceptions.print_warnings_and_raise_errors(
check_results["warn"], check_results["fail"]
)

def _build(self) -> None:
self._add_run_mode_custom_math()
Expand Down
2 changes: 1 addition & 1 deletion src/calliope/backend/expression_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ def sliced_param_or_var_parser(
) -> pp.ParserElement:
"""
Parsing grammar to process strings representing sliced model parameters or variables,
e.g. "source_max[node, tech]".
e.g. "source_use_max[node, tech]".
If a parameter, must be a data variable in the Model._model_data xarray dataset.
Expand Down
13 changes: 11 additions & 2 deletions src/calliope/config/model_data_checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ fail:
- where: cost_source_cap<0 AND not source_use_max
message: Cannot have a negative `cost_source_cap` as there is an unset corresponding `source_use_max` constraint

- where: source_equals=inf or sink_equals=inf
message: "Cannot include infinite values in `source_equals`/`sink_equals`"
- where: source_use_equals=inf or sink_use_equals=inf
message: "Cannot include infinite values in `source_use_equals`/`sink_use_equals`"

- where: (any(latitude, over=nodes) or any(longitude, over=nodes)) and not (latitude or longitude)
message: "Must define node latitude and longitude for _all_ nodes or _no_ nodes."
Expand All @@ -23,4 +23,13 @@ fail:
- where: storage_initial>1
message: "storage_initial is a fraction; values larger than 1 are not allowed."

- where: integer_dispatch=True AND NOT cap_method=integer
message: Cannot use the integer `integer_dispatch` unless the technology is using an integer unit capacities (`cap_method=integer`).

- where: (flow_cap_max OR flow_cap_min) AND flow_cap_per_unit
message: Cannot define both `flow_cap_per_unit` and `flow_cap_max`/`flow_cap_min`

- where: (storage_cap_max OR storage_cap_min) AND storage_cap_per_unit
message: Cannot define both `storage_cap_per_unit` and `storage_cap_max`/`storage_cap_min`

warn: []

0 comments on commit 3dc7597

Please sign in to comment.