Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c8f0a4e
Add dispatch method using cost-minimising supply
tsmbland May 1, 2026
2575ce8
Tidy-ups to dispatch module
tsmbland May 5, 2026
ce6c22d
Move core code to dispatch module
tsmbland May 5, 2026
1fe7e54
Remove internal helper functions
tsmbland May 5, 2026
2b43af8
Update tests
tsmbland May 5, 2026
6142139
Fix issue with prices data structure
tsmbland May 5, 2026
116727f
Add docstring and test
tsmbland May 5, 2026
cab8f70
Merge branch 'main' into cost_minimising_supply
tsmbland May 5, 2026
2a13a63
Change default and update results files
tsmbland May 5, 2026
a2cf081
Update tutorial models
tsmbland May 5, 2026
3fd29c8
Fix for models with multiple regions
tsmbland May 5, 2026
de88eeb
Update tutorial model
tsmbland May 5, 2026
7cf9d37
Update documentation
tsmbland May 5, 2026
5fc2a15
Add new method to documentation
tsmbland May 5, 2026
cd84aff
Copilot suggestions
tsmbland May 5, 2026
b5ea292
Revert dispatch method for some example model sectors
tsmbland May 6, 2026
08d7ba2
Add tests, fix errors with multi-region models
tsmbland May 6, 2026
fc62b8b
Use different mask
tsmbland May 6, 2026
99889e7
Fix documentation formatting
tsmbland May 6, 2026
e021235
Normalise some data inputs
tsmbland May 6, 2026
2a070d7
Typo
tsmbland May 6, 2026
d3970d1
Create dedicated marginal_cost function
tsmbland May 6, 2026
b80d991
Simplify marginal cost calculation
tsmbland May 6, 2026
3b7fc0e
Update results files
tsmbland May 6, 2026
4dda6f5
Fix error with dispatch ordering
tsmbland May 6, 2026
5f161db
Update results AGAIN
tsmbland May 6, 2026
b83550a
Different selection method (testing)
tsmbland May 6, 2026
e4395fe
Revert "Different selection method (testing)"
tsmbland May 6, 2026
1d78508
Simplify dispatch_by_merit_order
tsmbland May 6, 2026
e9decf2
Add tests
tsmbland May 6, 2026
ccbcd72
Ban implicit broadcasting over region dimension
tsmbland May 6, 2026
1f2f4f0
Fix some tests
tsmbland May 6, 2026
31348ac
Fix carbon budget
tsmbland May 6, 2026
62cd3a4
Fix another test
tsmbland May 6, 2026
8bdf36f
Merge branch 'main' into broadcast_regions
tsmbland May 7, 2026
0d1811c
Potential fix for pull request finding
tsmbland May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/muse/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ def patched_broadcast_compat_data(self, other):
"`broadcast_timeslice` or `distribute_timeslice` (see `muse.timeslices`)."
)

if (isinstance(other, Variable)) and ("region" in self.dims) != (
"region" in getattr(other, "dims", [])
):
raise ValueError(
"Broadcasting along the 'region' dimension is required, but automatic "
"broadcasting is disabled. Please handle it explicitly using "
"`broadcast_regions` (see `muse.utilities`)."
)

# The rest of the function is copied directly from
# xarray.core.variable._broadcast_compat_data
if all(hasattr(other, attr) for attr in ["dims", "data", "shape", "encoding"]):
Expand Down
4 changes: 4 additions & 0 deletions src/muse/demand_share.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,10 @@ def unmet_demand(
assert "year" not in capacity.dims
assert "year" not in demand.dims

# If there are no assets, no production is possible, so all demand is unmet.
if capacity.sizes["asset"] == 0:
return demand.copy()

# Calculate maximum production by existing assets
produced = maximum_production(
capacity=capacity,
Expand Down
8 changes: 5 additions & 3 deletions src/muse/mca.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from muse.readers import read_initial_market
from muse.sectors import SECTORS_REGISTERED, AbstractSector, Sector
from muse.timeslices import broadcast_timeslice, drop_timeslice
from muse.utilities import future_propagation
from muse.utilities import broadcast_regions, future_propagation


class MCA:
Expand Down Expand Up @@ -298,12 +298,14 @@ def run(self) -> None:
getLogger(__name__).info(
f"Updating carbon price for year {investment_year}"
)
new_price = self.update_carbon_price(new_market)
new_price: float = self.update_carbon_price(new_market)
future_price = DataArray(new_price, coords=dict(year=investment_year))
new_market.prices.loc[dict(commodity=self.carbon_commodities)] = (
future_propagation(
new_market.prices.sel(commodity=self.carbon_commodities),
broadcast_timeslice(future_price),
broadcast_timeslice(
broadcast_regions(future_price, new_market.region)
),
)
)

Expand Down
4 changes: 4 additions & 0 deletions src/muse/timeslices.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import pandas as pd
from xarray import DataArray

from muse.utilities import broadcast_regions

TIMESLICE: DataArray = None # type: ignore


Expand Down Expand Up @@ -156,6 +158,8 @@ def distribute_timeslice(
broadcasted = broadcast_timeslice(data, ts=ts)
timeslice_sum = ts.sum("timeslice").clip(1e-6) # prevents zero division
timeslice_fractions = ts / broadcast_timeslice(timeslice_sum, ts=ts)
if "region" in data.dims:
timeslice_fractions = broadcast_regions(timeslice_fractions, data)
return broadcasted * timeslice_fractions


Expand Down
20 changes: 20 additions & 0 deletions src/muse/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,3 +687,23 @@ def camel_to_snake(name: str) -> str:
result = result.replace("n2_o", "N2O")
result = result.replace("f-gases", "F-gases")
return result


def broadcast_regions(data: xr.DataArray, template: xr.DataArray) -> xr.DataArray:
"""Convert a non-regional array to a regional array by broadcasting.

If data is already regioned in the appropriate scheme, it will be returned
unchanged.

Args:
data: Array to broadcast.
template: Dataarray with region coordinates to broadcast to.

"""
# If data already has regions, check that it matches the template regions.
if "region" in data.dims:
if data.region.equals(template.region):
return data
raise ValueError("Data is already regioned, but does not match the reference.")

return data.expand_dims(region=template.region)
21 changes: 16 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from muse.__main__ import patched_broadcast_compat_data
from muse.agents import Agent
from muse.utilities import broadcast_regions


@contextmanager
Expand Down Expand Up @@ -241,7 +242,9 @@ def var(*dims, factor=100.0):
return dims, (rand(*shape) * factor).astype(type(factor))

result["agent_share"] = var("technology", "region", "year")
result["agent_share"] /= sum(result.agent_share)
result["agent_share"] /= broadcast_regions(
sum(result.agent_share), result.agent_share
)
result["agent_share_zero"] = result["agent_share"] * 0

# first create a mask so each tech will have consistent inputs/outputs across years
Expand Down Expand Up @@ -273,12 +276,20 @@ def var(*dims, factor=100.0):
fout.loc[{"technology": tech, "commodity": i}] = 1

# expand along year and region, and fill with random numbers
ones = (result.year == result.year) * (result.region == result.region)
result["fixed_inputs"] = result.fixed_inputs * ones
ones = broadcast_regions(result.year == result.year, result.region) * (
result.region == result.region
)
result["fixed_inputs"] = (
broadcast_regions(result.fixed_inputs, result.region) * ones
)
result.fixed_inputs[:] *= rand(*result.fixed_inputs.shape)
result["flexible_inputs"] = result.flexible_inputs * ones
result["flexible_inputs"] = (
broadcast_regions(result.flexible_inputs, result.region) * ones
)
result.flexible_inputs[:] *= rand(*result.flexible_inputs.shape)
result["fixed_outputs"] = result.fixed_outputs * ones
result["fixed_outputs"] = (
broadcast_regions(result.fixed_outputs, result.region) * ones
)
result.fixed_outputs[:] *= rand(*result.fixed_outputs.shape)

result["total_capacity_limit"] = var("technology", "region", "year")
Expand Down
Loading