From dcc04adb2563347b11932ddfb25a5046a6a8484f Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Fri, 8 Nov 2024 12:13:37 +0000 Subject: [PATCH 1/2] Remove costed production method --- src/muse/production.py | 58 ------------- src/muse/quantities.py | 76 ----------------- tests/test_quantities.py | 177 --------------------------------------- 3 files changed, 311 deletions(-) diff --git a/src/muse/production.py b/src/muse/production.py index ac5e9ac46..719f5274a 100644 --- a/src/muse/production.py +++ b/src/muse/production.py @@ -154,61 +154,3 @@ def demand_matched_production( capacity=capacity, technologies=technologies, ) - - -@register_production(name="costed") -def costed_production( - market: xr.Dataset, - capacity: xr.DataArray, - technologies: xr.Dataset, - costs: Union[xr.DataArray, Callable, str] = "alcoe", - with_minimum_service: bool = True, - with_emission: bool = True, -) -> xr.DataArray: - """Computes production from ranked assets. - - The assets are ranked according to their cost. The cost can be provided as an - xarray, a callable creating an xarray, or as "alcoe". The asset with least cost are - allowed to service the demand first, up to the maximum production. By default, the - minimum service is applied first. - """ - from muse.commodities import CommodityUsage, check_usage, is_pollutant - from muse.costs import annual_levelized_cost_of_energy - from muse.quantities import ( - costed_production, - emission, - ) - from muse.utilities import broadcast_techs - - if isinstance(costs, str) and costs.lower() == "alcoe": - costs = annual_levelized_cost_of_energy - elif isinstance(costs, str): - raise ValueError(f"Unknown cost {costs}") - if callable(costs): - technodata = cast(xr.Dataset, broadcast_techs(technologies, capacity)) - costs = costs( - prices=market.prices.sel(region=technodata.region), technologies=technodata - ) - else: - costs = costs - assert isinstance(costs, xr.DataArray) - - production = costed_production( - market.consumption, - costs, - capacity, - technologies, - with_minimum_service=with_minimum_service, - ) - # add production of environmental pollutants - if with_emission: - env = is_pollutant(technologies.comm_usage) - production[dict(commodity=env)] = emission( - production, technologies.fixed_outputs - ).transpose(*production.dims) - production[ - dict( - commodity=~check_usage(technologies.comm_usage, CommodityUsage.PRODUCT) - ) - ] = 0 - return production diff --git a/src/muse/quantities.py b/src/muse/quantities.py index 4d9f6c54a..b43d486a4 100644 --- a/src/muse/quantities.py +++ b/src/muse/quantities.py @@ -467,82 +467,6 @@ def capacity_in_use( return capa_in_use -def costed_production( - demand: xr.Dataset, - costs: xr.DataArray, - capacity: xr.DataArray, - technologies: xr.Dataset, - with_minimum_service: bool = True, -) -> xr.DataArray: - """Computes production from ranked assets. - - The assets are ranked according to their cost. The asset with least cost are allowed - to service the demand first, up to the maximum production. By default, the minimum - service is applied first. - """ - from muse.quantities import maximum_production - from muse.utilities import broadcast_techs - - technodata = cast(xr.Dataset, broadcast_techs(technologies, capacity)) - - if len(capacity.region.dims) == 0: - - def group_assets(x: xr.DataArray) -> xr.DataArray: - return x.sum("asset") - - else: - - def group_assets(x: xr.DataArray) -> xr.DataArray: - return xr.Dataset(dict(x=x)).groupby("region").sum("asset").x - - ranking = costs.rank("asset") - maxprod = maximum_production(technodata, capacity, timeslices=demand.timeslice) - commodity = (maxprod > 0).any([i for i in maxprod.dims if i != "commodity"]) - commodity = commodity.drop_vars( - [u for u in commodity.coords if u not in commodity.dims] - ) - demand = demand.sel(commodity=commodity).copy() - - constraints = ( - xr.Dataset(dict(maxprod=maxprod, ranking=ranking, has_output=maxprod > 0)) - .set_coords("ranking") - .set_coords("has_output") - .sel(commodity=commodity) - ) - - if not with_minimum_service: - production = xr.zeros_like(constraints.maxprod) - else: - production = ( - getattr(technodata, "minimum_service_factor", 0) * constraints.maxprod - ) - demand = np.maximum(demand - group_assets(production), 0) - - for rank in sorted(set(constraints.ranking.values.flatten())): - condition = (constraints.ranking == rank) & constraints.has_output - current_maxprod = constraints.maxprod.where(condition, 0) - fullprod = group_assets(current_maxprod) - if (fullprod <= demand + 1e-10).all(): - current_demand = fullprod - current_prod = current_maxprod - else: - if "region" in demand.dims: - demand_prod = demand.sel(region=production.region) - else: - demand_prod = demand - demand_prod = ( - current_maxprod / current_maxprod.sum("asset") * demand_prod - ).where(condition, 0) - current_prod = np.minimum(demand_prod, current_maxprod) - current_demand = group_assets(current_prod) - demand -= np.minimum(current_demand, demand) - production = production + current_prod - - result = xr.zeros_like(maxprod) - result[dict(commodity=commodity)] = result[dict(commodity=commodity)] + production - return result - - def minimum_production( technologies: xr.Dataset, capacity: xr.DataArray, diff --git a/tests/test_quantities.py b/tests/test_quantities.py index b934c3736..97b02a58d 100644 --- a/tests/test_quantities.py +++ b/tests/test_quantities.py @@ -4,8 +4,6 @@ import xarray as xr from pytest import approx, fixture -from muse.timeslices import drop_timeslice - @fixture def demand( @@ -423,181 +421,6 @@ def test_demand_matched_production( assert (production <= max_prod + 1e-8).all() -def test_costed_production_exact_match(market, capacity, technologies): - from muse.costs import annual_levelized_cost_of_energy - from muse.quantities import ( - costed_production, - maximum_production, - ) - from muse.utilities import broadcast_techs - - if set(capacity.region.values) != set(market.region.values): - capacity.region.values[: len(set(market.region.values))] = list( - set(market.region.values) - ) - technodata = broadcast_techs(technologies, capacity) - costs = annual_levelized_cost_of_energy( - prices=market.prices.sel(region=technodata.region), technologies=technodata - ) - maxdemand = ( - xr.Dataset( - dict( - mp=maximum_production( - technologies, capacity, timeslices=market.timeslice - ) - ) - ) - .groupby("region") - .sum("asset") - .mp - ) - market["consumption"] = drop_timeslice(maxdemand) - result = costed_production(market.consumption, costs, capacity, technologies) - assert isinstance(result, xr.DataArray) - actual = xr.Dataset(dict(r=result)).groupby("region").sum("asset").r - assert set(actual.dims) == set(maxdemand.dims) - for dim in actual.dims: - assert (actual[dim] == maxdemand[dim]).all() - assert np.abs(actual - maxdemand).max() < 1e-8 - - -def test_costed_production_single_region(market, capacity, technologies): - from muse.costs import annual_levelized_cost_of_energy - from muse.quantities import ( - costed_production, - maximum_production, - ) - from muse.utilities import broadcast_techs - - capacity = capacity.drop_vars("region") - capacity["region"] = "USA" - market = market.sel(region=[capacity.region.values]) - maxdemand = maximum_production( - technologies, capacity, timeslices=market.timeslice - ).sum("asset") - market["consumption"] = drop_timeslice(0.9 * maxdemand) - technodata = broadcast_techs(technologies, capacity) - costs = annual_levelized_cost_of_energy( - prices=market.prices.sel(region=technodata.region), technologies=technodata - ) - result = costed_production(market.consumption, costs, capacity, technologies) - assert isinstance(result, xr.DataArray) - actual = result.sum("asset") - assert set(actual.dims) == set(maxdemand.dims) - for dim in actual.dims: - assert (actual[dim] == maxdemand[dim]).all() - assert np.abs(actual - 0.9 * maxdemand).max() < 1e-8 - - -def test_costed_production_single_year(market, capacity, technologies): - from muse.costs import annual_levelized_cost_of_energy - from muse.quantities import ( - costed_production, - maximum_production, - ) - from muse.utilities import broadcast_techs - - capacity = capacity.sel(year=2010) - market = market.sel(year=2010) - maxdemand = ( - xr.Dataset( - dict( - mp=maximum_production( - technologies, capacity, timeslices=market.timeslice - ) - ) - ) - .groupby("region") - .sum("asset") - .mp - ) - market["consumption"] = drop_timeslice(0.9 * maxdemand) - technodata = broadcast_techs(technologies, capacity) - costs = annual_levelized_cost_of_energy( - prices=market.prices.sel(region=technodata.region), technologies=technodata - ) - result = costed_production(market.consumption, costs, capacity, technologies) - assert isinstance(result, xr.DataArray) - actual = xr.Dataset(dict(r=result)).groupby("region").sum("asset").r - assert set(actual.dims) == set(maxdemand.dims) - for dim in actual.dims: - assert (actual[dim] == maxdemand[dim]).all() - assert np.abs(actual - 0.9 * maxdemand).max() < 1e-8 - - -def test_costed_production_over_capacity(market, capacity, technologies): - from muse.costs import annual_levelized_cost_of_energy - from muse.quantities import ( - costed_production, - maximum_production, - ) - from muse.utilities import broadcast_techs - - capacity = capacity.isel(asset=[0, 1, 2]) - if set(capacity.region.values) != set(market.region.values): - capacity.region.values[: len(set(market.region.values))] = list( - set(market.region.values) - ) - maxdemand = ( - xr.Dataset( - dict( - mp=maximum_production( - technologies, capacity, timeslices=market.timeslice - ) - ) - ) - .groupby("region") - .sum("asset") - .mp - ) - market["consumption"] = drop_timeslice(maxdemand * 0.9) - technodata = broadcast_techs(technologies, capacity) - costs = annual_levelized_cost_of_energy( - prices=market.prices.sel(region=technodata.region), technologies=technodata - ) - result = costed_production(market.consumption, costs, capacity, technologies) - assert isinstance(result, xr.DataArray) - actual = xr.Dataset(dict(r=result)).groupby("region").sum("asset").r - assert set(actual.dims) == set(maxdemand.dims) - for dim in actual.dims: - assert (actual[dim] == maxdemand[dim]).all() - assert np.abs(actual - 0.9 * maxdemand).max() < 1e-8 - - -def test_costed_production_with_minimum_service(market, capacity, technologies, rng): - from muse.costs import annual_levelized_cost_of_energy - from muse.quantities import ( - costed_production, - maximum_production, - ) - from muse.utilities import broadcast_techs - - if set(capacity.region.values) != set(market.region.values): - capacity.region.values[: len(set(market.region.values))] = list( - set(market.region.values) - ) - technologies["minimum_service_factor"] = ( - technologies.utilization_factor.dims, - rng.uniform(low=0.5, high=0.9, size=technologies.utilization_factor.shape), - ) - maxprod = maximum_production(technologies, capacity, timeslices=market.timeslice) - minprod = maxprod * broadcast_techs(technologies.minimum_service_factor, maxprod) - maxdemand = xr.Dataset(dict(mp=minprod)).groupby("region").sum("asset").mp - market["consumption"] = drop_timeslice(maxdemand * 0.9) - technodata = broadcast_techs(technologies, capacity) - costs = annual_levelized_cost_of_energy( - prices=market.prices.sel(region=technodata.region), technologies=technodata - ) - result = costed_production(market.consumption, costs, capacity, technologies) - assert isinstance(result, xr.DataArray) - actual = xr.Dataset(dict(r=result)).groupby("region").sum("asset").r - assert set(actual.dims) == set(maxdemand.dims) - for dim in actual.dims: - assert (actual[dim] == maxdemand[dim]).all() - assert (actual >= 0.9 * maxdemand - 1e-8).all() - assert (result >= minprod - 1e-8).all() - - def test_min_production(technologies, capacity, timeslice): """Test minimum production quantity.""" from muse.quantities import maximum_production, minimum_production From 41b434dc46016f675cc1b655f2958ff205a173c1 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Fri, 8 Nov 2024 12:18:33 +0000 Subject: [PATCH 2/2] Remove match production method --- src/muse/production.py | 32 -------------------------------- src/muse/quantities.py | 30 ------------------------------ tests/test_quantities.py | 25 ------------------------- 3 files changed, 87 deletions(-) diff --git a/src/muse/production.py b/src/muse/production.py index 719f5274a..53494c06f 100644 --- a/src/muse/production.py +++ b/src/muse/production.py @@ -31,7 +31,6 @@ def production( """ __all__ = [ - "demand_matched_production", "factory", "maximum_production", "register_production", @@ -123,34 +122,3 @@ def supply( from muse.quantities import supply return supply(capacity, market.consumption, technologies) - - -@register_production(name="match") -def demand_matched_production( - market: xr.Dataset, - capacity: xr.DataArray, - technologies: xr.Dataset, - costs: str = "prices", -) -> xr.DataArray: - """Production from matching demand via annual lcoe.""" - from muse.costs import annual_levelized_cost_of_energy as lcoe - from muse.quantities import demand_matched_production, gross_margin - from muse.utilities import broadcast_techs - - if costs == "prices": - prices = market.prices - elif costs == "gross_margin": - prices = gross_margin(technologies, capacity, market.prices) - elif costs == "lcoe": - prices = lcoe( - market.prices, cast(xr.Dataset, broadcast_techs(technologies, capacity)) - ) - else: - raise ValueError(f"Unknown costs option {costs}") - - return demand_matched_production( - demand=market.consumption, - prices=prices, - capacity=capacity, - technologies=technologies, - ) diff --git a/src/muse/quantities.py b/src/muse/quantities.py index b43d486a4..f2ea8f887 100644 --- a/src/muse/quantities.py +++ b/src/muse/quantities.py @@ -386,36 +386,6 @@ def maximum_production( return result.where(is_enduse(result.comm_usage), 0) -def demand_matched_production( - demand: xr.DataArray, - prices: xr.DataArray, - capacity: xr.DataArray, - technologies: xr.Dataset, - **filters, -) -> xr.DataArray: - """Production matching the input demand. - - Arguments: - demand: demand to match. - prices: price from which to compute the annual levelized cost of energy. - capacity: capacity from which to obtain the maximum production constraints. - technologies: technologies we are looking at - **filters: keyword arguments with which to filter the input datasets and - data arrays., e.g. region, or year. - """ - from muse.costs import annual_levelized_cost_of_energy as ALCOE - from muse.demand_matching import demand_matching - from muse.utilities import broadcast_techs - - technodata = cast(xr.Dataset, broadcast_techs(technologies, capacity)) - cost = ALCOE(prices=prices, technologies=technodata, **filters) - max_production = maximum_production( - technodata, capacity, timeslices=demand, **filters - ) - assert ("timeslice" in demand.dims) == ("timeslice" in cost.dims) - return demand_matching(demand, cost, max_production) - - def capacity_in_use( production: xr.DataArray, technologies: xr.Dataset, diff --git a/tests/test_quantities.py b/tests/test_quantities.py index 97b02a58d..28fa533cf 100644 --- a/tests/test_quantities.py +++ b/tests/test_quantities.py @@ -396,31 +396,6 @@ def test_emission(production: xr.DataArray, technologies: xr.Dataset): assert em.values == approx(fout * enduses.sum().values * prod) -def test_demand_matched_production( - demand: xr.DataArray, capacity: xr.DataArray, technologies: xr.Dataset -): - from muse.commodities import CommodityUsage, is_enduse - from muse.quantities import demand_matched_production, maximum_production - - # try and make sure we have a few more outputs than the default fixture - technologies.comm_usage[:] = np.random.choice( - [CommodityUsage.PRODUCT] * 3 + list(set(technologies.comm_usage.values)), - technologies.comm_usage.shape, - ) - technologies.fixed_outputs[:] = np.random.random(technologies.fixed_outputs.shape) - technologies.fixed_outputs[:] *= is_enduse(technologies.comm_usage) - - capacity = capacity.sel(year=capacity.year.min(), drop=True) - max_prod = maximum_production(technologies, capacity, timeslices=demand.timeslice) - demand = max_prod.sum("asset") - demand[:] *= np.random.choice([0, 1, 1 / 2, 1 / 3, 1 / 10], demand.shape) - prices = xr.zeros_like(demand) - prices[:] = np.random.randint(1, 10, prices.shape) - production = demand_matched_production(demand, prices, capacity, technologies) - assert set(production.dims) == set(max_prod.dims).union(prices.dims, capacity.dims) - assert (production <= max_prod + 1e-8).all() - - def test_min_production(technologies, capacity, timeslice): """Test minimum production quantity.""" from muse.quantities import maximum_production, minimum_production