Skip to content

Commit 343995a

Browse files
authored
Merge pull request #460 from EnergySystemsModellingLab/develop
v1.2.0rc4
2 parents 3b060c8 + bc6e4c0 commit 343995a

6 files changed

Lines changed: 160 additions & 8 deletions

File tree

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 1.2.0rc3
2+
current_version = 1.2.0rc4
33
commit = True
44
tag = True
55

CITATION.cff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ authors:
99
given-names: Adam
1010

1111
title: MUSE_OS
12-
version: v1.2.0rc3
12+
version: v1.2.0rc4
1313
date-released: 2024-08-13

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
project = "MUSE"
99
copyright = "2024, Imperial College London"
1010
author = "Imperial College London"
11-
release = "1.2.0rc3"
11+
release = "1.2.0rc4"
1212
version = ".".join(release.split(".")[:2])
1313

1414
# -- General configuration ---------------------------------------------------

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ classifiers = [
2424
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)"
2525
]
2626
dependencies = [
27-
"numpy>=2.0",
27+
"numpy==2.0",
2828
"scipy>=1.13",
2929
"pandas>=2.2",
3030
"xarray>=2024.6",

src/muse/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import os
44

5-
VERSION = "1.2.0rc3"
5+
VERSION = "1.2.0rc4"
66

77

88
def _create_logger(color: bool = True):

src/muse/constraints.py

Lines changed: 155 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -554,15 +554,167 @@ def demand_limiting_capacity(
554554
else capacity
555555
)
556556

557-
# This constraint is independent of the production
558-
production = 0
557+
# An adjustment is required to account for technologies that have multiple output
558+
# commodities
559+
b = modify_dlc(technologies=capacity, demand=b)
559560

560561
return xr.Dataset(
561-
dict(capacity=capacity, production=production, b=b),
562+
dict(capacity=capacity, b=b),
562563
attrs=dict(kind=ConstraintKind.UPPER_BOUND),
563564
)
564565

565566

567+
def modify_dlc(technologies: xr.DataArray, demand: xr.DataArray) -> xr.DataArray:
568+
"""Modifies DLC constraint to account for techs with multiple output commodities.
569+
570+
Adjusts the commodity-level DLC based on the commodity output ratios of the
571+
available technologies, to allow for appropriate production of side-products.
572+
573+
Args:
574+
technologies: DataArray with dimensions "commodity" and "replacement". This
575+
defines the fixed commodity outputs for each potential replacement
576+
technology.
577+
demand: DataArray with dimension "commodity", which defines the demand for each
578+
commodity.
579+
580+
Returns:
581+
DataArray with dimension "commodity", which defines the new demand-limiting
582+
capacity constraint for each commodity.
583+
584+
Example:
585+
Let's consider a simple example of a refinery sector with two alternative
586+
technologies that each produce two commodities: gasoline and diesel.
587+
588+
We define the technologies DataArray as follows:
589+
>>> import xarray as xr
590+
>>> technologies = xr.DataArray(
591+
... data=[[1, 5], [0.5, 1]],
592+
... dims=['replacement', 'commodity'],
593+
... coords={'replacement': ['technology1', 'technology2'],
594+
... 'commodity': ['gasoline', 'diesel']},
595+
... )
596+
597+
technology1 produces 1 unit of gasoline and 5 units of diesel (per unit of
598+
activity), whereas technology2 produces 0.5 units of gasoline and 1 unit of
599+
diesel.
600+
601+
In this scenario, let's also define the demand for gasoline and diesel as
602+
follows (1 unit of demand for gasoline and 0 units for diesel):
603+
>>> demand = xr.DataArray(
604+
... data=[1, 0],
605+
... dims=['commodity'],
606+
... coords={'commodity': ['gasoline', 'diesel']},
607+
... )
608+
609+
The aim of the demand-limiting capacity (DLC) constraint is to limit the
610+
capacity of each technology so that supply is sufficient to meet the demand for
611+
each commodity, and no more.
612+
613+
However, in this case we have a problem. The demand for gasoline can be met by
614+
either technology1 or technology2 (as both produce gasoline), but doing so would
615+
require producing up to 5 units of diesel (if all demand was met by
616+
technology1), which would exceed the diesel demand (0). Therefore, to allow the
617+
model to meet the demand for gasoline via either technology, we must relax the
618+
DLC constraint on diesel (to 5 units).
619+
620+
In general, for an arbitrary set of technologies and commodity demands, the
621+
DLC of each commodity needs to be sufficiently high to permit any technology to
622+
act in service of any appropriate commodity demand, and no higher.
623+
624+
The first step is to calculate the commodity output ratios for each technology:
625+
>>> output_ratios = technologies.rename({"commodity": "commodity2"}) / technologies
626+
>>> output_ratios
627+
<xarray.DataArray (replacement: 2, commodity2: 2, commodity: 2)> Size: 64B
628+
array([[[1. , 0.2],
629+
[5. , 1. ]],
630+
<BLANKLINE>
631+
[[1. , 0.5],
632+
[2. , 1. ]]])
633+
Coordinates:
634+
* replacement (replacement) <U11 88B 'technology1' 'technology2'
635+
* commodity2 (commodity2) <U8 64B 'gasoline' 'diesel'
636+
* commodity (commodity) <U8 64B 'gasoline' 'diesel'
637+
638+
We introduce the dimension "commodity2" to compare the outputs of each commodity
639+
against every other commodity. For example, for technology1, producing 1 unit of
640+
gasoline leads to 5 units of diesel, whereas producing 1 unit of diesel leads to
641+
0.2 units of gasoline.
642+
643+
Multiplying these output ratios by the demand, we get the full outputs that each
644+
technology would produce whilst acting in service of each commodity-level
645+
demand:
646+
>>> outputs = output_ratios * demand
647+
>>> outputs
648+
<xarray.DataArray (replacement: 2, commodity2: 2, commodity: 2)> Size: 64B
649+
array([[[1., 0.],
650+
[5., 0.]],
651+
<BLANKLINE>
652+
[[1., 0.],
653+
[2., 0.]]])
654+
Coordinates:
655+
* replacement (replacement) <U11 88B 'technology1' 'technology2'
656+
* commodity2 (commodity2) <U8 64B 'gasoline' 'diesel'
657+
* commodity (commodity) <U8 64B 'gasoline' 'diesel'
658+
659+
In this case, meeting the gasoline demand with technology1 would require
660+
producing 1 unit of gasoline and 5 units of diesel, whereas meeting the gasoline
661+
demand with technology2 would require producing 1 unit of gasoline and 2 units
662+
of diesel. Since there is no diesel demand, all values for commodity = "diesel"
663+
are zero.
664+
665+
Then, taking a maximum over the "commodity" dimension, we get the maximum
666+
potential outputs of each technology:
667+
>>> max_outputs = outputs.max("commodity")
668+
>>> max_outputs
669+
<xarray.DataArray (replacement: 2, commodity2: 2)> Size: 32B
670+
array([[1., 5.],
671+
[1., 2.]])
672+
Coordinates:
673+
* replacement (replacement) <U11 88B 'technology1' 'technology2'
674+
* commodity2 (commodity2) <U8 64B 'gasoline' 'diesel'
675+
676+
In this case, this is just the outputs of each technology when acting in service
677+
of the gasoline demand.
678+
679+
Finally, summing over the "replacement" dimension, we get the maximum potential
680+
outputs of each commodity:
681+
>>> dlc = max_outputs.max("replacement").rename({"commodity2": "commodity"})
682+
>>> dlc
683+
<xarray.DataArray (commodity: 2)> Size: 16B
684+
array([1., 5.])
685+
Coordinates:
686+
* commodity (commodity) <U8 64B 'gasoline' 'diesel'
687+
688+
In this case, we get the maximum potential production of diesel as 5 units,
689+
which would occur as a side-product when technology1 is acting in service of the
690+
gasoline demand. This becomes the new DLC constraint.
691+
692+
Putting this all together:
693+
>>> from muse.constraints import modify_dlc
694+
>>> modify_dlc(technologies, demand)
695+
<xarray.DataArray (commodity: 2)> Size: 16B
696+
array([1., 5.])
697+
Coordinates:
698+
* commodity (commodity) <U8 64B 'gasoline' 'diesel'
699+
""" # noqa: E501
700+
# Calculate commodity output ratios for each technology
701+
output_ratios = technologies.rename({"commodity": "commodity2"}) / technologies
702+
output_ratios = output_ratios.where(np.isfinite(output_ratios), 0) # this is
703+
# necessary for technologies that do not produce every commodity, which would lead
704+
# to an "infinite" ratio between commodities
705+
706+
# Calculate the full outputs of each technology acting in service of each commodity
707+
# demand
708+
outputs = output_ratios * demand
709+
710+
# Maximum potential outputs for each technology
711+
max_outputs = outputs.max("commodity")
712+
713+
# Maximum potential production of each commodity -> demand-limiting capacity
714+
b = max_outputs.max("replacement").rename({"commodity2": "commodity"})
715+
return b
716+
717+
566718
@register_constraints
567719
def minimum_service(
568720
demand: xr.DataArray,

0 commit comments

Comments
 (0)