10.2. Further extending MUSE

10.2.1. Adding a search space filter

[1]:
from muse.agents import Agent
from muse.filters import register_filter
from xarray import DataArray, Dataset


@register_filter
def no_ccgt_filter(
    agent: Agent, search_space: DataArray, technologies: Dataset, market: Dataset
) -> DataArray:
    """Excludes gasCCGT."""
    dropped_tech = search_space.where(search_space.replacement != "windturbine")
    return search_space & search_space.replacement.isin(dropped_tech.replacement)
[2]:
import logging
import os

from muse import examples
from muse.mca import MCA

model_path = examples.copy_model(overwrite=True)
logging.getLogger("muse").setLevel(0)
mca = MCA.factory(os.path.join(model_path, "settings.toml"))
mca.run();
-- 2024-04-25 14:56:14 - muse.sectors.register - INFO
Sector legacy registered.

-- 2024-04-25 14:56:14 - muse.sectors.register - INFO
Sector preset registered, with alias presets.

-- 2024-04-25 14:56:14 - muse.sectors.register - INFO
Sector default registered.

[3]:
import pandas as pd
import seaborn as sns

results = pd.read_csv("Results/MCACapacity.csv")
results
sns.lineplot(
    data=results[results.sector == "power"], x="year", y="capacity", hue="technology"
)
[3]:
<Axes: xlabel='year', ylabel='capacity'>
../_images/advanced-guide_further-extending-muse_4_1.png

10.2.2. Registering a custom decision function

Next, we would like to add an additional decision function. A decision function is a transformation applied to aggregate multiple objectives into a single objective during agent investment. For example, through the use of a weighted sum.

In this example, we would like to take the median objective. However, the functions predefined in MUSE don’t include this functionality. MUSE contains examples such as the mean, weighted sum and a single objective. Therefore we will have to register and create our own.

Now, we create our new median_objective function:

[4]:
from typing import Any

from muse.decisions import register_decision
from xarray import DataArray, Dataset


@register_decision
def median_objective(objectives: Dataset, parameters: Any, **kwargs) -> DataArray:
    from xarray import concat

    allobjectives = concat(objectives.data_vars.values(), dim="concat_var")
    return allobjectives.median(set(allobjectives.dims) - {"asset", "replacement"})

After importing the decorator function, register_decision, and ensuring that we decorate our new function with @register_decision, we are able to create our new function as above.

Our new function median_objective modifies the mean function already built into MUSE, with one difference. Replacing the return value, from allobjectives.mean to allobjectives.mean.

@register_decision
def mean(objectives: Dataset, *args, **kwargs) -> DataArray:
    """Mean over objectives."""
    from xarray import concat

    allobjectives = concat(objectives.data_vars.values(), dim="concat_var")
    return allobjectives.mean(set(allobjectives.dims) - {"asset", "replacement"})

Of course, you are free to make your functions as complicated as you like, depending on your own requirements.

Next, we must edit our Agents.csv file. We will modify the default example for this tutorial. We change the first two entry rows, to be as follows:

Agent1,A1,1,R1,LCOE,NPV,EAC,1,,,TRUE,,,all,median_objective,1,-1,inf,New
Agent2,A1,2,R1,LCOE,NPV,EAC,1,,,TRUE,,,all,median_objective,1,-1,inf,Retrofit

Here, we add the NPV and EAC decision metrics, as well as replacing the singleObj DecisionMethod to median_objective.

Now we are able to run our modified model as before:

[5]:
logging.getLogger("muse").setLevel(0)
mca = MCA.factory("../tutorial-code/0-new-decision-metric/settings.toml")
mca.run();
-- 2024-04-25 14:56:35 - muse.registration - WARNING
A decision with the name median_objective already exists

Again, we visualise the power sector:

[6]:
import pandas as pd
import seaborn as sns

results = pd.read_csv("../tutorial-code/0-new-decision-metric/Results/MCACapacity.csv")
results = results.groupby(["year", "sector", "technology"]).sum().reset_index()
sns.lineplot(
    data=results[results.sector == "power"], x="year", y="capacity", hue="technology"
)
[6]:
<Axes: xlabel='year', ylabel='capacity'>
../_images/advanced-guide_further-extending-muse_11_1.png

We see a different scenario emerge through these different decision metrics. This shows the importance of decision metrics when making long-term investment decisions.

10.2.3. End of tutorials

In these tutorials you have seen the ways in which you can modify MUSE. All of these methods can be combined and extended to make as simple or complex model as you wish. Feel free to experiment and come up with your own ideas for your future work!

For further information, we refer you to the API in the next section.