diff --git a/docs/advanced-guide/extending-muse.ipynb b/docs/advanced-guide/extending-muse.ipynb index 897745af0..7d30313c6 100644 --- a/docs/advanced-guide/extending-muse.ipynb +++ b/docs/advanced-guide/extending-muse.ipynb @@ -89,7 +89,7 @@ "metadata": {}, "outputs": [], "source": [ - "from typing import List, Optional, Text\n", + "from typing import Optional\n", "\n", "from muse.outputs.sector import market_quantity, register_output_quantity\n", "from xarray import DataArray, Dataset\n", @@ -137,7 +137,7 @@ "\n", "@register_output_sink(name=\"txt\")\n", "@sink_to_file(\".txt\")\n", - "def text_dump(data: Any, filename: Text) -> None:\n", + "def text_dump(data: Any, filename: str) -> None:\n", " from pathlib import Path\n", "\n", " Path(filename).write_text(f\"Hello world!\\n\\n{data}\")" @@ -286,7 +286,7 @@ "metadata": {}, "outputs": [], "source": [ - "from typing import MutableMapping, Text\n", + "from collections.abc import MutableMapping\n", "\n", "import pandas as pd\n", "import xarray as xr\n", @@ -295,13 +295,13 @@ "\n", "@register_cached_quantity(overwrite=True)\n", "def capacity(\n", - " cached: List[xr.DataArray],\n", - " agents: MutableMapping[Text, MutableMapping[Text, Text]],\n", + " cached: list[xr.DataArray],\n", + " agents: MutableMapping[str, MutableMapping[str, str]],\n", ") -> pd.DataFrame:\n", " \"\"\"Consolidates the cached capacity into a single DataFrame to save.\n", "\n", " Args:\n", - " cached (List[xr.DataArray]): The list of cached arrays during the calculation of\n", + " cached (list[xr.DataArray]): The list of cached arrays during the calculation of\n", " the time period with the capacity.\n", " agents (MutableMapping[Text, MutableMapping[Text, Text]]): Agents' metadata.\n", "\n", @@ -453,8 +453,8 @@ " market: Dataset,\n", " capacity: DataArray,\n", " technologies: Dataset,\n", - " sum_over: Optional[List[Text]] = None,\n", - " drop: Optional[List[Text]] = None,\n", + " sum_over: Optional[list[str]] = None,\n", + " drop: Optional[list[str]] = None,\n", " rounding: int = 4,\n", "):\n", " \"\"\"Current consumption.\"\"\"\n", @@ -469,7 +469,7 @@ "\n", "@register_output_sink(name=\"txt\", overwrite=True)\n", "@sink_to_file(\".txt\")\n", - "def text_dump(data: Any, filename: Text, msg: Optional[Text] = \"Hello, world!\") -> None: # noqa: F811\n", + "def text_dump(data: Any, filename: str, msg: Optional[str] = \"Hello, world!\") -> None: # noqa: F811\n", " from pathlib import Path\n", "\n", " Path(filename).write_text(f\"{msg}\\n\\n{data}\")" diff --git a/docs/conf.py b/docs/conf.py index 5512f9f25..58af920dc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,7 +4,6 @@ # list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Project information ----------------------------------------------------- -from typing import List project = "MUSE" copyright = "2022, Sustainable Gas Institute" @@ -54,7 +53,7 @@ "xarray": ("http://xarray.pydata.org/en/stable/", None), } -bibtex_bibfiles: List[str] = [] +bibtex_bibfiles: list[str] = [] # -- GraphViz configuration ---------------------------------- graphviz_output_format = "svg" diff --git a/docs/tutorial-code/3-add-region/visualise-new-region.ipynb b/docs/tutorial-code/3-add-region/visualise-new-region.ipynb index 46ca83e1a..e1099feaa 100644 --- a/docs/tutorial-code/3-add-region/visualise-new-region.ipynb +++ b/docs/tutorial-code/3-add-region/visualise-new-region.ipynb @@ -41,7 +41,7 @@ ")\n", "\n", "for name, sector in mca_capacity.groupby(\"sector\"):\n", - " print(\"{} sector:\".format(name))\n", + " print(f\"{name} sector:\")\n", " g = sns.FacetGrid(data=sector, col=\"region\")\n", " g.map(sns.lineplot, \"year\", \"capacity\", \"technology\")\n", " g.add_legend()\n", diff --git a/docs/tutorial-code/4-modify-timing-data/visualise-time-framework-changes.ipynb b/docs/tutorial-code/4-modify-timing-data/visualise-time-framework-changes.ipynb index 9c27622c4..37f2daee0 100644 --- a/docs/tutorial-code/4-modify-timing-data/visualise-time-framework-changes.ipynb +++ b/docs/tutorial-code/4-modify-timing-data/visualise-time-framework-changes.ipynb @@ -26,7 +26,7 @@ "limits = [20, 20, 60]\n", "sec_list = list(mca_capacity.groupby(\"sector\").groups.keys())\n", "for name, sector in mca_capacity.groupby(\"sector\"):\n", - " print(\"{} sector:\".format(name))\n", + " print(f\"{name} sector:\")\n", " fig, ax = plt.subplots(1, 2)\n", " sns.lineplot(\n", " data=sector[sector.region == \"R1\"],\n", diff --git a/docs/tutorial-code/4-modify-timing-data/visualise-timing-result-changes.ipynb b/docs/tutorial-code/4-modify-timing-data/visualise-timing-result-changes.ipynb index be79fea05..f1c78d468 100644 --- a/docs/tutorial-code/4-modify-timing-data/visualise-timing-result-changes.ipynb +++ b/docs/tutorial-code/4-modify-timing-data/visualise-timing-result-changes.ipynb @@ -25,7 +25,7 @@ ")\n", "\n", "for name, sector in mca_capacity.groupby(\"sector\"):\n", - " print(\"{} sector:\".format(name))\n", + " print(f\"{name} sector:\")\n", "\n", " fig, ax = plt.subplots(1, 2)\n", " sns.lineplot(\n", diff --git a/docs/tutorial-code/5-add-service-demand/visualise-service-demand-addition.ipynb b/docs/tutorial-code/5-add-service-demand/visualise-service-demand-addition.ipynb index 8c7514b79..f91011a74 100644 --- a/docs/tutorial-code/5-add-service-demand/visualise-service-demand-addition.ipynb +++ b/docs/tutorial-code/5-add-service-demand/visualise-service-demand-addition.ipynb @@ -26,7 +26,7 @@ "limits = [20, 70, 60]\n", "sec_list = list(mca_capacity.groupby(\"sector\").groups.keys())\n", "for name, sector in mca_capacity.groupby(\"sector\"):\n", - " print(\"{} sector:\".format(name))\n", + " print(f\"{name} sector:\")\n", " fig, ax = plt.subplots(1, 2)\n", " sns.lineplot(\n", " data=sector[sector.region == \"R1\"],\n", diff --git a/docs/tutorial-code/6-add-correlation-demand/visualise-correlation-demand.ipynb b/docs/tutorial-code/6-add-correlation-demand/visualise-correlation-demand.ipynb index 9d71ae1aa..7351d8f1e 100644 --- a/docs/tutorial-code/6-add-correlation-demand/visualise-correlation-demand.ipynb +++ b/docs/tutorial-code/6-add-correlation-demand/visualise-correlation-demand.ipynb @@ -33,7 +33,7 @@ "outputs": [], "source": [ "for name, sector in mca_capacity.groupby(\"sector\"):\n", - " print(\"{} sector:\".format(name))\n", + " print(f\"{name} sector:\")\n", " sns.lineplot(\n", " data=sector[sector.region == \"R1\"], x=\"year\", y=\"capacity\", hue=\"technology\"\n", " )\n", diff --git a/docs/tutorial-code/7-min-max-timeslice-constraints/1-min-constraint/output.py b/docs/tutorial-code/7-min-max-timeslice-constraints/1-min-constraint/output.py index 1b5785cb0..95459f1f1 100644 --- a/docs/tutorial-code/7-min-max-timeslice-constraints/1-min-constraint/output.py +++ b/docs/tutorial-code/7-min-max-timeslice-constraints/1-min-constraint/output.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Text +from typing import Optional import xarray as xr from muse.outputs.sector import market_quantity, register_output_quantity @@ -9,8 +9,8 @@ def supply_timeslice( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - sum_over: Optional[List[Text]] = None, - drop: Optional[List[Text]] = None, + sum_over: Optional[list[str]] = None, + drop: Optional[list[str]] = None, rounding: int = 4, ) -> xr.DataArray: """Current supply.""" @@ -29,8 +29,8 @@ def consumption_timeslice( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - sum_over: Optional[List[Text]] = None, - drop: Optional[List[Text]] = None, + sum_over: Optional[list[str]] = None, + drop: Optional[list[str]] = None, rounding: int = 4, ) -> xr.DataArray: """Current consumption.""" diff --git a/docs/tutorial-code/7-min-max-timeslice-constraints/2-max-constraint/output.py b/docs/tutorial-code/7-min-max-timeslice-constraints/2-max-constraint/output.py index 1b5785cb0..95459f1f1 100644 --- a/docs/tutorial-code/7-min-max-timeslice-constraints/2-max-constraint/output.py +++ b/docs/tutorial-code/7-min-max-timeslice-constraints/2-max-constraint/output.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Text +from typing import Optional import xarray as xr from muse.outputs.sector import market_quantity, register_output_quantity @@ -9,8 +9,8 @@ def supply_timeslice( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - sum_over: Optional[List[Text]] = None, - drop: Optional[List[Text]] = None, + sum_over: Optional[list[str]] = None, + drop: Optional[list[str]] = None, rounding: int = 4, ) -> xr.DataArray: """Current supply.""" @@ -29,8 +29,8 @@ def consumption_timeslice( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - sum_over: Optional[List[Text]] = None, - drop: Optional[List[Text]] = None, + sum_over: Optional[list[str]] = None, + drop: Optional[list[str]] = None, rounding: int = 4, ) -> xr.DataArray: """Current consumption.""" diff --git a/pyproject.toml b/pyproject.toml index 7bfd7ae2e..3500fe72a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,12 +108,16 @@ files = ["src/**/*.py", "tests/**/*.py"] module = ["setup"] ignore_errors = true +[tool.ruff] +target-version = "py39" + [tool.ruff.lint] select = [ "D", # pydocstyle "E", # pycodestyle "F", # Pyflakes "I", # isort + "UP", # Pyupgrade "RUF" # Ruff's own checks ] ignore = [ diff --git a/src/muse/agents/agent.py b/src/muse/agents/agent.py index c5b27d8e1..8146711b9 100644 --- a/src/muse/agents/agent.py +++ b/src/muse/agents/agent.py @@ -1,7 +1,8 @@ """Holds all building agents.""" from abc import ABC, abstractmethod -from typing import Callable, Optional, Sequence, Text, Union +from collections.abc import Sequence +from typing import Callable, Optional, Union import xarray as xr @@ -14,11 +15,11 @@ class AbstractAgent(ABC): def __init__( self, - name: Text = "Agent", - region: Text = "", + name: str = "Agent", + region: str = "", assets: Optional[xr.Dataset] = None, - interpolation: Text = "linear", - category: Optional[Text] = None, + interpolation: str = "linear", + category: Optional[str] = None, quantity: Optional[float] = 1, ): """Creates a standard MUSE agent. @@ -102,10 +103,10 @@ class Agent(AbstractAgent): def __init__( self, - name: Text = "Agent", - region: Text = "USA", + name: str = "Agent", + region: str = "USA", assets: Optional[xr.Dataset] = None, - interpolation: Text = "linear", + interpolation: str = "linear", search_rules: Optional[Callable] = None, objectives: Optional[Callable] = None, decision: Optional[Callable] = None, @@ -115,7 +116,7 @@ def __init__( housekeeping: Optional[Callable] = None, merge_transform: Optional[Callable] = None, demand_threshhold: Optional[float] = None, - category: Optional[Text] = None, + category: Optional[str] = None, asset_threshhold: float = 1e-4, quantity: Optional[float] = 1, spend_limit: int = 0, diff --git a/src/muse/agents/factories.py b/src/muse/agents/factories.py index 91620cbe4..efd7056fd 100644 --- a/src/muse/agents/factories.py +++ b/src/muse/agents/factories.py @@ -1,7 +1,8 @@ """Holds all building agents.""" +from collections.abc import Mapping, Sequence from pathlib import Path -from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Text, Union +from typing import Any, Callable, Optional, Union import xarray as xr @@ -14,9 +15,9 @@ def create_standard_agent( technologies: xr.Dataset, capacity: xr.DataArray, year: int, - region: Text, - share: Optional[Text] = None, - interpolation: Text = "linear", + region: str, + share: Optional[str] = None, + interpolation: str = "linear", **kwargs, ): """Creates retrofit agent from muse primitives.""" @@ -46,11 +47,11 @@ def create_standard_agent( def create_retrofit_agent( technologies: xr.Dataset, capacity: xr.DataArray, - share: Text, + share: str, year: int, - region: Text, - interpolation: Text = "linear", - decision: Union[Callable, Text, Mapping] = "mean", + region: str, + interpolation: str = "linear", + decision: Union[Callable, str, Mapping] = "mean", **kwargs, ): """Creates retrofit agent from muse primitives.""" @@ -59,7 +60,7 @@ def create_retrofit_agent( from muse.filters import factory as filter_factory if not callable(decision): - name = decision if isinstance(decision, Text) else decision["name"] + name = decision if isinstance(decision, str) else decision["name"] unusual = {"lexo", "lexical_comparison", "epsilon_constaints", "epsilon"} if name in unusual: msg = ( @@ -91,13 +92,13 @@ def create_retrofit_agent( def create_newcapa_agent( capacity: xr.DataArray, year: int, - region: Text, - share: Text, - search_rules: Union[Text, Sequence[Text]] = "all", - interpolation: Text = "linear", - merge_transform: Union[Text, Mapping, Callable] = "new", + region: str, + share: str, + search_rules: Union[str, Sequence[str]] = "all", + interpolation: str = "linear", + merge_transform: Union[str, Mapping, Callable] = "new", quantity: float = 0.3, - housekeeping: Union[Text, Mapping, Callable] = "noop", + housekeeping: Union[str, Mapping, Callable] = "noop", retrofit_present: bool = True, **kwargs, ): @@ -161,7 +162,7 @@ def create_newcapa_agent( return result -def create_agent(agent_type: Text, **kwargs) -> Agent: +def create_agent(agent_type: str, **kwargs) -> Agent: method = { "retrofit": create_retrofit_agent, "newcapa": create_newcapa_agent, @@ -173,14 +174,14 @@ def create_agent(agent_type: Text, **kwargs) -> Agent: def factory( - existing_capacity_path: Optional[Union[Path, Text]] = None, - agent_parameters_path: Optional[Union[Path, Text]] = None, - technodata_path: Optional[Union[Path, Text]] = None, - technodata_timeslices_path: Optional[Union[Text, Path]] = None, - sector: Optional[Text] = None, - sectors_directory: Union[Text, Path] = DEFAULT_SECTORS_DIRECTORY, + existing_capacity_path: Optional[Union[Path, str]] = None, + agent_parameters_path: Optional[Union[Path, str]] = None, + technodata_path: Optional[Union[Path, str]] = None, + technodata_timeslices_path: Optional[Union[str, Path]] = None, + sector: Optional[str] = None, + sectors_directory: Union[str, Path] = DEFAULT_SECTORS_DIRECTORY, baseyear: int = 2010, -) -> List[Agent]: +) -> list[Agent]: """Reads list of agents from standard MUSE input files.""" from copy import deepcopy from logging import getLogger @@ -201,22 +202,22 @@ def factory( if existing_capacity_path is None: existing_capacity_path = find_sectors_file( - "Existing%s.csv" % sector, sector, sectors_directory + f"Existing{sector}.csv", sector, sectors_directory ) if agent_parameters_path is None: agent_parameters_path = find_sectors_file( - "BuildingAgent%s.csv" % sector, sector, sectors_directory + f"BuildingAgent{sector}.csv", sector, sectors_directory ) if technodata_path is None: technodata_path = find_sectors_file( - "technodata%s.csv" % sector, sector, sectors_directory + f"technodata{sector}.csv", sector, sectors_directory ) params = read_csv_agent_parameters(agent_parameters_path) techno = read_technodictionary(technodata_path) capa = read_initial_assets(existing_capacity_path) if technodata_timeslices_path and isinstance( - technodata_timeslices_path, (Text, Path) + technodata_timeslices_path, (str, Path) ): technodata_timeslices = read_technodata_timeslices(technodata_timeslices_path) else: @@ -263,24 +264,24 @@ def factory( def agents_factory( - params_or_path: Union[Text, Path, List], - capacity: Union[xr.DataArray, Text, Path], + params_or_path: Union[str, Path, list], + capacity: Union[xr.DataArray, str, Path], technologies: xr.Dataset, - regions: Optional[Sequence[Text]] = None, + regions: Optional[Sequence[str]] = None, year: Optional[int] = None, **kwargs, -) -> List[Agent]: +) -> list[Agent]: """Creates a list of agents for the chosen sector.""" from copy import deepcopy from logging import getLogger from muse.readers import read_csv_agent_parameters, read_initial_assets - if isinstance(params_or_path, (Text, Path)): + if isinstance(params_or_path, (str, Path)): params = read_csv_agent_parameters(params_or_path) else: params = params_or_path - if isinstance(capacity, (Text, Path)): + if isinstance(capacity, (str, Path)): capacity = read_initial_assets(capacity) assert isinstance(capacity, xr.DataArray) if year is None: @@ -328,10 +329,10 @@ def agents_factory( def _shared_capacity( technologies: xr.Dataset, capacity: xr.DataArray, - region: Text, - share: Text, + region: str, + share: str, year: int, - interpolation: Text = "linear", + interpolation: str = "linear", ) -> xr.DataArray: if "region" in capacity.dims: capacity = capacity.sel(region=region) @@ -363,12 +364,12 @@ def _shared_capacity( def _standardize_inputs( - housekeeping: Union[Text, Mapping, Callable] = "clean", - merge_transform: Union[Text, Mapping, Callable] = "merge", + housekeeping: Union[str, Mapping, Callable] = "clean", + merge_transform: Union[str, Mapping, Callable] = "merge", objectives: Union[ - Callable, Text, Mapping, Sequence[Union[Text, Mapping]] + Callable, str, Mapping, Sequence[Union[str, Mapping]] ] = "fixed_costs", - decision: Union[Callable, Text, Mapping] = "mean", + decision: Union[Callable, str, Mapping] = "mean", **kwargs, ): from muse.decisions import factory as decision_factory @@ -392,20 +393,20 @@ def _standardize_inputs( def _standardize_investing_inputs( - search_rules: Optional[Union[Text, Sequence[Text]]] = None, - investment: Union[Callable, Text, Mapping] = "adhoc", + search_rules: Optional[Union[str, Sequence[str]]] = None, + investment: Union[Callable, str, Mapping] = "adhoc", constraints: Optional[ - Union[Callable, Text, Mapping, Sequence[Union[Text, Mapping]]] + Union[Callable, str, Mapping, Sequence[Union[str, Mapping]]] ] = None, **kwargs, -) -> Dict[Text, Any]: +) -> dict[str, Any]: from muse.constraints import factory as constraints_factory from muse.investments import factory as investment_factory kwargs = _standardize_inputs(**kwargs) if search_rules is None: search_rules = list() - if isinstance(search_rules, Text): + if isinstance(search_rules, str): search_rules = [u.strip() for u in search_rules.split("->")] search_rules = list(search_rules) if len(search_rules) == 0 or search_rules[-1] != "compress": diff --git a/src/muse/carbon_budget.py b/src/muse/carbon_budget.py index 710a5effa..cc8993beb 100644 --- a/src/muse/carbon_budget.py +++ b/src/muse/carbon_budget.py @@ -1,4 +1,5 @@ -from typing import Callable, MutableMapping, Sequence, Text +from collections.abc import MutableMapping, Sequence +from typing import Callable import numpy as np import xarray as xr @@ -17,10 +18,10 @@ """carbon budget fitters signature.""" -CARBON_BUDGET_METHODS: MutableMapping[Text, CARBON_BUDGET_METHODS_SIGNATURE] = {} +CARBON_BUDGET_METHODS: MutableMapping[str, CARBON_BUDGET_METHODS_SIGNATURE] = {} """Dictionary of carbon budget methods checks.""" -CARBON_BUDGET_FITTERS: MutableMapping[Text, CARBON_BUDGET_FITTERS_SIGNATURE] = {} +CARBON_BUDGET_FITTERS: MutableMapping[str, CARBON_BUDGET_FITTERS_SIGNATURE] = {} """Dictionary of carbon budget fitters.""" @@ -80,7 +81,7 @@ def fitting( sample_size: int = 4, refine_price: bool = True, price_too_high_threshold: float = 10, - fitter: Text = "slinear", + fitter: str = "slinear", ) -> float: """Used to solve the carbon market. @@ -372,7 +373,7 @@ def bisection( sample_size: int = 2, refine_price: bool = True, price_too_high_threshold: float = 10, - fitter: Text = "slinear", + fitter: str = "slinear", ) -> float: """Applies bisection algorithm to escalate carbon price and meet the budget. diff --git a/src/muse/commodities.py b/src/muse/commodities.py index 6ff27f597..30398978e 100644 --- a/src/muse/commodities.py +++ b/src/muse/commodities.py @@ -1,7 +1,8 @@ """Methods and types around commodities.""" +from collections.abc import Sequence from enum import IntFlag, auto -from typing import Sequence, Text, Union +from typing import Union from numpy import ndarray from xarray import DataArray, Dataset @@ -41,7 +42,7 @@ class CommodityUsage(IntFlag): # BYPRODUCT = auto() @property - def name(self) -> Text: + def name(self) -> str: """Hack to get the name of the flag consistently across python versions.""" return ( self._name_ @@ -124,8 +125,8 @@ def just_tech(x): def check_usage( data: Sequence[CommodityUsage], - flag: Union[Text, CommodityUsage, None], - match: Text = "all", + flag: Union[str, CommodityUsage, None], + match: str = "all", ) -> ndarray: """Match usage flags with input data array. @@ -194,7 +195,7 @@ def check_usage( from numpy import bitwise_and, equal - if isinstance(flag, Text) and len(flag) > 0: + if isinstance(flag, str) and len(flag) > 0: usage = { k.lower(): getattr(CommodityUsage, k) for k in dir(CommodityUsage) @@ -204,7 +205,7 @@ def check_usage( flag = reduce( lambda x, y: x | y, [usage[a.lower().strip()] for a in flag.split("|")] ) - elif isinstance(flag, Text) or flag is None: + elif isinstance(flag, str) or flag is None: flag = CommodityUsage.OTHER if match.lower() == "all": diff --git a/src/muse/constraints.py b/src/muse/constraints.py index 67701269c..cdd714ca0 100644 --- a/src/muse/constraints.py +++ b/src/muse/constraints.py @@ -98,18 +98,13 @@ def constraints( from __future__ import annotations +from collections.abc import Mapping, MutableMapping, Sequence from dataclasses import dataclass from enum import Enum, auto from typing import ( Any, Callable, - List, - Mapping, - MutableMapping, Optional, - Sequence, - Text, - Tuple, Union, cast, ) @@ -162,7 +157,7 @@ class ConstraintKind(Enum): add constraints that are only used if some condition is met, e.g. minimum service conditions are defined in the technodata. """ -CONSTRAINTS: MutableMapping[Text, CONSTRAINT_SIGNATURE] = {} +CONSTRAINTS: MutableMapping[str, CONSTRAINT_SIGNATURE] = {} """Registry of constraint functions.""" @@ -182,7 +177,7 @@ def decorated( market: xr.Dataset, technologies: xr.Dataset, **kwargs, - ) -> Optional[Constraint]: + ) -> Constraint | None: """Computes and standardizes a constraint.""" constraint = function( # type: ignore demand, assets, search_space, market, technologies, **kwargs @@ -214,9 +209,7 @@ def decorated( def factory( - settings: Optional[ - Union[Text, Mapping, Sequence[Text], Sequence[Union[Text, Mapping]]] - ] = None, + settings: str | Mapping | Sequence[str] | Sequence[str | Mapping] | None = None, ) -> Callable: """Creates a list of constraints from standard settings. @@ -235,10 +228,10 @@ def factory( ) def normalize(x) -> MutableMapping: - return dict(name=x) if isinstance(x, Text) else x + return dict(name=x) if isinstance(x, str) else x - if isinstance(settings, (Text, Mapping)): - settings = cast(Union[Sequence[Text], Sequence[Mapping]], [settings]) + if isinstance(settings, (str, Mapping)): + settings = cast(Union[Sequence[str], Sequence[Mapping]], [settings]) parameters = [normalize(x) for x in settings] names = [x.pop("name") for x in parameters] @@ -252,8 +245,8 @@ def constraints( search_space: xr.DataArray, market: xr.Dataset, technologies: xr.Dataset, - year: Optional[int] = None, - ) -> List[Constraint]: + year: int | None = None, + ) -> list[Constraint]: if year is None: year = int(market.year.min()) constraints = [ @@ -272,9 +265,9 @@ def max_capacity_expansion( search_space: xr.DataArray, market: xr.Dataset, technologies: xr.Dataset, - year: Optional[int] = None, - forecast: Optional[int] = None, - interpolation: Text = "linear", + year: int | None = None, + forecast: int | None = None, + interpolation: str = "linear", ) -> Constraint: r"""Max-capacity addition, max-capacity growth, and capacity limits constraints. @@ -318,7 +311,7 @@ def max_capacity_expansion( if forecast is None and len(getattr(market, "year", [])) <= 1: forecast = 5 elif forecast is None: - forecast = next((int(u) for u in sorted(market.year - year) if u > 0)) + forecast = next(int(u) for u in sorted(market.year - year) if u > 0) forecast_year = year + forecast capacity = ( @@ -397,9 +390,9 @@ def demand( search_space: xr.DataArray, market: xr.Dataset, technologies: xr.Dataset, - year: Optional[int] = None, + year: int | None = None, forecast: int = 5, - interpolation: Text = "linear", + interpolation: str = "linear", ) -> Constraint: """Constraints production to meet demand.""" from muse.commodities import is_enduse @@ -421,9 +414,9 @@ def search_space( search_space: xr.DataArray, market: xr.Dataset, technologies: xr.Dataset, - year: Optional[int] = None, + year: int | None = None, forecast: int = 5, -) -> Optional[Constraint]: +) -> Constraint | None: """Removes disabled technologies.""" if search_space.all(): return None @@ -441,7 +434,7 @@ def max_production( search_space: xr.DataArray, market: xr.Dataset, technologies: xr.Dataset, - year: Optional[int] = None, + year: int | None = None, ) -> Constraint: """Constructs constraint between capacity and maximum production. @@ -507,8 +500,8 @@ def minimum_service( search_space: xr.DataArray, market: xr.Dataset, technologies: xr.Dataset, - year: Optional[int] = None, -) -> Optional[Constraint]: + year: int | None = None, +) -> Constraint | None: """Constructs constraint between capacity and minimum service.""" from xarray import ones_like, zeros_like @@ -637,7 +630,7 @@ def lp_costs( def merge_lp( costs: xr.Dataset, *constraints: Constraint -) -> Tuple[xr.Dataset, List[Constraint]]: +) -> tuple[xr.Dataset, list[Constraint]]: """Unify coordinate systems of costs and constraints. In practice, this function brings costs and constraints into a single xr.Dataset and @@ -972,11 +965,11 @@ class ScipyAdapter: c: np.ndarray to_muse: Callable[[np.ndarray], xr.Dataset] - bounds: Tuple[Optional[float], Optional[float]] = (0, np.inf) - A_ub: Optional[np.ndarray] = None - b_ub: Optional[np.ndarray] = None - A_eq: Optional[np.ndarray] = None - b_eq: Optional[np.ndarray] = None + bounds: tuple[float | None, float | None] = (0, np.inf) + A_ub: np.ndarray | None = None + b_ub: np.ndarray | None = None + A_eq: np.ndarray | None = None + b_eq: np.ndarray | None = None @classmethod def factory( @@ -1046,7 +1039,7 @@ def _unified_dataset( return data.transpose(*data.dims) @staticmethod - def _selected_quantity(data: xr.Dataset, name: Text) -> xr.Dataset: + def _selected_quantity(data: xr.Dataset, name: str) -> xr.Dataset: result = cast( xr.Dataset, data[[u for u in data.data_vars if str(u).startswith(name)]] ) @@ -1080,14 +1073,14 @@ def extract_bA(constraints, *kinds): capa_constraints = [reshape(capacities[i]) for i in indices] prod_constraints = [reshape(productions[i]) for i in indices] if capa_constraints: - A: Optional[np.ndarray] = np.concatenate( + A: np.ndarray | None = np.concatenate( ( np.concatenate(capa_constraints, axis=0), np.concatenate(prod_constraints, axis=0), ), axis=1, ) - b: Optional[np.ndarray] = np.concatenate( + b: np.ndarray | None = np.concatenate( [bs[i].stack(constraint=sorted(bs[i].dims)) for i in indices], axis=0, ) @@ -1119,7 +1112,7 @@ def extract_bA(constraints, *kinds): @staticmethod def _back_to_muse_quantity( - x: np.ndarray, template: Union[xr.DataArray, xr.Dataset] + x: np.ndarray, template: xr.DataArray | xr.Dataset ) -> xr.DataArray: result = xr.DataArray( x.reshape(template.shape), coords=template.coords, dims=template.dims diff --git a/src/muse/data/example/default_timeslice/output.py b/src/muse/data/example/default_timeslice/output.py index 1b5785cb0..95459f1f1 100644 --- a/src/muse/data/example/default_timeslice/output.py +++ b/src/muse/data/example/default_timeslice/output.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Text +from typing import Optional import xarray as xr from muse.outputs.sector import market_quantity, register_output_quantity @@ -9,8 +9,8 @@ def supply_timeslice( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - sum_over: Optional[List[Text]] = None, - drop: Optional[List[Text]] = None, + sum_over: Optional[list[str]] = None, + drop: Optional[list[str]] = None, rounding: int = 4, ) -> xr.DataArray: """Current supply.""" @@ -29,8 +29,8 @@ def consumption_timeslice( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - sum_over: Optional[List[Text]] = None, - drop: Optional[List[Text]] = None, + sum_over: Optional[list[str]] = None, + drop: Optional[list[str]] = None, rounding: int = 4, ) -> xr.DataArray: """Current consumption.""" diff --git a/src/muse/decisions.py b/src/muse/decisions.py index 03255bce7..6c62b922f 100644 --- a/src/muse/decisions.py +++ b/src/muse/decisions.py @@ -37,15 +37,11 @@ def weighted_sum(objectives: Dataset, parameters: Any, **kwargs) -> DataArray: "single_objective", "factory", ] +from collections.abc import Mapping, MutableMapping, Sequence from typing import ( Any, Callable, - Mapping, - MutableMapping, Optional, - Sequence, - Text, - Tuple, Union, ) @@ -53,7 +49,7 @@ def weighted_sum(objectives: Dataset, parameters: Any, **kwargs) -> DataArray: from muse.registration import registrator -PARAMS_TYPE = Sequence[Tuple[Text, bool, float]] +PARAMS_TYPE = Sequence[tuple[str, bool, float]] """Standard decision parameter type. Until MUSE input is more flexible, we need to be able to translate from this @@ -64,7 +60,7 @@ def weighted_sum(objectives: Dataset, parameters: Any, **kwargs) -> DataArray: DECISION_SIGNATURE = Callable[[Dataset, PARAMS_TYPE], DataArray] """Signature of functions implementing decisions.""" -DECISIONS: MutableMapping[Text, DECISION_SIGNATURE] = {} +DECISIONS: MutableMapping[str, DECISION_SIGNATURE] = {} """Dictionary of decision functions. Decision functions aggregate separate objectives into a single number per @@ -73,7 +69,7 @@ def weighted_sum(objectives: Dataset, parameters: Any, **kwargs) -> DataArray: @registrator(registry=DECISIONS, loglevel="info") -def register_decision(function: DECISION_SIGNATURE, name: Text): +def register_decision(function: DECISION_SIGNATURE, name: str): """Decorator to register a function as a decision. Registers a function as a decision so that it can be applied easily when aggregating @@ -100,13 +96,11 @@ def coeff_sign(minimise: bool, coeff: Any): return coeff if minimise else -coeff -def factory(settings: Union[Text, Mapping] = "mean") -> Callable: +def factory(settings: Union[str, Mapping] = "mean") -> Callable: """Creates a decision method based on the input settings.""" - from typing import Dict - - if isinstance(settings, Text): + if isinstance(settings, str): function = DECISIONS[settings] - params: Dict = {} + params: dict = {} else: function = DECISIONS[settings["name"]] params = {k: v for k, v in settings.items() if k != "name"} @@ -127,7 +121,7 @@ def mean(objectives: Dataset, *args, **kwargs) -> DataArray: @register_decision -def weighted_sum(objectives: Dataset, parameters: Mapping[Text, float]) -> DataArray: +def weighted_sum(objectives: Dataset, parameters: Mapping[str, float]) -> DataArray: r"""Weighted sum over normalized objectives. The objectives are each normalized to [0, 1] over the `replacement` @@ -173,7 +167,7 @@ def weighted_sum(objectives: Dataset, parameters: Mapping[Text, float]) -> DataA @register_decision(name="lexo") def lexical_comparison( - objectives: Dataset, parameters: Union[PARAMS_TYPE, Sequence[Tuple[Text, float]]] + objectives: Dataset, parameters: Union[PARAMS_TYPE, Sequence[tuple[str, float]]] ) -> DataArray: """Lexical comparison over the objectives. @@ -201,7 +195,7 @@ def lexical_comparison( @register_decision(name="retro_lexo") def retro_lexical_comparison( - objectives: Dataset, parameters: Union[PARAMS_TYPE, Sequence[Tuple[Text, float]]] + objectives: Dataset, parameters: Union[PARAMS_TYPE, Sequence[tuple[str, float]]] ) -> DataArray: """Lexical comparison over the objectives. @@ -230,7 +224,7 @@ def retro_lexical_comparison( def _epsilon_constraints( - objectives: Dataset, optimize: Text, mask: Optional[Any] = None, **epsilons + objectives: Dataset, optimize: str, mask: Optional[Any] = None, **epsilons ) -> DataArray: """Minimizes one objective subject to constraints on other objectives.""" constraints = True @@ -246,7 +240,7 @@ def _epsilon_constraints( @register_decision(name=("epsilon", "epsilon_con")) def epsilon_constraints( objectives: Dataset, - parameters: Union[PARAMS_TYPE, Sequence[Tuple[Text, bool, float]]], + parameters: Union[PARAMS_TYPE, Sequence[tuple[str, bool, float]]], mask: Optional[Any] = None, ) -> DataArray: r"""Minimizes first objective subject to constraints on other objectives. @@ -309,7 +303,7 @@ def transform(name, minimize, epsilon=None): @register_decision(name=("single", "singleObj")) def single_objective( objectives: Dataset, - parameters: Union[Text, Tuple[Text, bool], Tuple[Text, bool, float], PARAMS_TYPE], + parameters: Union[str, tuple[str, bool], tuple[str, bool, float], PARAMS_TYPE], ) -> DataArray: """Single objective decision method. @@ -324,9 +318,9 @@ def single_objective( - A tuple (string, bool, factor): defaults to standard sequence `[(string, direction, factor)]` """ - if isinstance(parameters, Text): + if isinstance(parameters, str): params = parameters, 1, 1 - elif len(parameters) == 1 and isinstance(parameters[0], Text): + elif len(parameters) == 1 and isinstance(parameters[0], str): params = parameters[0], 1, 1 elif len(parameters) == 1: params = parameters[0] diff --git a/src/muse/decorators.py b/src/muse/decorators.py index aeb2802c1..48c0d4a75 100644 --- a/src/muse/decorators.py +++ b/src/muse/decorators.py @@ -1,12 +1,13 @@ +from collections.abc import Mapping from logging import getLogger -from typing import Callable, Mapping, Text +from typing import Callable from muse.registration import registrator SETTINGS_CHECKS_SIGNATURE = Callable[[dict], None] """settings checks signature.""" -SETTINGS_CHECKS: Mapping[Text, SETTINGS_CHECKS_SIGNATURE] = {} +SETTINGS_CHECKS: Mapping[str, SETTINGS_CHECKS_SIGNATURE] = {} """Dictionary of settings checks.""" @@ -26,7 +27,7 @@ def register_settings_check(function: SETTINGS_CHECKS_SIGNATURE): def decorated(settings) -> None: result = function(settings) - msg = " {} PASSED".format(function.__name__) + msg = f" {function.__name__} PASSED" getLogger(__name__).info(msg) return result diff --git a/src/muse/demand_matching.py b/src/muse/demand_matching.py index 270c2594a..0a9c94d5f 100644 --- a/src/muse/demand_matching.py +++ b/src/muse/demand_matching.py @@ -46,7 +46,7 @@ __all__ = ["demand_matching"] -from typing import Optional, Set +from typing import Optional import pandas as pd from xarray import DataArray @@ -56,7 +56,7 @@ def demand_matching( demand: DataArray, cost: DataArray, *constraints: DataArray, - protected_dims: Optional[Set] = None, + protected_dims: Optional[set] = None, ) -> DataArray: r"""Demand matching over heterogeneous dimensions. diff --git a/src/muse/demand_share.py b/src/muse/demand_share.py index 9cd5bf6a6..03865a606 100644 --- a/src/muse/demand_share.py +++ b/src/muse/demand_share.py @@ -45,15 +45,11 @@ def demand_share( "unmet_forecasted_demand", "DEMAND_SHARE_SIGNATURE", ] +from collections.abc import Hashable, Mapping, MutableMapping, Sequence from typing import ( Any, Callable, - Hashable, - Mapping, - MutableMapping, Optional, - Sequence, - Text, Union, cast, ) @@ -73,7 +69,7 @@ def demand_share( ] """Demand share signature.""" -DEMAND_SHARE: MutableMapping[Text, DEMAND_SHARE_SIGNATURE] = {} +DEMAND_SHARE: MutableMapping[str, DEMAND_SHARE_SIGNATURE] = {} """Dictionary of demand share functions.""" @@ -84,11 +80,11 @@ def register_demand_share(function: DEMAND_SHARE_SIGNATURE): def factory( - settings: Optional[Union[Text, Mapping[Text, Any]]] = None, + settings: Optional[Union[str, Mapping[str, Any]]] = None, ) -> DEMAND_SHARE_SIGNATURE: - if settings is None or isinstance(settings, Text): + if settings is None or isinstance(settings, str): name = settings or "new_and_retro" - params: Mapping[Text, Any] = {} + params: Mapping[str, Any] = {} else: name = settings.get("name", "new_and_retro") params = {k: v for k, v in settings.items() if k != "name"} @@ -116,7 +112,7 @@ def new_and_retro( agents: Sequence[AbstractAgent], market: xr.Dataset, technologies: xr.Dataset, - production: Union[Text, Mapping, Callable] = "maximum_production", + production: Union[str, Mapping, Callable] = "maximum_production", current_year: Optional[int] = None, forecast: int = 5, ) -> xr.DataArray: @@ -327,7 +323,7 @@ def standard_demand( agents: Sequence[AbstractAgent], market: xr.Dataset, technologies: xr.Dataset, - production: Union[Text, Mapping, Callable] = "maximum_production", + production: Union[str, Mapping, Callable] = "maximum_production", current_year: Optional[int] = None, forecast: int = 5, ) -> xr.DataArray: @@ -432,7 +428,7 @@ def unmet_forecasted_demand( market: xr.Dataset, technologies: xr.Dataset, current_year: Optional[int] = None, - production: Union[Text, Mapping, Callable] = "maximum_production", + production: Union[str, Mapping, Callable] = "maximum_production", forecast: int = 5, ) -> xr.DataArray: """Forecast demand that cannot be serviced by non-decommissioned current assets.""" @@ -512,7 +508,7 @@ def unmet_demand( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - production: Union[Text, Mapping, Callable] = "maximum_production", + production: Union[str, Mapping, Callable] = "maximum_production", ): r"""Share of the demand that cannot be serviced by the existing assets. @@ -592,7 +588,7 @@ def new_and_retro_demands( capacity: xr.DataArray, market: xr.Dataset, technologies: xr.Dataset, - production: Union[Text, Mapping, Callable] = "maximum_production", + production: Union[str, Mapping, Callable] = "maximum_production", current_year: Optional[int] = None, forecast: int = 5, ) -> xr.Dataset: @@ -662,7 +658,7 @@ def new_demand( capacity: xr.DataArray, market: xr.Dataset, technologies: xr.Dataset, - production: Union[Text, Mapping, Callable] = "maximum_production", + production: Union[str, Mapping, Callable] = "maximum_production", current_year: Optional[int] = None, forecast: int = 5, ) -> xr.DataArray: diff --git a/src/muse/examples.py b/src/muse/examples.py index 5db4e6c3c..a888763ff 100644 --- a/src/muse/examples.py +++ b/src/muse/examples.py @@ -27,7 +27,7 @@ from logging import getLogger from pathlib import Path -from typing import List, Optional, Text, Union, cast +from typing import Optional, Union, cast import numpy as np import xarray as xr @@ -45,12 +45,12 @@ def example_data_dir() -> Path: return Path(muse.__file__).parent / "data" / "example" -def available_examples() -> List[str]: +def available_examples() -> list[str]: """List examples available in the examples folder.""" return [d.stem for d in example_data_dir().iterdir() if d.is_dir()] -def model(name: Text = "default") -> MCA: +def model(name: str = "default") -> MCA: """Fully constructs a given example model.""" from tempfile import TemporaryDirectory @@ -66,8 +66,8 @@ def model(name: Text = "default") -> MCA: def copy_model( - name: Text = "default", - path: Optional[Union[Text, Path]] = None, + name: str = "default", + path: Optional[Union[str, Path]] = None, overwrite: bool = False, ) -> Path: """Copy model files to given path. @@ -86,14 +86,14 @@ def copy_model( path = Path() if path is None else Path(path) if path.exists() and not path.is_dir(): - raise IOError(f"{path} exists and is not a directory") + raise OSError(f"{path} exists and is not a directory") path /= "model" if path.exists(): if not path.is_dir(): - raise IOError(f"{path} exists and is not a directory") + raise OSError(f"{path} exists and is not a directory") elif not overwrite: - raise IOError(f"{path} exists and ``overwrite`` is not allowed") + raise OSError(f"{path} exists and ``overwrite`` is not allowed") rmtree(path) if name.lower() == "default": @@ -111,7 +111,7 @@ def copy_model( return path -def technodata(sector: Text, model: Text = "default") -> xr.Dataset: +def technodata(sector: str, model: str = "default") -> xr.Dataset: """Technology for a sector of a given example model.""" from tempfile import TemporaryDirectory @@ -129,7 +129,7 @@ def technodata(sector: Text, model: Text = "default") -> xr.Dataset: return read_technodata(settings, sector) -def search_space(sector: Text, model: Text = "default") -> xr.DataArray: +def search_space(sector: str, model: str = "default") -> xr.DataArray: """Determines which technology is considered for which asset. Used in constraints or during investment. @@ -139,7 +139,7 @@ def search_space(sector: Text, model: Text = "default") -> xr.DataArray: return _nontrade_search_space(sector, model) -def sector(sector: Text, model: Text = "default") -> AbstractSector: +def sector(sector: str, model: str = "default") -> AbstractSector: """Loads a given sector from a given example model.""" from tempfile import TemporaryDirectory @@ -153,7 +153,7 @@ def sector(sector: Text, model: Text = "default") -> AbstractSector: return SECTORS_REGISTERED[kind](sector, settings) -def available_sectors(model: Text = "default") -> List[Text]: +def available_sectors(model: str = "default") -> list[str]: """Sectors in this particular model.""" from tempfile import TemporaryDirectory @@ -165,7 +165,7 @@ def available_sectors(model: Text = "default") -> List[Text]: return [u for u in undo_damage(settings).keys() if u != "list"] -def mca_market(model: Text = "default") -> xr.Dataset: +def mca_market(model: str = "default") -> xr.Dataset: """Initial market as seen by the MCA.""" from tempfile import TemporaryDirectory @@ -202,7 +202,7 @@ def mca_market(model: Text = "default") -> xr.Dataset: return cast(xr.Dataset, market) -def residential_market(model: Text = "default") -> xr.Dataset: +def residential_market(model: str = "default") -> xr.Dataset: """Initial market as seen by the residential sector.""" from muse.mca import single_year_iteration @@ -238,7 +238,7 @@ def random_agent_assets(rng: np.random.Generator): return result -def matching_market(sector: Text, model: Text = "default") -> xr.Dataset: +def matching_market(sector: str, model: str = "default") -> xr.Dataset: """Market with a demand matching the maximum production from a sector.""" from muse.examples import sector as load_sector from muse.quantities import consumption, maximum_production @@ -366,7 +366,7 @@ def _copy_trade(path: Path): copyfile(example_data_dir() / "trade" / "settings.toml", path / "settings.toml") -def _trade_search_space(sector: Text, model: Text = "default") -> xr.DataArray: +def _trade_search_space(sector: str, model: str = "default") -> xr.DataArray: from muse.agents import Agent from muse.examples import sector as load_sector from muse.sectors import Sector @@ -392,7 +392,7 @@ def _trade_search_space(sector: Text, model: Text = "default") -> xr.DataArray: ) -def _nontrade_search_space(sector: Text, model: Text = "default") -> xr.DataArray: +def _nontrade_search_space(sector: str, model: str = "default") -> xr.DataArray: from numpy import ones technology = technodata(sector, model).technology diff --git a/src/muse/filters.py b/src/muse/filters.py index 111d79c00..ba451d03c 100644 --- a/src/muse/filters.py +++ b/src/muse/filters.py @@ -90,14 +90,11 @@ def search_space_initializer( "initialize_from_technologies", ] +from collections.abc import Mapping, MutableMapping, Sequence from typing import ( Any, Callable, - Mapping, - MutableMapping, Optional, - Sequence, - Text, Union, cast, ) @@ -111,14 +108,14 @@ def search_space_initializer( SSF_SIGNATURE = Callable[[Agent, xr.DataArray, xr.Dataset, xr.Dataset], xr.DataArray] """ Search space filter signature """ -SEARCH_SPACE_FILTERS: MutableMapping[Text, SSF_SIGNATURE] = {} +SEARCH_SPACE_FILTERS: MutableMapping[str, SSF_SIGNATURE] = {} """Filters for selecting technology search spaces.""" SSI_SIGNATURE = Callable[[Agent, xr.DataArray, xr.Dataset, xr.Dataset], xr.DataArray] """ Search space initializer signature """ -SEARCH_SPACE_INITIALIZERS: MutableMapping[Text, SSI_SIGNATURE] = {} +SEARCH_SPACE_INITIALIZERS: MutableMapping[str, SSI_SIGNATURE] = {} """Functions to create an initial search-space.""" @@ -165,8 +162,8 @@ def decorated(agent: Agent, *args, **kwargs) -> xr.DataArray: def factory( - settings: Optional[Union[Text, Mapping, Sequence[Union[Text, Mapping]]]] = None, - separator: Text = "->", + settings: Optional[Union[str, Mapping, Sequence[Union[str, Mapping]]]] = None, + separator: str = "->", ): """Creates filters from input TOML data. @@ -198,17 +195,17 @@ def factory( from functools import partial if settings is None: - parameters: Sequence[Mapping[Text, Any]] = [] + parameters: Sequence[Mapping[str, Any]] = [] elif isinstance(settings, Mapping): parameters = [settings] - elif isinstance(settings, Text): + elif isinstance(settings, str): parameters = [{"name": name.strip()} for name in settings.split(separator)] else: parameters = [ - {"name": item} if isinstance(item, Text) else item for item in settings + {"name": item} if isinstance(item, str) else item for item in settings ] if len(parameters) == 0 or parameters[0]["name"] not in SEARCH_SPACE_INITIALIZERS: - initial_settings: Mapping[Text, Text] = {"name": "initialize_from_technologies"} + initial_settings: Mapping[str, str] = {"name": "initialize_from_technologies"} else: initial_settings, parameters = cast(Mapping, parameters[0]), parameters[1:] @@ -242,7 +239,7 @@ def same_enduse( search_space: xr.DataArray, technologies: xr.Dataset, *args, - enduse_label: Text = "service", + enduse_label: str = "service", **kwargs, ) -> xr.DataArray: """Only allow for technologies with at least the same end-use.""" @@ -335,7 +332,7 @@ def maturity( search_space: xr.DataArray, technologies: xr.Dataset, market: xr.Dataset, - enduse_label: Text = "service", + enduse_label: str = "service", **kwargs, ) -> xr.DataArray: """Only allows technologies that have achieve a given market share. @@ -367,7 +364,7 @@ def spend_limit( search_space: xr.DataArray, technologies: xr.Dataset, market: xr.Dataset, - enduse_label: Text = "service", + enduse_label: str = "service", **kwargs, ) -> xr.DataArray: """Only allows technologies that have achieve a given market share. @@ -463,7 +460,7 @@ def initialize_from_assets( demand: xr.DataArray, technologies: xr.Dataset, *args, - coords: Sequence[Text] = ("region", "technology"), + coords: Sequence[str] = ("region", "technology"), **kwargs, ): """Initialize a search space from existing technologies.""" diff --git a/src/muse/hooks.py b/src/muse/hooks.py index eaca96c7f..7456b3937 100644 --- a/src/muse/hooks.py +++ b/src/muse/hooks.py @@ -11,20 +11,21 @@ "housekeeping_factory", "asset_merge_factory", ] -from typing import Callable, Mapping, MutableMapping, Text, Union +from collections.abc import Mapping, MutableMapping +from typing import Callable, Union from xarray import Dataset from muse.agents import Agent from muse.registration import registrator -INITIAL_ASSET_TRANSFORM: MutableMapping[Text, Callable] = {} +INITIAL_ASSET_TRANSFORM: MutableMapping[str, Callable] = {} """ Transform at the start of each step. """ -FINAL_ASSET_TRANSFORM: MutableMapping[Text, Callable] = {} +FINAL_ASSET_TRANSFORM: MutableMapping[str, Callable] = {} """ Transform at the end of each step, including new assets. """ -def housekeeping_factory(settings: Union[Text, Mapping] = "noop") -> Callable: +def housekeeping_factory(settings: Union[str, Mapping] = "noop") -> Callable: """Returns a function for performing initial housekeeping. For instance, remove technologies with no capacity now or in the future. @@ -33,7 +34,7 @@ def housekeeping_factory(settings: Union[Text, Mapping] = "noop") -> Callable: """ from muse.agents import AbstractAgent - if isinstance(settings, Text): + if isinstance(settings, str): name = settings params: Mapping = {} else: @@ -48,7 +49,7 @@ def initial_assets_transform(agent: AbstractAgent, assets: Dataset) -> Dataset: return initial_assets_transform -def asset_merge_factory(settings: Union[Text, Mapping] = "new") -> Callable: +def asset_merge_factory(settings: Union[str, Mapping] = "new") -> Callable: """Returns a function for merging new investments into assets. Available merging functions should be registered with @@ -60,7 +61,7 @@ def asset_merge_factory(settings: Union[Text, Mapping] = "new") -> Callable: Available housekeeping functions should be registered with :py:func:`@register_initial_asset_transform`. """ - if isinstance(settings, Text): + if isinstance(settings, str): name = settings params: Mapping = {} else: diff --git a/src/muse/interactions.py b/src/muse/interactions.py index f6302d6c4..091311cf4 100644 --- a/src/muse/interactions.py +++ b/src/muse/interactions.py @@ -22,15 +22,16 @@ "transfer_assets", ] -from typing import Callable, List, Mapping, Optional, Sequence, Text, Tuple, Union +from collections.abc import Mapping, Sequence +from typing import Callable, Optional, Union from muse.agents import AbstractAgent, Agent from muse.errors import NoInteractionsFound from muse.registration import registrator -AGENT_INTERACTIONS: Mapping[Text, Callable] = {} +AGENT_INTERACTIONS: Mapping[str, Callable] = {} """All interaction between a single agents and its interactees.""" -INTERACTION_NETS: Mapping[Text, Callable] = {} +INTERACTION_NETS: Mapping[str, Callable] = {} """All functions to computes lists of agents interaction with each other.""" INTERACTION_NET = Sequence[Sequence[Agent]] @@ -86,7 +87,7 @@ def register_agent_interaction(function: AGENT_INTERACTION_SIGNATURE): def factory( - inputs: Optional[Sequence[Union[Mapping, Tuple[Text, Text]]]] = None, + inputs: Optional[Sequence[Union[Mapping, tuple[str, str]]]] = None, ) -> Callable[[Sequence[AbstractAgent]], None]: """Creates an interaction functor.""" if inputs is None: @@ -100,10 +101,10 @@ def factory( else: net_params, action_params = params - if isinstance(net_params, Text): + if isinstance(net_params, str): net_params = {"name": net_params} - if isinstance(action_params, Text): + if isinstance(action_params, str): action_params = {"name": action_params} net = INTERACTION_NETS[net_params["name"]] @@ -138,10 +139,10 @@ def compute_interactions(agents: Sequence[AbstractAgent]) -> None: def agents_groupby( - agents: Sequence[Agent], attributes: Sequence[Text] -) -> Mapping[Tuple, List[Agent]]: + agents: Sequence[Agent], attributes: Sequence[str] +) -> Mapping[tuple, list[Agent]]: attr_list = [tuple(getattr(agent, attr) for attr in attributes) for agent in agents] - result: Mapping[Tuple, List[Agent]] = {tuple(n): [] for n in attr_list} + result: Mapping[tuple, list[Agent]] = {tuple(n): [] for n in attr_list} for attrs, agent in zip(attr_list, agents): result[attrs].append(agent) return result @@ -149,7 +150,7 @@ def agents_groupby( @register_interaction_net(name=["default", "new_to_retro"]) def new_to_retro_net( - agents: Sequence[Agent], first_category: Text = "newcapa" + agents: Sequence[Agent], first_category: str = "newcapa" ) -> INTERACTION_NET: """Interactions between new and retrofit agents.""" groups = agents_groupby(agents, ("region", "name")) diff --git a/src/muse/investments.py b/src/muse/investments.py index 41a85385b..d6ed8248a 100644 --- a/src/muse/investments.py +++ b/src/muse/investments.py @@ -47,14 +47,11 @@ def investment( "register_investment", "INVESTMENT_SIGNATURE", ] +from collections.abc import Mapping, MutableMapping from typing import ( Any, Callable, - List, - Mapping, - MutableMapping, Optional, - Text, Union, cast, ) @@ -69,12 +66,12 @@ def investment( from muse.registration import registrator INVESTMENT_SIGNATURE = Callable[ - [xr.DataArray, xr.DataArray, xr.Dataset, List[Constraint], KwArg(Any)], + [xr.DataArray, xr.DataArray, xr.Dataset, list[Constraint], KwArg(Any)], Union[xr.DataArray, xr.Dataset], ] """Investment signature. """ -INVESTMENTS: MutableMapping[Text, INVESTMENT_SIGNATURE] = {} +INVESTMENTS: MutableMapping[str, INVESTMENT_SIGNATURE] = {} """Dictionary of investment functions.""" @@ -94,7 +91,7 @@ def decorated( costs: xr.DataArray, search_space: xr.DataArray, technologies: xr.Dataset, - constraints: List[Constraint], + constraints: list[Constraint], **kwargs, ) -> xr.DataArray: result = function(costs, search_space, technologies, constraints, **kwargs) @@ -113,13 +110,11 @@ def decorated( return decorated -def factory(settings: Optional[Union[Text, Mapping]] = None) -> Callable: - from typing import Dict - +def factory(settings: Optional[Union[str, Mapping]] = None) -> Callable: if settings is None: name = "match_demand" - params: Dict = {} - elif isinstance(settings, Text): + params: dict = {} + elif isinstance(settings, str): name = settings params = {} else: @@ -127,7 +122,7 @@ def factory(settings: Optional[Union[Text, Mapping]] = None) -> Callable: params = {k: v for k, v in settings.items() if k != "name"} top = params.get("timeslice_op", "max") - if isinstance(top, Text): + if isinstance(top, str): if top.lower() == "max": def timeslice_op(x: xr.DataArray) -> xr.DataArray: @@ -150,7 +145,7 @@ def timeslice_op(x: xr.DataArray) -> xr.DataArray: def compute_investment( search: xr.Dataset, technologies: xr.Dataset, - constraints: List[Constraint], + constraints: list[Constraint], **kwargs, ) -> xr.DataArray: """Computes investment needed to fulfill demand. @@ -182,7 +177,7 @@ def cliff_retirement_profile( technical_life: xr.DataArray, current_year: int = 0, protected: int = 0, - interpolation: Text = "linear", + interpolation: str = "linear", **kwargs, ) -> xr.DataArray: """Cliff-like retirement profile from current year. @@ -251,7 +246,7 @@ def adhoc_match_demand( costs: xr.DataArray, search_space: xr.DataArray, technologies: xr.Dataset, - constraints: List[Constraint], + constraints: list[Constraint], year: int, timeslice_op: Optional[Callable[[xr.DataArray], xr.DataArray]] = None, ) -> xr.DataArray: @@ -259,11 +254,9 @@ def adhoc_match_demand( from muse.quantities import capacity_in_use, maximum_production from muse.timeslices import QuantityType, convert_timeslice - demand = next((c for c in constraints if c.name == "demand")).b + demand = next(c for c in constraints if c.name == "demand").b - max_capacity = next( - (c for c in constraints if c.name == "max capacity expansion") - ).b + max_capacity = next(c for c in constraints if c.name == "max capacity expansion").b max_prod = maximum_production( technologies, max_capacity, @@ -306,7 +299,7 @@ def scipy_match_demand( costs: xr.DataArray, search_space: xr.DataArray, technologies: xr.Dataset, - constraints: List[Constraint], + constraints: list[Constraint], year: Optional[int] = None, timeslice_op: Optional[Callable[[xr.DataArray], xr.DataArray]] = None, **options, @@ -331,7 +324,7 @@ def scipy_match_demand( techs = technologies.sel(year=year).drop_vars("year") else: techs = technologies - timeslice = next((cs.timeslice for cs in constraints if "timeslice" in cs.dims)) + timeslice = next(cs.timeslice for cs in constraints if "timeslice" in cs.dims) adapter = ScipyAdapter.factory( techs, cast(np.ndarray, costs), timeslice, *constraints @@ -366,7 +359,7 @@ def cvxopt_match_demand( costs: xr.DataArray, search_space: xr.DataArray, technologies: xr.Dataset, - constraints: List[Constraint], + constraints: list[Constraint], year: Optional[int] = None, timeslice_op: Optional[Callable[[xr.DataArray], xr.DataArray]] = None, **options, @@ -401,7 +394,7 @@ def default_to_scipy(): if "timeslice" in costs.dims and timeslice_op is not None: costs = timeslice_op(costs) - timeslice = next((cs.timeslice for cs in constraints if "timeslice" in cs.dims)) + timeslice = next(cs.timeslice for cs in constraints if "timeslice" in cs.dims) adapter = ScipyAdapter.factory( techs, -cast(np.ndarray, costs), timeslice, *constraints ) diff --git a/src/muse/mca.py b/src/muse/mca.py index fde3735e6..ac7a27e2a 100644 --- a/src/muse/mca.py +++ b/src/muse/mca.py @@ -1,16 +1,11 @@ from __future__ import annotations +from collections.abc import Mapping, Sequence from pathlib import Path from typing import ( Any, Callable, - List, - Mapping, NamedTuple, - Optional, - Sequence, - Text, - Union, cast, ) @@ -21,7 +16,7 @@ from muse.sectors import SECTORS_REGISTERED, AbstractSector -class MCA(object): +class MCA: """Market Clearing Algorithm. The market clearing algorithm is the main object implementing the MUSE model. It is @@ -30,7 +25,7 @@ class MCA(object): """ @classmethod - def factory(cls, settings: Union[Text, Path, Mapping, Any]) -> MCA: + def factory(cls, settings: str | Path | Mapping | Any) -> MCA: """Loads MCA from input settings and input files. Arguments: @@ -45,7 +40,7 @@ def factory(cls, settings: Union[Text, Path, Mapping, Any]) -> MCA: from muse.readers import read_settings from muse.readers.toml import convert - if isinstance(settings, (Text, Path)): + if isinstance(settings, (str, Path)): settings = read_settings(settings) # type: ignore elif isinstance(settings, Mapping): settings = convert(settings) @@ -120,25 +115,25 @@ def factory(cls, settings: Union[Text, Path, Mapping, Any]) -> MCA: def __init__( self, - sectors: List[AbstractSector], + sectors: list[AbstractSector], market: Dataset, - outputs: Optional[Callable[[List[AbstractSector], Dataset], Any]] = None, - outputs_cache: Optional[OutputCache] = None, + outputs: Callable[[list[AbstractSector], Dataset], Any] | None = None, + outputs_cache: OutputCache | None = None, time_framework: Sequence[int] = list(range(2010, 2100, 10)), equilibrium: bool = True, - equilibrium_variable: Text = "demand", + equilibrium_variable: str = "demand", maximum_iterations: int = 3, tolerance: float = 0.1, tolerance_unmet_demand: float = -0.1, - excluded_commodities: Optional[Sequence[Text]] = None, - carbon_budget: Optional[Sequence] = None, - carbon_price: Optional[Sequence] = None, - carbon_commodities: Optional[Sequence[Text]] = None, + excluded_commodities: Sequence[str] | None = None, + carbon_budget: Sequence | None = None, + carbon_price: Sequence | None = None, + carbon_commodities: Sequence[str] | None = None, debug: bool = False, control_undershoot: bool = True, control_overshoot: bool = True, - carbon_method: Text = "fitting", - method_options: Optional[Mapping] = None, + carbon_method: str = "fitting", + method_options: Mapping | None = None, ): """Market clearing algorithm class which rules the whole MUSE.""" from logging import getLogger @@ -151,7 +146,7 @@ def __init__( getLogger(__name__).info("MCA Initialisation") - self.sectors: List[AbstractSector] = list(sectors) + self.sectors: list[AbstractSector] = list(sectors) self.market = market # Simulation flow parameters @@ -197,8 +192,8 @@ def __init__( def find_equilibrium( self, market: Dataset, - sectors: Optional[List[AbstractSector]] = None, - maxiter: Optional[int] = None, + sectors: list[AbstractSector] | None = None, + maxiter: int | None = None, ) -> FindEquilibriumResults: """Specialised version of the find_equilibrium function. @@ -249,7 +244,7 @@ def update_carbon_budget(self, market: Dataset, year_idx: int) -> float: self.control_undershoot, ) - def update_carbon_price(self, market) -> Optional[float]: + def update_carbon_price(self, market) -> float | None: """Calculates the updated carbon price, if required. If the emission calculated for the next time period is larger than the @@ -442,11 +437,11 @@ class SingleYearIterationResult(NamedTuple): """ market: Dataset - sectors: List[AbstractSector] + sectors: list[AbstractSector] def single_year_iteration( - market: Dataset, sectors: List[AbstractSector] + market: Dataset, sectors: list[AbstractSector] ) -> SingleYearIterationResult: """Runs one iteration of the sectors (runs each sector once). @@ -511,17 +506,17 @@ class FindEquilibriumResults(NamedTuple): converged: bool market: Dataset - sectors: List[AbstractSector] + sectors: list[AbstractSector] def find_equilibrium( market: Dataset, - sectors: List[AbstractSector], + sectors: list[AbstractSector], maxiter: int = 3, tol: float = 0.1, - equilibrium_variable: Text = "demand", + equilibrium_variable: str = "demand", tol_unmet_demand: float = -0.1, - excluded_commodities: Optional[Sequence] = None, + excluded_commodities: Sequence | None = None, equilibrium: bool = True, ) -> FindEquilibriumResults: """Runs the equilibrium loop. @@ -672,8 +667,8 @@ def check_equilibrium( market: Dataset, int_market: Dataset, tolerance: float, - equilibrium_variable: Text, - year: Optional[int] = None, + equilibrium_variable: str, + year: int | None = None, ) -> bool: """Checks if equilibrium has been reached. diff --git a/src/muse/objectives.py b/src/muse/objectives.py index 72b00d98f..87af8f9a3 100644 --- a/src/muse/objectives.py +++ b/src/muse/objectives.py @@ -71,7 +71,8 @@ def comfort( "factory", ] -from typing import Any, Callable, Mapping, MutableMapping, Sequence, Text, Union, cast +from collections.abc import Mapping, MutableMapping, Sequence +from typing import Any, Callable, Union, cast import numpy as np import xarray as xr @@ -87,14 +88,14 @@ def comfort( ] """Objectives signature.""" -OBJECTIVES: MutableMapping[Text, OBJECTIVE_SIGNATURE] = {} +OBJECTIVES: MutableMapping[str, OBJECTIVE_SIGNATURE] = {} """Dictionary of objectives when selecting replacement technology.""" -def objective_factory(settings=Union[Text, Mapping]): +def objective_factory(settings=Union[str, Mapping]): from functools import partial - if isinstance(settings, Text): + if isinstance(settings, str): params = dict(name=settings) else: params = dict(**settings) @@ -104,7 +105,7 @@ def objective_factory(settings=Union[Text, Mapping]): def factory( - settings: Union[Text, Mapping, Sequence[Union[Text, Mapping]]] = "LCOE", + settings: Union[str, Mapping, Sequence[Union[str, Mapping]]] = "LCOE", ) -> Callable: """Creates a function computing multiple objectives. @@ -114,15 +115,14 @@ def factory( objectives defined by name or by dictionary. """ from logging import getLogger - from typing import Dict, List - if isinstance(settings, Text): - params: List[Dict] = [{"name": settings}] + if isinstance(settings, str): + params: list[dict] = [{"name": settings}] elif isinstance(settings, Mapping): params = [dict(**settings)] else: params = [ - {"name": param} if isinstance(param, Text) else dict(**param) + {"name": param} if isinstance(param, str) else dict(**param) for param in settings ] @@ -178,10 +178,7 @@ def decorated_objective( dtype = result.values.dtype if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)): - msg = "dtype of objective %s is not a number (%s)" % ( - function.__name__, - dtype, - ) + msg = f"dtype of objective {function.__name__} is not a number ({dtype})" getLogger(function.__module__).warning(msg) if "technology" in result.dims: diff --git a/src/muse/outputs/cache.py b/src/muse/outputs/cache.py index 76d018e31..71712cd95 100644 --- a/src/muse/outputs/cache.py +++ b/src/muse/outputs/cache.py @@ -28,16 +28,11 @@ from __future__ import annotations from collections import ChainMap +from collections.abc import Mapping, MutableMapping, Sequence from functools import reduce from operator import attrgetter from typing import ( Callable, - List, - Mapping, - MutableMapping, - Optional, - Sequence, - Text, Union, ) @@ -49,11 +44,11 @@ from muse.sectors import AbstractSector OUTPUT_QUANTITY_SIGNATURE = Callable[ - [List[xr.DataArray]], Union[xr.DataArray, pd.DataFrame] + [list[xr.DataArray]], Union[xr.DataArray, pd.DataFrame] ] """Signature of functions computing quantities for later analysis.""" -OUTPUT_QUANTITIES: MutableMapping[Text, OUTPUT_QUANTITY_SIGNATURE] = {} +OUTPUT_QUANTITIES: MutableMapping[str, OUTPUT_QUANTITY_SIGNATURE] = {} """Quantity for post-simulation analysis.""" CACHE_TOPIC_CHANNEL = "cache_quantity" @@ -76,10 +71,10 @@ def decorated(*args, **kwargs): def cache_quantity( - function: Optional[Callable] = None, - quantity: Union[str, Sequence[str], None] = None, + function: Callable | None = None, + quantity: str | Sequence[str] | None = None, **kwargs: xr.DataArray, -) -> Optional[Callable]: +) -> Callable | None: """Cache one or more quantities to be post-processed later on. This function can be used as a decorator, in which case the quantity input argument @@ -175,8 +170,8 @@ def decorated(*args, **kwargs): def match_quantities( - quantity: Union[str, Sequence[str]], - data: Union[xr.DataArray, xr.Dataset, Sequence[xr.DataArray]], + quantity: str | Sequence[str], + data: xr.DataArray | xr.Dataset | Sequence[xr.DataArray], ) -> Mapping[str, xr.DataArray]: """Matches the quantities with the corresponding data. @@ -196,10 +191,10 @@ def match_quantities( (Mapping[str, xr.DataArray]) A dictionary matching the quantity names with the corresponding data. """ - if isinstance(quantity, Text) and isinstance(data, xr.DataArray): + if isinstance(quantity, str) and isinstance(data, xr.DataArray): return {quantity: data} - elif isinstance(quantity, Text) and isinstance(data, xr.Dataset): + elif isinstance(quantity, str) and isinstance(data, xr.Dataset): return {quantity: data[quantity]} elif isinstance(quantity, Sequence) and isinstance(data, xr.Dataset): @@ -245,10 +240,8 @@ class OutputCache: def __init__( self, *parameters: Mapping, - output_quantities: Optional[ - MutableMapping[Text, OUTPUT_QUANTITY_SIGNATURE] - ] = None, - sectors: Optional[List[AbstractSector]] = None, + output_quantities: MutableMapping[str, OUTPUT_QUANTITY_SIGNATURE] | None = None, + sectors: list[AbstractSector] | None = None, topic: str = CACHE_TOPIC_CHANNEL, ): from muse.outputs.sector import _factory @@ -256,7 +249,7 @@ def __init__( output_quantities = ( OUTPUT_QUANTITIES if output_quantities is None else output_quantities ) - self.agents: MutableMapping[Text, MutableMapping[Text, Text]] = ( + self.agents: MutableMapping[str, MutableMapping[str, str]] = ( extract_agents(sectors) if sectors is not None else {} ) @@ -270,7 +263,7 @@ def __init__( f"Valid quantities are: {list(output_quantities.keys())}" ) - self.to_save: Mapping[str, List[xr.DataArray]] = { + self.to_save: Mapping[str, list[xr.DataArray]] = { p["quantity"]: [] for p in parameters if p["quantity"] in output_quantities } @@ -326,8 +319,8 @@ def consolidate_cache(self, year: int) -> None: def extract_agents( - sectors: List[AbstractSector], -) -> MutableMapping[Text, MutableMapping[Text, Text]]: + sectors: list[AbstractSector], +) -> MutableMapping[str, MutableMapping[str, str]]: """_summary_. Args: @@ -341,7 +334,7 @@ def extract_agents( def extract_agents_internal( sector: AbstractSector, -) -> MutableMapping[Text, MutableMapping[Text, Text]]: +) -> MutableMapping[str, MutableMapping[str, str]]: """Extract simple agent metadata from a sector. Args: @@ -351,7 +344,7 @@ def extract_agents_internal( Mapping[Text, Text]: A dictionary with the uuid of each agent as keys and a dictionary with the name, agent type and agent sector as values. """ - info: MutableMapping[Text, MutableMapping[Text, Text]] = {} + info: MutableMapping[str, MutableMapping[str, str]] = {} sector_name = getattr(sector, "name", "unnamed") agents = sorted(getattr(sector, "agents", []), key=attrgetter("name")) for agent in agents: @@ -367,7 +360,7 @@ def extract_agents_internal( return info -def _aggregate_cache(quantity: Text, data: List[xr.DataArray]) -> pd.DataFrame: +def _aggregate_cache(quantity: str, data: list[xr.DataArray]) -> pd.DataFrame: """Combine a list of DataArrays in a dataframe. The merging gives precedence to the last entries of the list over the first ones. @@ -403,9 +396,9 @@ def check_col(colname: str) -> str: def consolidate_quantity( - quantity: Text, - cached: List[xr.DataArray], - agents: MutableMapping[Text, MutableMapping[Text, Text]], + quantity: str, + cached: list[xr.DataArray], + agents: MutableMapping[str, MutableMapping[str, str]], ) -> pd.DataFrame: """Consolidates the cached quantity into a single DataFrame to save. @@ -444,8 +437,8 @@ def consolidate_quantity( @register_cached_quantity def capacity( - cached: List[xr.DataArray], - agents: MutableMapping[Text, MutableMapping[Text, Text]], + cached: list[xr.DataArray], + agents: MutableMapping[str, MutableMapping[str, str]], ) -> pd.DataFrame: """Consolidates the cached capacities into a single DataFrame to save. @@ -461,8 +454,8 @@ def capacity( @register_cached_quantity def production( - cached: List[xr.DataArray], - agents: MutableMapping[Text, MutableMapping[Text, Text]], + cached: list[xr.DataArray], + agents: MutableMapping[str, MutableMapping[str, str]], ) -> pd.DataFrame: """Consolidates the cached production into a single DataFrame to save. @@ -478,8 +471,8 @@ def production( @register_cached_quantity(name="lifetime_levelized_cost_of_energy") def lcoe( - cached: List[xr.DataArray], - agents: MutableMapping[Text, MutableMapping[Text, Text]], + cached: list[xr.DataArray], + agents: MutableMapping[str, MutableMapping[str, str]], ) -> pd.DataFrame: """Consolidates the cached LCOE into a single DataFrame to save. diff --git a/src/muse/outputs/mca.py b/src/muse/outputs/mca.py index 9424ea314..c93480bf7 100644 --- a/src/muse/outputs/mca.py +++ b/src/muse/outputs/mca.py @@ -16,19 +16,13 @@ def quantity( or an xarray xr.DataArray. """ +from collections.abc import Hashable, Iterable, Mapping, MutableMapping, Sequence from operator import attrgetter from pathlib import Path from typing import ( Any, Callable, - Hashable, - Iterable, - List, - Mapping, - MutableMapping, Optional, - Sequence, - Text, Union, cast, ) @@ -43,14 +37,14 @@ def quantity( from muse.timeslices import QuantityType, convert_timeslice OUTPUT_QUANTITY_SIGNATURE = Callable[ - [xr.Dataset, List[AbstractSector], KwArg(Any)], Union[xr.DataArray, pd.DataFrame] + [xr.Dataset, list[AbstractSector], KwArg(Any)], Union[xr.DataArray, pd.DataFrame] ] """Signature of functions computing quantities for later analysis.""" -OUTPUT_QUANTITIES: MutableMapping[Text, OUTPUT_QUANTITY_SIGNATURE] = {} +OUTPUT_QUANTITIES: MutableMapping[str, OUTPUT_QUANTITY_SIGNATURE] = {} """Quantity for post-simulation analysis.""" -OUTPUTS_PARAMETERS = Union[Text, Mapping] +OUTPUTS_PARAMETERS = Union[str, Mapping] """Acceptable Datastructures for outputs parameters""" @@ -79,7 +73,7 @@ def round_values(function: Callable) -> OUTPUT_QUANTITY_SIGNATURE: @wraps(function) def rounded( - market: xr.Dataset, sectors: List[AbstractSector], rounding: int = 4, **kwargs + market: xr.Dataset, sectors: list[AbstractSector], rounding: int = 4, **kwargs ) -> xr.DataArray: result = function(market, sectors, **kwargs) @@ -95,7 +89,7 @@ def rounded( def factory( *parameters: OUTPUTS_PARAMETERS, -) -> Callable[[xr.Dataset, List[AbstractSector]], List[Path]]: +) -> Callable[[xr.Dataset, list[AbstractSector]], list[Path]]: """Creates outputs functions for post-mortem analysis. Each parameter is a dictionary containing the following: @@ -121,13 +115,13 @@ def reformat_finite_resources(params): from muse.readers.toml import MissingSettings name = params["quantity"] - if not isinstance(name, Text): + if not isinstance(name, str): name = name["name"] if name.lower() not in {"finite_resources", "finiteresources"}: return params quantity = params["quantity"] - if isinstance(quantity, Text): + if isinstance(quantity, str): quantity = dict(name=quantity) else: quantity = dict(**quantity) @@ -153,7 +147,7 @@ def reformat_finite_resources(params): @register_output_quantity @round_values def consumption( - market: xr.Dataset, sectors: List[AbstractSector], **kwargs + market: xr.Dataset, sectors: list[AbstractSector], **kwargs ) -> xr.DataArray: """Current consumption.""" from muse.outputs.sector import market_quantity @@ -163,7 +157,7 @@ def consumption( @register_output_quantity @round_values -def supply(market: xr.Dataset, sectors: List[AbstractSector], **kwargs) -> xr.DataArray: +def supply(market: xr.Dataset, sectors: list[AbstractSector], **kwargs) -> xr.DataArray: """Current supply.""" from muse.outputs.sector import market_quantity @@ -174,9 +168,9 @@ def supply(market: xr.Dataset, sectors: List[AbstractSector], **kwargs) -> xr.Da @round_values def prices( market: xr.Dataset, - sectors: List[AbstractSector], + sectors: list[AbstractSector], drop_empty: bool = True, - keep_columns: Optional[Union[Sequence[Text], Text]] = "prices", + keep_columns: Optional[Union[Sequence[str], str]] = "prices", **kwargs, ) -> pd.DataFrame: """Current MCA market prices.""" @@ -187,7 +181,7 @@ def prices( if drop_empty: result = result[result.prices != 0] - if isinstance(keep_columns, Text): + if isinstance(keep_columns, str): result = result[[*ts_coords, keep_columns]] elif keep_columns is not None and len(keep_columns) > 0: @@ -205,7 +199,7 @@ def prices( @register_output_quantity @round_values def capacity( - market: xr.Dataset, sectors: List[AbstractSector], **kwargs + market: xr.Dataset, sectors: list[AbstractSector], **kwargs ) -> pd.DataFrame: """Current capacity across all sectors.""" return _aggregate_sectors(sectors, op=sector_capacity) @@ -213,7 +207,7 @@ def capacity( def sector_capacity(sector: AbstractSector) -> pd.DataFrame: """Sector capacity with agent annotations.""" - capa_sector: List[xr.DataArray] = [] + capa_sector: list[xr.DataArray] = [] agents = sorted(getattr(sector, "agents", []), key=attrgetter("name")) for agent in agents: capa_agent = agent.assets.capacity @@ -254,7 +248,7 @@ def sector_capacity(sector: AbstractSector) -> pd.DataFrame: def _aggregate_sectors( - sectors: List[AbstractSector], *args, op: Callable + sectors: list[AbstractSector], *args, op: Callable ) -> pd.DataFrame: """Aggregate outputs from all sectors.""" alldata = [op(sector, *args) for sector in sectors] @@ -270,10 +264,10 @@ class AggregateResources: def __init__( self, - commodities: Union[Text, Iterable[Hashable]] = (), - metric: Text = "consumption", + commodities: Union[str, Iterable[Hashable]] = (), + metric: str = "consumption", ): - if isinstance(commodities, Text): + if isinstance(commodities, str): commodities = [commodities] else: commodities = list(commodities) @@ -284,7 +278,7 @@ def __init__( def __call__( self, market: xr.Dataset, - sectors: List[AbstractSector], + sectors: list[AbstractSector], year: Optional[int] = None, ) -> Optional[xr.DataArray]: if len(self.commodities) == 0: @@ -307,14 +301,14 @@ class FiniteResources(AggregateResources): def __init__( self, - limits_path: Union[Text, Path, xr.DataArray], - commodities: Union[Text, Iterable[Hashable]] = (), - metric: Text = "consumption", + limits_path: Union[str, Path, xr.DataArray], + commodities: Union[str, Iterable[Hashable]] = (), + metric: str = "consumption", ): from muse.readers.csv import read_finite_resources super().__init__(commodities=commodities, metric=metric) - if isinstance(limits_path, Text): + if isinstance(limits_path, str): limits_path = Path(limits_path) if isinstance(limits_path, Path): limits_path = read_finite_resources(limits_path) @@ -324,7 +318,7 @@ def __init__( def __call__( self, market: xr.Dataset, - sectors: List[AbstractSector], + sectors: list[AbstractSector], year: Optional[int] = None, ) -> Optional[xr.DataArray]: if len(self.commodities) == 0: @@ -347,7 +341,7 @@ def __call__( @register_output_quantity(name=["timeslice_supply"]) def metric_supply( - market: xr.Dataset, sectors: List[AbstractSector], **kwargs + market: xr.Dataset, sectors: list[AbstractSector], **kwargs ) -> pd.DataFrame: """Current timeslice supply across all sectors.""" market_out = market.copy(deep=True) @@ -358,7 +352,7 @@ def sector_supply(sector: AbstractSector, market: xr.Dataset, **kwargs) -> pd.Da """Sector supply with agent annotations.""" from muse.production import supply - data_sector: List[xr.DataArray] = [] + data_sector: list[xr.DataArray] = [] techs = getattr(sector, "technologies", []) agents = sorted(getattr(sector, "agents", []), key=attrgetter("name")) @@ -426,7 +420,7 @@ def sector_supply(sector: AbstractSector, market: xr.Dataset, **kwargs) -> pd.Da @register_output_quantity(name=["yearly_supply"]) def metricy_supply( - market: xr.Dataset, sectors: List[AbstractSector], **kwargs + market: xr.Dataset, sectors: list[AbstractSector], **kwargs ) -> pd.DataFrame: """Current yearlysupply across all sectors.""" market_out = market.copy(deep=True) @@ -496,7 +490,7 @@ def capacity(agents): ] ) - data_sector: List[xr.DataArray] = [] + data_sector: list[xr.DataArray] = [] techs = getattr(sector, "technologies", []) agents = sorted(getattr(sector, "agents", []), key=attrgetter("name")) @@ -589,7 +583,7 @@ def capacity(agents): @register_output_quantity(name=["timeslice_consumption"]) def metric_consumption( - market: xr.Dataset, sectors: List[AbstractSector], **kwargs + market: xr.Dataset, sectors: list[AbstractSector], **kwargs ) -> pd.DataFrame: """Current timeslice consumption across all sectors.""" return _aggregate_sectors(sectors, market, op=sector_consumption) @@ -602,7 +596,7 @@ def sector_consumption( from muse.production import supply from muse.quantities import consumption - data_sector: List[xr.DataArray] = [] + data_sector: list[xr.DataArray] = [] techs = getattr(sector, "technologies", []) agents = sorted(getattr(sector, "agents", []), key=attrgetter("name")) @@ -673,7 +667,7 @@ def sector_consumption( @register_output_quantity(name=["yearly_consumption"]) def metricy_consumption( - market: xr.Dataset, sectors: List[AbstractSector], **kwargs + market: xr.Dataset, sectors: list[AbstractSector], **kwargs ) -> pd.DataFrame: """Current yearly consumption across all sectors.""" return _aggregate_sectors(sectors, market, op=sectory_consumption) @@ -686,7 +680,7 @@ def sectory_consumption( from muse.production import supply from muse.quantities import consumption - data_sector: List[xr.DataArray] = [] + data_sector: list[xr.DataArray] = [] techs = getattr(sector, "technologies", []) agents = sorted(getattr(sector, "agents", []), key=attrgetter("name")) @@ -746,7 +740,7 @@ def sectory_consumption( @register_output_quantity(name=["fuel_costs"]) def metric_fuel_costs( - market: xr.Dataset, sectors: List[AbstractSector], **kwargs + market: xr.Dataset, sectors: list[AbstractSector], **kwargs ) -> pd.DataFrame: """Current lifetime levelised cost across all sectors.""" return _aggregate_sectors(sectors, market, op=sector_fuel_costs) @@ -760,7 +754,7 @@ def sector_fuel_costs( from muse.production import supply from muse.quantities import consumption - data_sector: List[xr.DataArray] = [] + data_sector: list[xr.DataArray] = [] technologies = getattr(sector, "technologies", []) agents = sorted(getattr(sector, "agents", []), key=attrgetter("name")) @@ -814,7 +808,7 @@ def sector_fuel_costs( @register_output_quantity(name=["capital_costs"]) def metric_capital_costs( - market: xr.Dataset, sectors: List[AbstractSector], **kwargs + market: xr.Dataset, sectors: list[AbstractSector], **kwargs ) -> pd.DataFrame: """Current capital costs across all sectors.""" return _aggregate_sectors(sectors, market, op=sector_capital_costs) @@ -824,7 +818,7 @@ def sector_capital_costs( sector: AbstractSector, market: xr.Dataset, **kwargs ) -> pd.DataFrame: """Sector capital costs with agent annotations.""" - data_sector: List[xr.DataArray] = [] + data_sector: list[xr.DataArray] = [] technologies = getattr(sector, "technologies", []) agents = sorted(getattr(sector, "agents", []), key=attrgetter("name")) @@ -865,7 +859,7 @@ def sector_capital_costs( @register_output_quantity(name=["emission_costs"]) def metric_emission_costs( - market: xr.Dataset, sectors: List[AbstractSector], **kwargs + market: xr.Dataset, sectors: list[AbstractSector], **kwargs ) -> pd.DataFrame: """Current emission costs across all sectors.""" return _aggregate_sectors(sectors, market, op=sector_emission_costs) @@ -878,7 +872,7 @@ def sector_emission_costs( from muse.commodities import is_enduse, is_pollutant from muse.production import supply - data_sector: List[xr.DataArray] = [] + data_sector: list[xr.DataArray] = [] technologies = getattr(sector, "technologies", []) agents = sorted(getattr(sector, "agents", []), key=attrgetter("name")) @@ -933,7 +927,7 @@ def sector_emission_costs( @register_output_quantity(name=["LCOE"]) def metric_lcoe( - market: xr.Dataset, sectors: List[AbstractSector], **kwargs + market: xr.Dataset, sectors: list[AbstractSector], **kwargs ) -> pd.DataFrame: """Current emission costs across all sectors.""" return _aggregate_sectors(sectors, market, op=sector_lcoe) @@ -962,7 +956,7 @@ def capacity_to_service_demand(demand, technologies): return max_demand / technologies.utilization_factor / max_hours # Filtering of the inputs - data_sector: List[xr.DataArray] = [] + data_sector: list[xr.DataArray] = [] technologies = getattr(sector, "technologies", []) agents = sorted(getattr(sector, "agents", []), key=attrgetter("name")) retro = [a for a in agents if a.category == "retrofit"] @@ -1135,7 +1129,7 @@ def capacity_to_service_demand(demand, technologies): @register_output_quantity(name=["EAC"]) def metric_eac( - market: xr.Dataset, sectors: List[AbstractSector], **kwargs + market: xr.Dataset, sectors: list[AbstractSector], **kwargs ) -> pd.DataFrame: """Current emission costs across all sectors.""" return _aggregate_sectors(sectors, market, op=sector_eac) @@ -1164,7 +1158,7 @@ def capacity_to_service_demand(demand, technologies): return max_demand / technologies.utilization_factor / max_hours # Filtering of the inputs - data_sector: List[xr.DataArray] = [] + data_sector: list[xr.DataArray] = [] technologies = getattr(sector, "technologies", []) agents = sorted(getattr(sector, "agents", []), key=attrgetter("name")) retro = [a for a in agents if a.category == "retrofit"] diff --git a/src/muse/outputs/sector.py b/src/muse/outputs/sector.py index bf69de166..9eb6c59a0 100644 --- a/src/muse/outputs/sector.py +++ b/src/muse/outputs/sector.py @@ -20,7 +20,8 @@ def quantity( The function should never modify it's arguments. """ -from typing import Any, Callable, List, Mapping, MutableMapping, Optional, Text, Union +from collections.abc import Mapping, MutableMapping +from typing import Any, Callable, Optional, Union import pandas as pd import xarray as xr @@ -34,10 +35,10 @@ def quantity( ] """Signature of functions computing quantities for later analysis.""" -OUTPUT_QUANTITIES: MutableMapping[Text, OUTPUT_QUANTITY_SIGNATURE] = {} +OUTPUT_QUANTITIES: MutableMapping[str, OUTPUT_QUANTITY_SIGNATURE] = {} """Quantity for post-simulation analysis.""" -OUTPUTS_PARAMETERS = Union[Text, Mapping] +OUTPUTS_PARAMETERS = Union[str, Mapping] """Acceptable Datastructures for outputs parameters""" @@ -59,7 +60,7 @@ def decorated(*args, **kwargs): def _quantity_factory( - parameters: Mapping, registry: Mapping[Text, Callable] + parameters: Mapping, registry: Mapping[str, Callable] ) -> Callable: from functools import partial from inspect import isclass @@ -82,25 +83,25 @@ def _quantity_factory( def _factory( - registry: Mapping[Text, Callable], + registry: Mapping[str, Callable], *parameters: OUTPUTS_PARAMETERS, - sector_name: Text = "default", + sector_name: str = "default", ) -> Callable: from muse.outputs.sinks import factory as sink_factory - if isinstance(parameters, Text): - params: List = [{"quantity": parameters}] + if isinstance(parameters, str): + params: list = [{"quantity": parameters}] elif isinstance(parameters, Mapping): params = [parameters] else: params = [ # type: ignore - {"quantity": o} if isinstance(o, Text) else o for o in parameters + {"quantity": o} if isinstance(o, str) else o for o in parameters ] quantities = [_quantity_factory(param, registry) for param in params] sinks = [sink_factory(param, sector_name=sector_name) for param in params] - def save_multiple_outputs(market, *args, year: Optional[int] = None) -> List[Any]: + def save_multiple_outputs(market, *args, year: Optional[int] = None) -> list[Any]: if year is None: year = int(market.year.min()) @@ -113,8 +114,8 @@ def save_multiple_outputs(market, *args, year: Optional[int] = None) -> List[Any def factory( - *parameters: OUTPUTS_PARAMETERS, sector_name: Text = "default" -) -> Callable[[xr.Dataset, xr.DataArray, xr.Dataset], List[Any]]: + *parameters: OUTPUTS_PARAMETERS, sector_name: str = "default" +) -> Callable[[xr.Dataset, xr.DataArray, xr.Dataset], list[Any]]: """Creates outputs functions for post-mortem analysis. Each parameter is a dictionary containing the following: @@ -152,14 +153,14 @@ def capacity( def market_quantity( quantity: xr.DataArray, - sum_over: Optional[Union[Text, List[Text]]] = None, - drop: Optional[Union[Text, List[Text]]] = None, + sum_over: Optional[Union[str, list[str]]] = None, + drop: Optional[Union[str, list[str]]] = None, ) -> xr.DataArray: from pandas import MultiIndex from muse.utilities import multiindex_to_coords - if isinstance(sum_over, Text): + if isinstance(sum_over, str): sum_over = [sum_over] if sum_over: sum_over = [s for s in sum_over if s in quantity.coords] @@ -179,8 +180,8 @@ def consumption( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - sum_over: Optional[List[Text]] = None, - drop: Optional[List[Text]] = None, + sum_over: Optional[list[str]] = None, + drop: Optional[list[str]] = None, rounding: int = 4, ) -> xr.DataArray: """Current consumption.""" @@ -199,8 +200,8 @@ def supply( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - sum_over: Optional[List[Text]] = None, - drop: Optional[List[Text]] = None, + sum_over: Optional[list[str]] = None, + drop: Optional[list[str]] = None, rounding: int = 4, ) -> xr.DataArray: """Current supply.""" @@ -219,8 +220,8 @@ def costs( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - sum_over: Optional[List[Text]] = None, - drop: Optional[List[Text]] = None, + sum_over: Optional[list[str]] = None, + drop: Optional[list[str]] = None, rounding: int = 4, ) -> xr.DataArray: """Current costs.""" diff --git a/src/muse/outputs/sinks.py b/src/muse/outputs/sinks.py index 683caadd3..4d7325eea 100644 --- a/src/muse/outputs/sinks.py +++ b/src/muse/outputs/sinks.py @@ -17,14 +17,11 @@ def to_netcfd(quantity: DataArray, config: Mapping) -> Optional[Text]: pass """ +from collections.abc import Mapping, MutableMapping, Sequence from typing import ( Any, Callable, - Mapping, - MutableMapping, Optional, - Sequence, - Text, Union, cast, ) @@ -36,15 +33,15 @@ def to_netcfd(quantity: DataArray, config: Mapping) -> Optional[Text]: from muse.registration import registrator OUTPUT_SINK_SIGNATURE = Callable[ - [Union[xr.DataArray, pd.DataFrame], int, KwArg(Any)], Optional[Text] + [Union[xr.DataArray, pd.DataFrame], int, KwArg(Any)], Optional[str] ] """Signature of functions used to save quantities.""" -OUTPUT_SINKS: MutableMapping[Text, Union[OUTPUT_SINK_SIGNATURE, Callable]] = {} +OUTPUT_SINKS: MutableMapping[str, Union[OUTPUT_SINK_SIGNATURE, Callable]] = {} """Stores a quantity somewhere.""" -def factory(parameters: Mapping, sector_name: Text = "default") -> Callable: +def factory(parameters: Mapping, sector_name: str = "default") -> Callable: from functools import partial from inspect import isclass from pathlib import Path @@ -55,11 +52,11 @@ def factory(parameters: Mapping, sector_name: Text = "default") -> Callable: config.pop("quantity", None) def normalize( - params: Optional[Mapping], filename: Optional[Text] = None + params: Optional[Mapping], filename: Optional[str] = None ) -> MutableMapping: if isinstance(params, Mapping): params = dict(**params) - elif isinstance(params, Text): + elif isinstance(params, str): params = dict(name=params) else: suffix = config.get("suffix", Path(filename).suffix if filename else "csv") @@ -96,7 +93,7 @@ def register_output_sink(function: OUTPUT_SINK_SIGNATURE = None) -> Callable: return function -def sink_to_file(suffix: Text): +def sink_to_file(suffix: str): """Simplifies sinks to files. The decorator takes care of figuring out the path to the file, as well as trims the @@ -109,7 +106,7 @@ def sink_to_file(suffix: Text): from muse.defaults import DEFAULT_OUTPUT_DIRECTORY - def decorator(function: Callable[[Union[pd.DataFrame, xr.DataArray], Text], None]): + def decorator(function: Callable[[Union[pd.DataFrame, xr.DataArray], str], None]): @wraps(function) def decorated( quantity: Union[pd.DataFrame, xr.DataArray], year: int, **config @@ -152,7 +149,7 @@ def decorated( "not been given." ) getLogger(function.__module__).critical(msg) - raise IOError(msg) + raise OSError(msg) filename.parent.mkdir(parents=True, exist_ok=True) function(quantity, filename, **params) # type: ignore @@ -164,7 +161,7 @@ def decorated( def standardize_quantity( - function: Callable[[Union[pd.DataFrame, xr.DataArray], Text], None], + function: Callable[[Union[pd.DataFrame, xr.DataArray], str], None], ): """Helps standardize how the quantities are specified. @@ -191,7 +188,7 @@ def decorated( *args, set_index: Union[Any, NotSpecified] = NotSpecified, sort_index: Union[Any, NotSpecified, bool] = NotSpecified, - keep_columns: Union[Text, Sequence[Text], NotSpecified] = NotSpecified, + keep_columns: Union[str, Sequence[str], NotSpecified] = NotSpecified, group_by: Union[Any, NotSpecified] = NotSpecified, **config, ) -> None: @@ -230,7 +227,7 @@ def decorated( @sink_to_file(".csv") @standardize_quantity def to_csv( - quantity: Union[pd.DataFrame, xr.DataArray], filename: Text, **params + quantity: Union[pd.DataFrame, xr.DataArray], filename: str, **params ) -> None: """Saves data array to csv format, using pandas.to_csv. @@ -252,7 +249,7 @@ def to_csv( @register_output_sink(name=("netcdf", "nc")) @sink_to_file(".nc") def to_netcdf( - quantity: Union[xr.DataArray, pd.DataFrame], filename: Text, **params + quantity: Union[xr.DataArray, pd.DataFrame], filename: str, **params ) -> None: """Saves data array to csv format, using xarray.to_netcdf. @@ -274,7 +271,7 @@ def to_netcdf( @sink_to_file(".xlsx") @standardize_quantity def to_excel( - quantity: Union[pd.DataFrame, xr.DataArray], filename: Text, **params + quantity: Union[pd.DataFrame, xr.DataArray], filename: str, **params ) -> None: """Saves data array to csv format, using pandas.to_excel. @@ -290,7 +287,7 @@ def to_excel( try: quantity.to_excel(filename, **params) except ModuleNotFoundError as e: - msg = "Cannot save to excel format: missing python package (%s)" % e + msg = f"Cannot save to excel format: missing python package ({e})" getLogger(__name__).critical(msg) raise @@ -301,8 +298,8 @@ class YearlyAggregate: def __init__( self, - final_sink: Optional[MutableMapping[Text, Any]] = None, - sector: Text = "", + final_sink: Optional[MutableMapping[str, Any]] = None, + sector: str = "", axis="year", **kwargs, ): diff --git a/src/muse/production.py b/src/muse/production.py index 32f099592..b8155bacc 100644 --- a/src/muse/production.py +++ b/src/muse/production.py @@ -38,7 +38,8 @@ def production( "supply", "PRODUCTION_SIGNATURE", ] -from typing import Any, Callable, Mapping, MutableMapping, Text, Union, cast +from collections.abc import Mapping, MutableMapping +from typing import Any, Callable, Union, cast import xarray as xr @@ -47,7 +48,7 @@ def production( PRODUCTION_SIGNATURE = Callable[[xr.DataArray, xr.DataArray, xr.Dataset], xr.DataArray] """Production signature.""" -PRODUCTION_METHODS: MutableMapping[Text, PRODUCTION_SIGNATURE] = {} +PRODUCTION_METHODS: MutableMapping[str, PRODUCTION_SIGNATURE] = {} """Dictionary of production methods. """ @@ -63,7 +64,7 @@ def register_production(function: PRODUCTION_SIGNATURE = None): def factory( - settings: Union[Text, Mapping] = "maximum_production", **kwargs + settings: Union[str, Mapping] = "maximum_production", **kwargs ) -> PRODUCTION_SIGNATURE: """Creates a production functor. @@ -80,9 +81,9 @@ def factory( from muse.production import PRODUCTION_METHODS - if isinstance(settings, Text): + if isinstance(settings, str): name = settings - keywords: MutableMapping[Text, Any] = dict() + keywords: MutableMapping[str, Any] = dict() else: keywords = dict(**settings) name = keywords.pop("name") @@ -129,7 +130,7 @@ def demand_matched_production( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - costs: Text = "prices", + costs: str = "prices", ) -> xr.DataArray: """Production from matching demand via annual lcoe.""" from muse.quantities import annual_levelized_cost_of_energy as lcoe @@ -160,7 +161,7 @@ def costed_production( market: xr.Dataset, capacity: xr.DataArray, technologies: xr.Dataset, - costs: Union[xr.DataArray, Callable, Text] = "alcoe", + costs: Union[xr.DataArray, Callable, str] = "alcoe", with_minimum_service: bool = True, with_emission: bool = True, ) -> xr.DataArray: @@ -179,9 +180,9 @@ def costed_production( ) from muse.utilities import broadcast_techs - if isinstance(costs, Text) and costs.lower() == "alcoe": + if isinstance(costs, str) and costs.lower() == "alcoe": costs = annual_levelized_cost_of_energy - elif isinstance(costs, Text): + elif isinstance(costs, str): raise ValueError(f"Unknown cost {costs}") if callable(costs): technodata = cast(xr.Dataset, broadcast_techs(technologies, capacity)) diff --git a/src/muse/quantities.py b/src/muse/quantities.py index 604286d59..162e1fcf5 100644 --- a/src/muse/quantities.py +++ b/src/muse/quantities.py @@ -5,7 +5,8 @@ functions are used in different areas of the model. """ -from typing import Callable, Optional, Sequence, Text, Tuple, Union, cast +from collections.abc import Sequence +from typing import Callable, Optional, Union, cast import numpy as np import xarray as xr @@ -15,7 +16,7 @@ def supply( capacity: xr.DataArray, demand: xr.DataArray, technologies: Union[xr.Dataset, xr.DataArray], - interpolation: Text = "linear", + interpolation: str = "linear", production_method: Optional[Callable] = None, ) -> xr.DataArray: """Production and emission for a given capacity servicing a given demand. @@ -196,7 +197,7 @@ def gross_margin( # costs due to pollutants production_costs = prices * fixed_outputs environmental_costs = (production_costs.sel(commodity=environmentals)).sum( - ("commodity") + "commodity" ) # revenues due to product sales revenues = (production_costs.sel(commodity=enduses)).sum("commodity") @@ -312,8 +313,8 @@ def consumption( def annual_levelized_cost_of_energy( prices: xr.DataArray, technologies: xr.Dataset, - interpolation: Text = "linear", - fill_value: Union[int, Text] = "extrapolate", + interpolation: str = "linear", + fill_value: Union[int, str] = "extrapolate", **filters, ) -> xr.DataArray: """Undiscounted levelized cost of energy (LCOE) of technologies on each given year. @@ -500,7 +501,7 @@ def demand_matched_production( def capacity_in_use( production: xr.DataArray, technologies: xr.Dataset, - max_dim: Optional[Union[Text, Tuple[Text]]] = "commodity", + max_dim: Optional[Union[str, tuple[str]]] = "commodity", **filters, ): """Capacity-in-use for each asset, given production. @@ -549,7 +550,7 @@ def capacity_in_use( def supply_cost( - production: xr.DataArray, lcoe: xr.DataArray, asset_dim: Optional[Text] = "asset" + production: xr.DataArray, lcoe: xr.DataArray, asset_dim: Optional[str] = "asset" ) -> xr.DataArray: """Supply cost given production and the levelized cost of energy. diff --git a/src/muse/readers/__init__.py b/src/muse/readers/__init__.py index f9b7827bc..631cecdaf 100644 --- a/src/muse/readers/__init__.py +++ b/src/muse/readers/__init__.py @@ -1,7 +1,5 @@ """Aggregates methods to read data from file.""" -from typing import Text - from muse.defaults import DATA_DIRECTORY from muse.readers.csv import * # noqa: F403 from muse.readers.toml import read_settings, read_timeslices # noqa: F401 @@ -10,7 +8,7 @@ """Default settings path.""" -def camel_to_snake(name: Text) -> Text: +def camel_to_snake(name: str) -> str: """Transforms CamelCase to snake_case.""" from re import sub @@ -27,7 +25,7 @@ def kebab_to_camel(string): return "".join(x.capitalize() for x in string.split("-")) -def snake_to_kebab(string: Text) -> Text: +def snake_to_kebab(string: str) -> str: from re import sub result = sub(r"((?<=[a-z])[A-Z]|(? Path: """Looks through a few standard place for sector files.""" filename = Path(filename) @@ -62,13 +63,13 @@ def find_sectors_file( if path.is_file(): return path if sector is not None: - msg = "Could not find sector %s file %s." % (sector.title(), filename) + msg = f"Could not find sector {sector.title()} file {filename}." else: - msg = "Could not find file %s." % filename - raise IOError(msg) + msg = f"Could not find file {filename}." + raise OSError(msg) -def read_technodictionary(filename: Union[Text, Path]) -> xr.Dataset: +def read_technodictionary(filename: Union[str, Path]) -> xr.Dataset: """Reads and formats technodata into a dataset. There are three axes: technologies, regions, and year. @@ -132,7 +133,7 @@ def to_agent_share(name): return result -def read_technodata_timeslices(filename: Union[Text, Path]) -> xr.Dataset: +def read_technodata_timeslices(filename: Union[str, Path]) -> xr.Dataset: from muse.readers import camel_to_snake csv = pd.read_csv(filename, float_precision="high", low_memory=False) @@ -170,7 +171,7 @@ def read_technodata_timeslices(filename: Union[Text, Path]) -> xr.Dataset: return result -def read_io_technodata(filename: Union[Text, Path]) -> xr.Dataset: +def read_io_technodata(filename: Union[str, Path]) -> xr.Dataset: """Reads process inputs or outputs. There are four axes: (technology, region, year, commodity) @@ -221,7 +222,7 @@ def read_io_technodata(filename: Union[Text, Path]) -> xr.Dataset: return result -def read_initial_assets(filename: Union[Text, Path]) -> xr.DataArray: +def read_initial_assets(filename: Union[str, Path]) -> xr.DataArray: """Reads and formats data about initial capacity into a dataframe.""" data = pd.read_csv(filename, float_precision="high", low_memory=False) if "Time" in data.columns: @@ -238,7 +239,7 @@ def read_initial_assets(filename: Union[Text, Path]) -> xr.DataArray: return result -def read_initial_capacity(data: Union[Text, Path, pd.DataFrame]) -> xr.DataArray: +def read_initial_capacity(data: Union[str, Path, pd.DataFrame]) -> xr.DataArray: if not isinstance(data, pd.DataFrame): data = pd.read_csv(data, float_precision="high", low_memory=False) if "Unit" in data.columns: @@ -256,12 +257,12 @@ def read_initial_capacity(data: Union[Text, Path, pd.DataFrame]) -> xr.DataArray def read_technologies( - technodata_path_or_sector: Optional[Union[Text, Path]] = None, - technodata_timeslices_path: Optional[Union[Text, Path]] = None, - comm_out_path: Optional[Union[Text, Path]] = None, - comm_in_path: Optional[Union[Text, Path]] = None, - commodities: Optional[Union[Text, Path, xr.Dataset]] = None, - sectors_directory: Union[Text, Path] = DEFAULT_SECTORS_DIRECTORY, + technodata_path_or_sector: Optional[Union[str, Path]] = None, + technodata_timeslices_path: Optional[Union[str, Path]] = None, + comm_out_path: Optional[Union[str, Path]] = None, + comm_in_path: Optional[Union[str, Path]] = None, + commodities: Optional[Union[str, Path, xr.Dataset]] = None, + sectors_directory: Union[str, Path] = DEFAULT_SECTORS_DIRECTORY, ) -> xr.Dataset: """Reads data characterising technologies from files. @@ -298,7 +299,7 @@ def read_technologies( if (not comm_out_path) and (not comm_in_path): sector = technodata_path_or_sector - assert sector is None or isinstance(sector, Text) + assert sector is None or isinstance(sector, str) tpath = find_sectors_file( f"technodata{sector.title()}.csv", sector, @@ -315,7 +316,7 @@ def read_technologies( sectors_directory, ) else: - assert isinstance(technodata_path_or_sector, (Text, Path)) + assert isinstance(technodata_path_or_sector, (str, Path)) assert comm_out_path is not None assert comm_in_path is not None tpath = Path(technodata_path_or_sector) @@ -328,7 +329,7 @@ def read_technologies( - inputs: {ipath} """ if technodata_timeslices_path and isinstance( - technodata_timeslices_path, (Text, Path) + technodata_timeslices_path, (str, Path) ): ttpath = Path(technodata_timeslices_path) msg += f"""- technodata_timeslices: {ttpath} @@ -336,7 +337,7 @@ def read_technologies( else: ttpath = None - if isinstance(commodities, (Text, Path)): + if isinstance(commodities, (str, Path)): msg += f"""- global commodities file: {commodities}""" logger = getLogger(__name__) @@ -363,17 +364,17 @@ def read_technologies( except xr.core.merge.MergeError: raise UnitsConflictInCommodities - if isinstance(ttpath, (Text, Path)): + if isinstance(ttpath, (str, Path)): technodata_timeslice = read_technodata_timeslices(ttpath) result = result.drop_vars("utilization_factor") result = result.merge(technodata_timeslice) else: technodata_timeslice = None # try and add info about commodities - if isinstance(commodities, (Text, Path)): + if isinstance(commodities, (str, Path)): try: commodities = read_global_commodities(commodities) - except IOError: + except OSError: logger.warning("Could not load global commodities file.") commodities = None @@ -382,7 +383,7 @@ def read_technologies( result = result.merge(commodities.sel(commodity=result.commodity)) else: - raise IOError( + raise OSError( "Commodities not found in global commodities file: check spelling." ) @@ -397,11 +398,11 @@ def read_technologies( return result -def read_csv_timeslices(path: Union[Text, Path], **kwargs) -> xr.DataArray: +def read_csv_timeslices(path: Union[str, Path], **kwargs) -> xr.DataArray: """Reads timeslice information from input.""" from logging import getLogger - getLogger(__name__).info("Reading timeslices from %s" % path) + getLogger(__name__).info(f"Reading timeslices from {path}") data = pd.read_csv(path, float_precision="high", **kwargs) def snake_case(string): @@ -426,7 +427,7 @@ def snake_case(string): return result.timeslice -def read_global_commodities(path: Union[Text, Path]) -> xr.Dataset: +def read_global_commodities(path: Union[str, Path]) -> xr.Dataset: """Reads commodities information from input.""" from logging import getLogger @@ -436,7 +437,7 @@ def read_global_commodities(path: Union[Text, Path]) -> xr.Dataset: if path.is_dir(): path = path / "MuseGlobalCommodities.csv" if not path.is_file(): - raise IOError(f"File {path} does not exist.") + raise OSError(f"File {path} does not exist.") getLogger(__name__).info(f"Reading global commodities from {path}.") @@ -458,9 +459,9 @@ def read_global_commodities(path: Union[Text, Path]) -> xr.Dataset: def read_timeslice_shares( - path: Union[Text, Path] = DEFAULT_SECTORS_DIRECTORY, - sector: Optional[Text] = None, - timeslice: Union[Text, Path, xr.DataArray] = "Timeslices{sector}.csv", + path: Union[str, Path] = DEFAULT_SECTORS_DIRECTORY, + sector: Optional[str] = None, + timeslice: Union[str, Path, xr.DataArray] = "Timeslices{sector}.csv", ) -> xr.Dataset: """Reads sliceshare information into a xr.Dataset. @@ -479,15 +480,15 @@ def read_timeslice_shares( path, filename = path.parent, path.name re = match(r"TimesliceShare(.*)\.csv", filename) sector = path.name if re is None else re.group(1) - if isinstance(timeslice, Text) and "{sector}" in timeslice: + if isinstance(timeslice, str) and "{sector}" in timeslice: timeslice = timeslice.format(sector=sector) - if isinstance(timeslice, (Text, Path)) and not Path(timeslice).is_file(): + if isinstance(timeslice, (str, Path)) and not Path(timeslice).is_file(): timeslice = find_sectors_file(timeslice, sector, path) - if isinstance(timeslice, (Text, Path)): + if isinstance(timeslice, (str, Path)): timeslice = read_csv_timeslices(timeslice, low_memory=False) - share_path = find_sectors_file("TimesliceShare%s.csv" % sector, sector, path) - getLogger(__name__).info("Reading timeslice shares from %s" % share_path) + share_path = find_sectors_file(f"TimesliceShare{sector}.csv", sector, path) + getLogger(__name__).info(f"Reading timeslice shares from {share_path}") data = pd.read_csv(share_path, float_precision="high", low_memory=False) data.index = pd.MultiIndex.from_arrays( (data.RegionName, data.SN), names=("region", "timeslice") @@ -507,7 +508,7 @@ def read_timeslice_shares( return result.shares -def read_csv_agent_parameters(filename) -> List: +def read_csv_agent_parameters(filename) -> list: """Reads standard MUSE agent-declaration csv-files. Returns a list of dictionaries, where each dictionary can be used to instantiate an @@ -516,7 +517,7 @@ def read_csv_agent_parameters(filename) -> List: from re import sub if ( - isinstance(filename, Text) + isinstance(filename, str) and Path(filename).suffix != ".csv" and not Path(filename).is_file() ): @@ -554,7 +555,7 @@ def read_csv_agent_parameters(filename) -> List: if not issubclass(type(u), (int, float)): raise ValueError(f"Agent ObjData requires a float entry in {filename}") decision_params = [ - u for u in zip(objectives, sorting, floats) if isinstance(u[0], Text) + u for u in zip(objectives, sorting, floats) if isinstance(u[0], str) ] agent_type = { @@ -587,7 +588,7 @@ def read_csv_agent_parameters(filename) -> List: return result -def read_macro_drivers(path: Union[Text, Path]) -> xr.Dataset: +def read_macro_drivers(path: Union[str, Path]) -> xr.Dataset: """Reads a standard MUSE csv file for macro drivers.""" from logging import getLogger @@ -612,9 +613,9 @@ def read_macro_drivers(path: Union[Text, Path]) -> xr.Dataset: def read_initial_market( - projections: Union[xr.DataArray, Path, Text], - base_year_import: Optional[Union[Text, Path, xr.DataArray]] = None, - base_year_export: Optional[Union[Text, Path, xr.DataArray]] = None, + projections: Union[xr.DataArray, Path, str], + base_year_import: Optional[Union[str, Path, xr.DataArray]] = None, + base_year_export: Optional[Union[str, Path, xr.DataArray]] = None, timeslices: Optional[xr.DataArray] = None, ) -> xr.Dataset: """Read projections, import and export csv files.""" @@ -623,14 +624,14 @@ def read_initial_market( from muse.timeslices import QuantityType, convert_timeslice # Projections must always be present - if isinstance(projections, (Text, Path)): + if isinstance(projections, (str, Path)): getLogger(__name__).info(f"Reading projections from {projections}") projections = read_attribute_table(projections) if timeslices is not None: projections = convert_timeslice(projections, timeslices, QuantityType.INTENSIVE) # Base year export is optional. If it is not there, it's set to zero - if isinstance(base_year_export, (Text, Path)): + if isinstance(base_year_export, (str, Path)): getLogger(__name__).info(f"Reading base year export from {base_year_export}") base_year_export = read_attribute_table(base_year_export) elif base_year_export is None: @@ -638,7 +639,7 @@ def read_initial_market( base_year_export = xr.zeros_like(projections) # Base year import is optional. If it is not there, it's set to zero - if isinstance(base_year_import, (Text, Path)): + if isinstance(base_year_import, (str, Path)): getLogger(__name__).info(f"Reading base year import from {base_year_import}") base_year_import = read_attribute_table(base_year_import) elif base_year_import is None: @@ -677,7 +678,7 @@ def read_initial_market( return result -def read_attribute_table(path: Union[Text, Path]) -> xr.DataArray: +def read_attribute_table(path: Union[str, Path]) -> xr.DataArray: """Read a standard MUSE csv file for price projections.""" from logging import getLogger @@ -685,7 +686,7 @@ def read_attribute_table(path: Union[Text, Path]) -> xr.DataArray: path = Path(path) if not path.is_file(): - raise IOError(f"{path} does not exist.") + raise OSError(f"{path} does not exist.") getLogger(__name__).info(f"Reading prices from {path}") @@ -714,7 +715,7 @@ def read_attribute_table(path: Union[Text, Path]) -> xr.DataArray: return result -def read_regression_parameters(path: Union[Text, Path]) -> xr.Dataset: +def read_regression_parameters(path: Union[str, Path]) -> xr.Dataset: """Reads the regression parameters from a standard MUSE csv file.""" from logging import getLogger @@ -722,7 +723,7 @@ def read_regression_parameters(path: Union[Text, Path]) -> xr.Dataset: path = Path(path) if not path.is_file(): - raise IOError(f"{path} does not exist or is not a file.") + raise OSError(f"{path} does not exist or is not a file.") getLogger(__name__).info(f"Reading regression parameters from {path}.") table = pd.read_csv(path, float_precision="high", low_memory=False) @@ -772,10 +773,10 @@ def read_regression_parameters(path: Union[Text, Path]) -> xr.Dataset: def read_csv_outputs( - paths: Union[Text, Path, Sequence[Union[Text, Path]]], - columns: Text = "commodity", - indices: Sequence[Text] = ("RegionName", "ProcessName", "Timeslice"), - drop: Sequence[Text] = ("Unnamed: 0",), + paths: Union[str, Path, Sequence[Union[str, Path]]], + columns: str = "commodity", + indices: Sequence[str] = ("RegionName", "ProcessName", "Timeslice"), + drop: Sequence[str] = ("Unnamed: 0",), ) -> xr.Dataset: """Read standard MUSE output files for consumption or supply.""" from re import match @@ -785,16 +786,16 @@ def read_csv_outputs( def expand_paths(path): from glob import glob - if isinstance(paths, Text): + if isinstance(paths, str): return [Path(p) for p in glob(path)] return Path(path) - if isinstance(paths, Text): + if isinstance(paths, str): allfiles = expand_paths(paths) else: allfiles = [expand_paths(p) for p in cast(Sequence, paths)] if len(allfiles) == 0: - raise IOError(f"No files found with paths {paths}") + raise OSError(f"No files found with paths {paths}") datas = {} for path in allfiles: @@ -809,10 +810,10 @@ def expand_paths(path): reyear = match(r"\S*.(\d{4})\S*\.csv", path.name) if reyear is None: - raise IOError(f"Unexpected filename {path.name}") + raise OSError(f"Unexpected filename {path.name}") year = int(reyear.group(1)) if year in datas: - raise IOError(f"Year f{year} was found twice") + raise OSError(f"Year f{year} was found twice") data.year = year datas[year] = xr.DataArray(data) @@ -833,12 +834,12 @@ def expand_paths(path): def read_trade( - data: Union[pd.DataFrame, Text, Path], + data: Union[pd.DataFrame, str, Path], columns_are_source: bool = True, - parameters: Optional[Text] = None, + parameters: Optional[str] = None, skiprows: Optional[Sequence[int]] = None, - name: Optional[Text] = None, - drop: Optional[Union[Text, Sequence[Text]]] = None, + name: Optional[str] = None, + drop: Optional[Union[str, Sequence[str]]] = None, ) -> Union[xr.DataArray, xr.Dataset]: """Read CSV table with source and destination regions.""" from muse.readers import camel_to_snake @@ -855,7 +856,7 @@ def read_trade( row_region = "src_region" col_region = "dst_region" data = data.apply(to_numeric, axis=0) - if isinstance(drop, Text): + if isinstance(drop, str): drop = [drop] if drop: drop = list(set(drop).intersection(data.columns)) @@ -892,7 +893,7 @@ def read_trade( return result.rename(src_region="region") -def read_finite_resources(path: Union[Text, Path]) -> xr.DataArray: +def read_finite_resources(path: Union[str, Path]) -> xr.DataArray: """Reads finite resources from csv file. The CSV file is made up of columns "Region", "Year", as well @@ -922,15 +923,15 @@ def read_finite_resources(path: Union[Text, Path]) -> xr.DataArray: def check_utilization_not_all_zero(data, filename): if "utilization_factor" not in data.columns: raise ValueError( - """A technology needs to have a utilization factor defined for every - timeslice. Please check file {}.""".format(filename) + f"""A technology needs to have a utilization factor defined for every + timeslice. Please check file {filename}.""" ) utilization_sum = data.groupby(["technology", "region", "year"]).sum() if (utilization_sum.utilization_factor == 0).any(): raise ValueError( - """A technology can not have a utilization factor of 0 for every - timeslice. Please check file {}.""".format(filename) + f"""A technology can not have a utilization factor of 0 for every + timeslice. Please check file {filename}.""" ) return data diff --git a/src/muse/readers/toml.py b/src/muse/readers/toml.py index f05d47881..5b357d636 100644 --- a/src/muse/readers/toml.py +++ b/src/muse/readers/toml.py @@ -4,20 +4,14 @@ import importlib.util as implib from collections import namedtuple +from collections.abc import Mapping, MutableMapping, Sequence from copy import deepcopy from logging import getLogger from pathlib import Path from typing import ( IO, Any, - Dict, - List, - Mapping, - MutableMapping, Optional, - Sequence, - Text, - Tuple, Union, ) @@ -47,7 +41,7 @@ class IncorrectSettings(InputError): def convert(dictionary): """Converts a dictionary (with nested ones) to a nametuple.""" for key, value in dictionary.items(): - if isinstance(value, Dict): + if isinstance(value, dict): dictionary[key] = convert(value) return namedtuple("MUSEOptions", dictionary.keys())(**dictionary) @@ -78,11 +72,11 @@ def __format__(self, spec): def format_path( - filepath: Text, + filepath: str, replacements: Optional[Mapping] = None, - path: Optional[Union[Text, Path]] = None, - cwd: Optional[Union[Text, Path]] = None, - muse_sectors: Optional[Text] = None, + path: Optional[Union[str, Path]] = None, + cwd: Optional[Union[str, Path]] = None, + muse_sectors: Optional[str] = None, ): """Replaces known patterns in a path. @@ -110,10 +104,10 @@ def format_path( def format_paths( settings: Mapping, replacements: Optional[Mapping] = None, - path: Optional[Union[Text, Path]] = None, - cwd: Optional[Union[Text, Path]] = None, - muse_sectors: Optional[Text] = None, - suffixes: Sequence[Text] = (".csv", ".nc", ".xls", ".xlsx", ".py", ".toml"), + path: Optional[Union[str, Path]] = None, + cwd: Optional[Union[str, Path]] = None, + muse_sectors: Optional[str] = None, + suffixes: Sequence[str] = (".csv", ".nc", ".xls", ".xlsx", ".py", ".toml"), ): """Format paths passed to settings. @@ -214,7 +208,7 @@ def format_paths( **({} if replacements is None else replacements), } - def format(path: Text) -> Text: + def format(path: str) -> str: if path.lower() in ("optional", "required"): return path return format_path(path, **patterns) # type: ignore @@ -228,7 +222,7 @@ def format(path: Text) -> Text: def is_a_path(key, value): return any(re.search(x, key) is not None for x in path_names) or ( - isinstance(value, Text) and Path(value).suffix in suffixes + isinstance(value, str) and Path(value).suffix in suffixes ) path = format(settings.get("path", str(patterns["path"]))) @@ -242,7 +236,7 @@ def is_a_path(key, value): result[key] = format(value) elif isinstance(value, Mapping): result[key] = format_paths(value, patterns, path) - elif isinstance(value, List): + elif isinstance(value, list): result[key] = [ format_paths(item, patterns, path) if isinstance(item, Mapping) @@ -256,8 +250,8 @@ def is_a_path(key, value): def read_split_toml( - tomlfile: Union[Text, Path, IO[Text], Mapping], - path: Optional[Union[Text, Path]] = None, + tomlfile: Union[str, Path, IO[str], Mapping], + path: Optional[Union[str, Path]] = None, ) -> MutableMapping: """Reads and consolidate TOML files. @@ -349,8 +343,8 @@ def splice_section(settings: Mapping): def read_settings( - settings_file: Union[Text, Path, IO[Text], Mapping], - path: Optional[Union[Text, Path]] = None, + settings_file: Union[str, Path, IO[str], Mapping], + path: Optional[Union[str, Path]] = None, ) -> Any: """Loads the input settings for any MUSE simulation. @@ -402,9 +396,9 @@ def read_settings( def read_ts_multiindex( - settings: Optional[Union[Mapping, Text]] = None, + settings: Optional[Union[Mapping, str]] = None, timeslice: Optional[xr.DataArray] = None, - transforms: Optional[Dict[Tuple, np.ndarray]] = None, + transforms: Optional[dict[tuple, np.ndarray]] = None, ) -> pd.MultiIndex: '''Read multiindex for a timeslice from TOML. @@ -465,7 +459,7 @@ def read_ts_multiindex( indices = (TIMESLICE if timeslice is None else timeslice).get_index("timeslice") if transforms is None: transforms = TRANSFORMS - if isinstance(settings, Text): + if isinstance(settings, str): settings = loads(settings) elif settings is None: return indices @@ -481,7 +475,7 @@ def read_ts_multiindex( levels = [ settings.get(name, level) for name, level in zip(indices.names, indices.levels) ] - levels = [[level] if isinstance(level, Text) else level for level in levels] + levels = [[level] if isinstance(level, str) else level for level in levels] for i, level in enumerate(levels): known = [index[i] for index in transforms if len(index) > i] unexpected = set(level).difference(known) @@ -494,9 +488,9 @@ def read_ts_multiindex( def read_timeslices( - settings: Optional[Union[Text, Mapping]] = None, + settings: Optional[Union[str, Mapping]] = None, timeslice: Optional[xr.DataArray] = None, - transforms: Optional[Dict[Tuple, np.ndarray]] = None, + transforms: Optional[dict[tuple, np.ndarray]] = None, ) -> xr.Dataset: '''Reads timeslice levels and create resulting timeslice coordinate. @@ -586,22 +580,22 @@ def add_known_parameters(dd, u, parent=None): if isinstance(v, Mapping): new_parent = k if parent is not None: - new_parent = "{}.{}".format(parent, k) + new_parent = f"{parent}.{k}" d[k] = add_known_parameters(d.get(k, {}), v, new_parent) else: d[k] = v # Required parameters - elif isinstance(d[k], Text) and d[k].lower() == "required": + elif isinstance(d[k], str) and d[k].lower() == "required": missing.append(k) # Optional parameters with default values - elif isinstance(d[k], Text) and d[k].lower() == "optional": + elif isinstance(d[k], str) and d[k].lower() == "optional": d.pop(k) elif parent is not None: - defaults_used.append("{}.{}".format(parent, k)) + defaults_used.append(f"{parent}.{k}") else: defaults_used.append(k) - msg = "ERROR - Required parameters missing in input file: {}.".format(missing) + msg = f"ERROR - Required parameters missing in input file: {missing}." if len(missing) > 0: raise MissingSettings(msg) @@ -626,7 +620,7 @@ def add_unknown_parameters(dd, u): return d -def validate_settings(settings: Dict) -> None: +def validate_settings(settings: dict) -> None: """Run the checks on the settings file.""" msg = " Validating input settings..." getLogger(__name__).info(msg) @@ -637,7 +631,7 @@ def validate_settings(settings: Dict) -> None: SETTINGS_CHECKS[check](settings) -def check_plugins(settings: Dict) -> None: +def check_plugins(settings: dict) -> None: """Checks that the user custom defined python files exist. Checks that the user custom defined python files exist. If flagged to use, they are @@ -648,10 +642,10 @@ def check_plugins(settings: Dict) -> None: """ plugins = settings.get("plugins", []) - if isinstance(plugins, (Dict, Mapping)): + if isinstance(plugins, (dict, Mapping)): plugins = plugins.get("plugins") - if isinstance(plugins, (Path, Text)): + if isinstance(plugins, (Path, str)): plugins = [plugins] if not plugins: @@ -672,7 +666,7 @@ def check_plugins(settings: Dict) -> None: @register_settings_check(vary_name=False) -def check_log_level(settings: Dict) -> None: +def check_log_level(settings: dict) -> None: """Check the log level required in the simulation.""" valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] msg = "ERROR - Valid log levels are {}.".format(", ".join(valid_levels)) @@ -682,7 +676,7 @@ def check_log_level(settings: Dict) -> None: @register_settings_check(vary_name=False) -def check_interpolation_mode(settings: Dict) -> None: +def check_interpolation_mode(settings: dict) -> None: """Just updates the interpolation mode to a bool. There's no check, actually. @@ -702,7 +696,7 @@ def check_interpolation_mode(settings: Dict) -> None: @register_settings_check(vary_name=False) -def check_budget_parameters(settings: Dict) -> None: +def check_budget_parameters(settings: dict) -> None: """Check the parameters that are required if carbon_budget > 0.""" length = len(settings["carbon_budget_control"]["budget"]) if length > 0: @@ -725,7 +719,7 @@ def check_budget_parameters(settings: Dict) -> None: @register_settings_check(vary_name=False) -def check_foresight(settings: Dict) -> None: +def check_foresight(settings: dict) -> None: """Check that foresight is a multiple of the smaller time_framework difference. If so, we update the time framework adding the foresight year to the list and @@ -746,7 +740,7 @@ def check_foresight(settings: Dict) -> None: @register_settings_check(vary_name=False) -def check_iteration_control(settings: Dict) -> None: +def check_iteration_control(settings: dict) -> None: """Checks the variables related to the control of the iterations. This includes whether equilibrium must be reached, the maximum number of iterations @@ -768,7 +762,7 @@ def check_iteration_control(settings: Dict) -> None: @register_settings_check(vary_name=False) -def check_time_slices(settings: Dict) -> None: +def check_time_slices(settings: dict) -> None: """Check the time slices. If there is no error, they are transformed into a xr.DataArray @@ -782,7 +776,7 @@ def check_time_slices(settings: Dict) -> None: @register_settings_check(vary_name=False) -def check_global_data_files(settings: Dict) -> None: +def check_global_data_files(settings: dict) -> None: """Checks that the global user files exist.""" user_data = settings["global_input_files"] @@ -791,7 +785,7 @@ def check_global_data_files(settings: Dict) -> None: else: basedir = settings["root"] / Path(user_data["path"]) - msg = "ERROR Directory of global user files does not exist: {}.".format(basedir) + msg = f"ERROR Directory of global user files does not exist: {basedir}." assert basedir.exists(), msg # Update the path to the base directory @@ -814,7 +808,7 @@ def check_global_data_files(settings: Dict) -> None: @register_settings_check(vary_name=False) -def check_sectors_files(settings: Dict) -> None: +def check_sectors_files(settings: dict) -> None: """Checks that the sector files exist.""" sectors = settings["sectors"] priorities = { @@ -844,10 +838,10 @@ def check_sectors_files(settings: Dict) -> None: def read_technodata( settings: Any, - sector_name: Optional[Text] = None, + sector_name: Optional[str] = None, time_framework: Optional[Sequence[int]] = None, - commodities: Optional[Union[Text, Path]] = None, - regions: Optional[Sequence[Text]] = None, + commodities: Optional[Union[str, Path]] = None, + regions: Optional[Sequence[str]] = None, **kwargs, ) -> xr.Dataset: """Helper function to create technodata for a given sector.""" @@ -873,7 +867,7 @@ def read_technodata( raise MissingSettings("Missing technodata section") technosettings = undo_damage(settings.technodata) - if isinstance(technosettings, Text): + if isinstance(technosettings, str): technosettings = dict( technodata=technosettings, technodata_timeslices=technodata_timeslices, @@ -910,7 +904,7 @@ def read_technodata( techcomms = technologies.commodity[ins | outs] technologies = technologies.sel(commodity=techcomms) for name, value in technosettings.items(): - if isinstance(name, (Text, Path)): + if isinstance(name, (str, Path)): data = read_trade(value, drop="Unit") if "region" in data.dims: data = data.sel(region=regions) diff --git a/src/muse/registration.py b/src/muse/registration.py index d44910cf6..23321e00f 100644 --- a/src/muse/registration.py +++ b/src/muse/registration.py @@ -2,7 +2,8 @@ __all__ = ["registrator"] -from typing import Callable, MutableMapping, Optional, Sequence, Text, Union +from collections.abc import MutableMapping, Sequence +from typing import Callable, Optional, Union def name_variations(*args): @@ -39,8 +40,8 @@ def nospacecase(name): def registrator( decorator: Optional[Callable] = None, registry: Optional[MutableMapping] = None, - logname: Optional[Text] = None, - loglevel: Optional[Text] = "Debug", + logname: Optional[str] = None, + loglevel: Optional[str] = "Debug", ) -> Callable: """A decorator to create a decorator that registers functions with MUSE. @@ -132,7 +133,7 @@ def registrator( @wraps(decorator) def register( function=None, - name: Optional[Union[Text, Sequence[Text]]] = None, + name: Optional[Union[str, Sequence[str]]] = None, vary_name: bool = True, overwrite: bool = False, ): @@ -148,7 +149,7 @@ def register( if name is None: names = [function.__name__] - elif isinstance(name, Text): + elif isinstance(name, str): names = [name, function.__name__] else: names = [*name, function.__name__] @@ -156,7 +157,7 @@ def register( # all registered filters will use the same logger, at least for the # default logging done in the decorated function logger = getLogger(function.__module__) - msg = "Computing {}: {}".format(logname, names[0]) + msg = f"Computing {logname}: {names[0]}" assert decorator is not None if "name" in signature(decorator).parameters: diff --git a/src/muse/regressions.py b/src/muse/regressions.py index dbd5f0070..41471bb15 100644 --- a/src/muse/regressions.py +++ b/src/muse/regressions.py @@ -3,8 +3,9 @@ from __future__ import annotations from abc import abstractmethod +from collections.abc import Mapping, Sequence from pathlib import Path -from typing import Callable, ClassVar, Mapping, Optional, Sequence, Text, Tuple, Union +from typing import Callable, ClassVar from xarray import DataArray, Dataset @@ -67,7 +68,7 @@ class Regression(Callable): This class attribute must be overridden. """ - def __init__(self, interpolation: Text = "linear", base_year: int = 2010, **kwargs): + def __init__(self, interpolation: str = "linear", base_year: int = 2010, **kwargs): super().__init__() self.interpolation = interpolation """Interpolation method when interpolating years""" @@ -82,15 +83,15 @@ def __init__(self, interpolation: Text = "linear", base_year: int = 2010, **kwar @abstractmethod def __call__( self, - gdp_or_dataset: Union[DataArray, Dataset], - population: Optional[DataArray], - year: Optional[Union[int, Sequence[int]]] = None, + gdp_or_dataset: DataArray | Dataset, + population: DataArray | None, + year: int | Sequence[int] | None = None, forecast: int = 5, **kwargs, ) -> DataArray: pass - def sel(self, **filters) -> "Regression": + def sel(self, **filters) -> Regression: """Regression over part of the data only.""" return self.__class__( interpolation=self.interpolation, @@ -100,7 +101,7 @@ def sel(self, **filters) -> "Regression": @staticmethod def _to_dataset( - first: Union[DataArray, Dataset], population: Optional[DataArray] + first: DataArray | Dataset, population: DataArray | None ) -> Dataset: data = first if isinstance(first, Dataset) else Dataset({"gdp": first}) if population is not None: @@ -108,7 +109,7 @@ def _to_dataset( return data @staticmethod - def _split_kwargs(data: Dataset, **kwargs) -> Tuple[Mapping, Mapping]: + def _split_kwargs(data: Dataset, **kwargs) -> tuple[Mapping, Mapping]: filters = {k: v for k, v in kwargs.items() if k in data.dims} attrs = {k: v for k, v in kwargs.items() if k not in data.dims} return filters, attrs @@ -116,8 +117,8 @@ def _split_kwargs(data: Dataset, **kwargs) -> Tuple[Mapping, Mapping]: @classmethod def factory( cls, - regression_data: Union[Text, Path, Dataset], - interpolation: Text = "linear", + regression_data: str | Path | Dataset, + interpolation: str = "linear", base_year: int = 2010, **filters, ) -> Regression: @@ -127,7 +128,7 @@ def factory( assert cls.__mappings__ assert cls.__regression__ != "" - if isinstance(regression_data, (Text, Path)): + if isinstance(regression_data, (str, Path)): regression_data = read_regression_parameters(regression_data) # Get the parameters of interest with a 'simple' name @@ -137,13 +138,13 @@ def factory( def factory( - regression_parameters: Union[Text, Path, Dataset], - sector: Optional[Union[Text, Sequence[Text]]] = None, + regression_parameters: str | Path | Dataset, + sector: str | Sequence[str] | None = None, ) -> Regression: """Creates regression functor from standard MUSE data for given sector.""" from muse.readers import read_regression_parameters - if isinstance(regression_parameters, (Text, Path)): + if isinstance(regression_parameters, (str, Path)): regression_parameters = read_regression_parameters(regression_parameters) if sector is not None: @@ -189,7 +190,7 @@ def _kebab_case(name: str) -> str: def register_regression( - Functor: Regression = None, name: Optional[Text] = None + Functor: Regression = None, name: str | None = None ) -> Regression: """Registers a functor with MUSE regressions. @@ -213,15 +214,12 @@ def register_regression( logger = getLogger(__name__) def factory(file_or_dataset, *args, **kwargs): - if isinstance(file_or_dataset, (Path, Text)): - msg = "Creating regression functor {} from data in {}".format( - _kebab_case(name if name is not None else Functor.__name__), - file_or_dataset, - ) + if isinstance(file_or_dataset, (Path, str)): + opt = _kebab_case(name if name is not None else Functor.__name__) + msg = f"Creating regression functor {opt} from data in {file_or_dataset}" else: - msg = "Creating regression functor {} from dataset".format( - _kebab_case(name if name is not None else Functor.__name__) - ) + opt = _kebab_case(name if name is not None else Functor.__name__) + msg = f"Creating regression functor {opt} from dataset" logger.info(msg) function = getattr(Functor, "factory", Functor) return function(file_or_dataset, *args, **kwargs) @@ -230,7 +228,7 @@ def factory(file_or_dataset, *args, **kwargs): REGRESSION_FUNCTOR_NAMES[Functor.__name__.lower()] = [] for n in name_variations(*names): if n in REGRESSION_FUNCTOR_CREATOR: - msg = "A regression with the name %s already exists" % n + msg = f"A regression with the name {n} already exists" raise RuntimeError(msg) REGRESSION_FUNCTOR_CREATOR[n] = factory REGRESSION_FUNCTOR_NAMES[Functor.__name__.lower()].append(n) @@ -239,7 +237,7 @@ def factory(file_or_dataset, *args, **kwargs): def regression_functor( - mappings: Mapping[Text, Text], name: Optional[Text] = None + mappings: Mapping[str, str], name: str | None = None ) -> Regression: """Creates a macro-driver functor from a function. @@ -266,16 +264,16 @@ def decorator(func): classname = func.__name__ logger = getLogger(__name__) - log = "Calling {} regression function".format(_kebab_case(classname)) + log = f"Calling {_kebab_case(classname)} regression function" # the main function will transform the input so 'func' can deal with it @wraps(func) def __call__( self, - gdp_or_dataset: Union[DataArray, Dataset], - population: Optional[DataArray] = None, - year: Optional[Union[int, Sequence[int]]] = None, - forecast: Optional[Union[int, Sequence[int]]] = None, + gdp_or_dataset: DataArray | Dataset, + population: DataArray | None = None, + year: int | Sequence[int] | None = None, + forecast: int | Sequence[int] | None = None, **kwargs, ): from numpy import ndarray @@ -328,12 +326,12 @@ def __call__( if __call__.__doc__ is None: __call__.__doc__ = "\n\n" + msg else: - __call__.__doc__ += "Regression function: {}\n\n{}".format(name_, msg) + __call__.__doc__ += f"Regression function: {name_}\n\n{msg}" - doc = """Regression function: {name} + doc = f"""Regression function: {name_} - This functor is a regression function registered with MUSE as '{name}'. - """.format(name=name_) + This functor is a regression function registered with MUSE as '{name_}'. + """ Self = type( classname, @@ -369,7 +367,7 @@ def ExponentialAdj( gdp: DataArray, population: DataArray, *args, - year: Optional[Union[int, Sequence[int]]] = None, + year: int | Sequence[int] | None = None, forecast: int = 5, n: int = 6, **kwargs, @@ -421,7 +419,7 @@ def LogisticSigmoid( gdp: DataArray, population: DataArray, *args, - year: Optional[Union[int, Sequence[int]]] = None, + year: int | Sequence[int] | None = None, **kwargs, ) -> DataArray: """0.001 * (constant * pop + gdp * c / sqrt(1 + (gdp * scale / pop)^2).""" @@ -465,9 +463,9 @@ class Linear(Regression): def __call__( self, - gdp_or_dataset: Union[DataArray, Dataset], - population: Optional[DataArray] = None, - year: Optional[Union[int, Sequence[int]]] = None, + gdp_or_dataset: DataArray | Dataset, + population: DataArray | None = None, + year: int | Sequence[int] | None = None, forecast: int = 5, **kwargs, ) -> DataArray: @@ -506,15 +504,15 @@ def __call__( def endogenous_demand( - regression_parameters: Union[Text, Path, Dataset], - drivers: Union[Text, Path, Dataset], - sector: Optional[Union[Text, Sequence]] = None, + regression_parameters: str | Path | Dataset, + drivers: str | Path | Dataset, + sector: str | Sequence | None = None, **kwargs, ) -> Dataset: """Endogenous demand based on macro drivers and regression parameters.""" from muse.readers import read_macro_drivers regression = factory(regression_parameters, sector=sector) - if isinstance(drivers, (Text, Path)): + if isinstance(drivers, (str, Path)): drivers = read_macro_drivers(drivers) return regression(drivers, **kwargs) diff --git a/src/muse/sectors/abstract.py b/src/muse/sectors/abstract.py index 3fd3c24c7..231b444c4 100644 --- a/src/muse/sectors/abstract.py +++ b/src/muse/sectors/abstract.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Text +from typing import Any from xarray import Dataset @@ -22,7 +22,7 @@ class AbstractSector(ABC): @classmethod @abstractmethod - def factory(cls, name: Text, settings: Any) -> AbstractSector: + def factory(cls, name: str, settings: Any) -> AbstractSector: """Creates class from settings named-tuple.""" pass diff --git a/src/muse/sectors/legacy_sector.py b/src/muse/sectors/legacy_sector.py index bb88bb709..ad61cdc6f 100644 --- a/src/muse/sectors/legacy_sector.py +++ b/src/muse/sectors/legacy_sector.py @@ -4,10 +4,11 @@ once accessing those sectors is no longer needed. """ +from collections.abc import Sequence from dataclasses import dataclass from itertools import chain from logging import getLogger -from typing import Any, Dict, Sequence, Text, Tuple, Union +from typing import Any, Union import numpy as np import pandas as pd @@ -20,7 +21,7 @@ @dataclass -class LegacyMarket(object): +class LegacyMarket: BaseYear: int EndYear: int Foresight: np.ndarray @@ -32,13 +33,13 @@ class LegacyMarket(object): macro_drivers: pd.DataFrame dfRegions: pd.DataFrame Regions: np.ndarray - interpolation_mode: Text + interpolation_mode: str @register_sector(name="legacy") class LegacySector(AbstractSector): # type: ignore @classmethod - def factory(cls, name: Text, settings: Any, **kwargs) -> "LegacySector": + def factory(cls, name: str, settings: Any, **kwargs) -> "LegacySector": from pathlib import Path from muse_legacy.sectors import SECTORS @@ -130,7 +131,7 @@ def factory(cls, name: Text, settings: Any, **kwargs) -> "LegacySector": name: global_commodities.isel(commodity=sector_comm), } - msg = "LegacySector {} created successfully.".format(name) + msg = f"LegacySector {name} created successfully." getLogger(__name__).info(msg) return cls( name, @@ -150,19 +151,19 @@ def factory(cls, name: Text, settings: Any, **kwargs) -> "LegacySector": def __init__( self, - name: Text, + name: str, old_sector, - timeslices: Dict, - commodities: Dict, + timeslices: dict, + commodities: dict, commodity_price: DataArray, static_trade: DataArray, regions: Sequence, time_framework: np.ndarray, - mode: Text, + mode: str, excess: Union[int, float], - market_iterative: Text, - sectors_dir: Text, - output_dir: Text, + market_iterative: str, + sectors_dir: str, + output_dir: str, ): super().__init__() self.name = name @@ -277,7 +278,7 @@ def runprocessmodule(self, consumption, supplycost, supply, t): return result @staticmethod - def load_timeslices_and_aggregation(timeslices, sectors) -> Tuple[dict, str]: + def load_timeslices_and_aggregation(timeslices, sectors) -> tuple[dict, str]: """Loads all sector timeslices and finds the finest one.""" timeslices = {"prices": timeslices.rename("prices timeslices")} finest = timeslices["prices"].copy() @@ -378,11 +379,11 @@ def ndarray_to_xarray( global_commodities: DataArray, sector_commodities: DataArray, data_ts: pd.MultiIndex, - dims: Sequence[Text], - regions: Sequence[Text], + dims: Sequence[str], + regions: Sequence[str], ) -> DataArray: """From ndarray to dataarray.""" - from typing import Hashable, Mapping + from collections.abc import Hashable, Mapping from muse.timeslices import convert_timeslice @@ -403,11 +404,11 @@ def xarray_to_ndarray( ts: pd.MultiIndex, qt: QuantityType, global_commodities: DataArray, - dims: Sequence[Text], - regions: Sequence[Text], + dims: Sequence[str], + regions: Sequence[str], ) -> np.ndarray: """From dataarray to ndarray.""" - from typing import Hashable, Mapping + from collections.abc import Hashable, Mapping from muse.timeslices import convert_timeslice @@ -424,7 +425,7 @@ def xarray_to_ndarray( return result.values -def commodities_idx(sector, comm: Text) -> Sequence: +def commodities_idx(sector, comm: str) -> Sequence: """Gets the indices of the commodities involved in the processes of the sector. Arguments: diff --git a/src/muse/sectors/preset_sector.py b/src/muse/sectors/preset_sector.py index 67327c5d0..2bdbbdec8 100644 --- a/src/muse/sectors/preset_sector.py +++ b/src/muse/sectors/preset_sector.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any, Text +from typing import Any from xarray import DataArray, Dataset @@ -14,9 +14,9 @@ class PresetSector(AbstractSector): # type: ignore """Sector with outcomes fixed from the start.""" @classmethod - def factory(cls, name: Text, settings: Any) -> PresetSector: + def factory(cls, name: str, settings: Any) -> PresetSector: """Constructs a PresetSectors from input data.""" - from typing import Sequence + from collections.abc import Sequence from xarray import DataArray, zeros_like @@ -108,7 +108,7 @@ def factory(cls, name: Text, settings: Any) -> PresetSector: presets["costs"] = costs if len(presets.data_vars) == 0: - raise IOError("None of supply, consumption, costs given") + raise OSError("None of supply, consumption, costs given") # add missing data as zeros: we only need one of conumption, costs, supply components = {"supply", "consumption", "costs"} @@ -140,14 +140,14 @@ def factory(cls, name: Text, settings: Any) -> PresetSector: def __init__( self, presets: Dataset, - interpolation_mode: Text = "linear", - name: Text = "preset", + interpolation_mode: str = "linear", + name: str = "preset", ): super().__init__() self.presets: Dataset = presets """Market across time and space.""" - self.interpolation_mode: Text = interpolation_mode + self.interpolation_mode: str = interpolation_mode """Interpolation method""" self.name = name """Name by which to identify a sector""" diff --git a/src/muse/sectors/register.py b/src/muse/sectors/register.py index 690888d1b..d761ced45 100644 --- a/src/muse/sectors/register.py +++ b/src/muse/sectors/register.py @@ -1,15 +1,16 @@ -from typing import Callable, Mapping, Optional, Sequence, Text, Type, Union +from collections.abc import Mapping, Sequence +from typing import Callable, Optional, Union from muse.sectors.abstract import AbstractSector -SECTORS_REGISTERED: Mapping[Text, Callable] = {} +SECTORS_REGISTERED: Mapping[str, Callable] = {} """Dictionary of sectors.""" def register_sector( - sector_class: Optional[Type[AbstractSector]] = None, - name: Optional[Union[Text, Sequence[Text]]] = None, -) -> Type[AbstractSector]: + sector_class: Optional[type[AbstractSector]] = None, + name: Optional[Union[str, Sequence[str]]] = None, +) -> type[AbstractSector]: """Registers a sector so it is available MUSE-wide. Example: @@ -24,8 +25,8 @@ def register_sector( if sector_class is None: return lambda x: register_sector(x, name=name) # type: ignore - if isinstance(name, Text): - names: Sequence[Text] = (name,) + if isinstance(name, str): + names: Sequence[str] = (name,) elif name is None: names = (sector_class.__name__,) else: diff --git a/src/muse/sectors/sector.py b/src/muse/sectors/sector.py index 4d08878c8..f764dd64c 100644 --- a/src/muse/sectors/sector.py +++ b/src/muse/sectors/sector.py @@ -1,15 +1,9 @@ from __future__ import annotations +from collections.abc import Iterator, Mapping, Sequence from typing import ( Any, Callable, - Iterator, - Mapping, - Optional, - Sequence, - Text, - Tuple, - Union, cast, ) @@ -28,7 +22,7 @@ class Sector(AbstractSector): # type: ignore """Base class for all sectors.""" @classmethod - def factory(cls, name: Text, settings: Any) -> Sector: + def factory(cls, name: str, settings: Any) -> Sector: from muse.interactions import factory as interaction_factory from muse.outputs.sector import factory as ofactory from muse.production import factory as pfactory @@ -62,7 +56,7 @@ def factory(cls, name: Text, settings: Any) -> Sector: ._asdict() .items() ] - are_disjoint_commodities = sum((len(s.commodities) for s in subsectors)) == len( + are_disjoint_commodities = sum(len(s.commodities) for s in subsectors) == len( set().union(*(set(s.commodities) for s in subsectors)) # type: ignore ) if not are_disjoint_commodities: @@ -73,7 +67,7 @@ def factory(cls, name: Text, settings: Any) -> Sector: supply_args = sector_settings.pop( "supply", sector_settings.pop("dispatch_production", {}) ) - if isinstance(supply_args, Text): + if isinstance(supply_args, str): supply_args = {"name": supply_args} else: supply_args = nametuple_to_dict(supply_args) @@ -96,32 +90,32 @@ def factory(cls, name: Text, settings: Any) -> Sector: def __init__( self, - name: Text, + name: str, technologies: xr.Dataset, subsectors: Sequence[Subsector] = [], - timeslices: Optional[pd.MultiIndex] = None, + timeslices: pd.MultiIndex | None = None, technodata_timeslices: xr.Dataset = None, - interactions: Optional[Callable[[Sequence[AbstractAgent]], None]] = None, - interpolation: Text = "linear", - outputs: Optional[Callable] = None, - supply_prod: Optional[PRODUCTION_SIGNATURE] = None, + interactions: Callable[[Sequence[AbstractAgent]], None] | None = None, + interpolation: str = "linear", + outputs: Callable | None = None, + supply_prod: PRODUCTION_SIGNATURE | None = None, ): from muse.interactions import factory as interaction_factory from muse.outputs.sector import factory as ofactory from muse.production import maximum_production - self.name: Text = name + self.name: str = name """Name of the sector.""" self.subsectors: Sequence[Subsector] = list(subsectors) """Subsectors controlled by this object.""" self.technologies: xr.Dataset = technologies """Parameters describing the sector's technologies.""" - self.timeslices: Optional[pd.MultiIndex] = timeslices + self.timeslices: pd.MultiIndex | None = timeslices """Timeslice at which this sector operates. If None, it will operate using the timeslice of the input market. """ - self.interpolation: Mapping[Text, Any] = { + self.interpolation: Mapping[str, Any] = { "method": interpolation, "kwargs": {"fill_value": "extrapolate"}, } @@ -177,8 +171,8 @@ def forecast(self): def next( self, mca_market: xr.Dataset, - time_period: Optional[int] = None, - current_year: Optional[int] = None, + time_period: int | None = None, + current_year: int | None = None, ) -> xr.Dataset: """Advance sector by one time period. @@ -410,12 +404,12 @@ def agents(self) -> Iterator[AbstractAgent]: def convert_market_timeslice( market: xr.Dataset, timeslice: pd.MultiIndex, - intensive: Union[Text, Tuple[Text]] = "prices", + intensive: str | tuple[str] = "prices", ) -> xr.Dataset: """Converts market from one to another timeslice.""" from muse.timeslices import QuantityType, convert_timeslice - if isinstance(intensive, Text): + if isinstance(intensive, str): intensive = (intensive,) timesliced = {d for d in market.data_vars if "timeslice" in market[d].dims} diff --git a/src/muse/sectors/subsector.py b/src/muse/sectors/subsector.py index 4c8f221fe..f6f3564bf 100644 --- a/src/muse/sectors/subsector.py +++ b/src/muse/sectors/subsector.py @@ -1,16 +1,9 @@ from __future__ import annotations +from collections.abc import Hashable, MutableMapping, Sequence from typing import ( Any, Callable, - Hashable, - List, - MutableMapping, - Optional, - Sequence, - Text, - Tuple, - Union, cast, ) @@ -26,11 +19,11 @@ class Subsector: def __init__( self, agents: Sequence[Agent], - commodities: Sequence[Text], - demand_share: Optional[Callable] = None, - constraints: Optional[Callable] = None, - investment: Optional[Callable] = None, - name: Text = "subsector", + commodities: Sequence[str], + demand_share: Callable | None = None, + constraints: Callable | None = None, + investment: Callable | None = None, + name: str = "subsector", forecast: int = 5, expand_market_prices: bool = False, ): @@ -39,7 +32,7 @@ def __init__( from muse import investments as iv self.agents: Sequence[Agent] = list(agents) - self.commodities: List[Text] = list(commodities) + self.commodities: list[str] = list(commodities) self.demand_share = demand_share or ds.factory() self.constraints = constraints or cs.factory() self.investment = investment or iv.factory() @@ -58,7 +51,7 @@ def invest( technologies: xr.Dataset, market: xr.Dataset, time_period: int = 5, - current_year: Optional[int] = None, + current_year: int | None = None, ) -> None: if current_year is None: current_year = market.year.min() @@ -106,8 +99,8 @@ def aggregate_lp( technologies: xr.Dataset, market: xr.Dataset, time_period: int = 5, - current_year: Optional[int] = None, - ) -> Optional[Tuple[xr.Dataset, Sequence[xr.Dataset]]]: + current_year: int | None = None, + ) -> tuple[xr.Dataset, Sequence[xr.Dataset]] | None: from muse.utilities import agent_concatenation, reduce_assets if current_year is None: @@ -170,9 +163,9 @@ def factory( cls, settings: Any, technologies: xr.Dataset, - regions: Optional[Sequence[Text]] = None, - current_year: Optional[int] = None, - name: Text = "subsector", + regions: Sequence[str] | None = None, + current_year: int | None = None, + name: str = "subsector", ) -> Subsector: from muse import constraints as cs from muse import demand_share as ds @@ -232,7 +225,7 @@ def factory( expand_market_prices = getattr(settings, "expand_market_prices", None) if expand_market_prices is None: expand_market_prices = "dst_region" in technologies.dims and not any( - (isinstance(u, InvestingAgent) for u in agents) + isinstance(u, InvestingAgent) for u in agents ) return cls( @@ -248,8 +241,8 @@ def factory( def aggregate_enduses( - assets: Sequence[Union[xr.Dataset, xr.DataArray]], technologies: xr.Dataset -) -> Sequence[Text]: + assets: Sequence[xr.Dataset | xr.DataArray], technologies: xr.Dataset +) -> Sequence[str]: """Aggregate enduse commodities for input assets. This function is meant as a helper to figure out the commodities attached to a group diff --git a/src/muse/timeslices.py b/src/muse/timeslices.py index 0255a6a83..8f3e791c5 100644 --- a/src/muse/timeslices.py +++ b/src/muse/timeslices.py @@ -9,8 +9,9 @@ "represent_hours", ] +from collections.abc import Mapping, Sequence from enum import Enum, unique -from typing import Dict, Mapping, Optional, Sequence, Text, Tuple, Union +from typing import Optional, Union import xarray as xr from numpy import ndarray @@ -21,7 +22,7 @@ TIMESLICE: DataArray = None # type: ignore """Array with the finest timeslice.""" -TRANSFORMS: Dict[Tuple, ndarray] = None # type: ignore +TRANSFORMS: dict[tuple, ndarray] = None # type: ignore """Transforms from each aggregate to the finest timeslice.""" DEFAULT_TIMESLICE_DESCRIPTION = """ @@ -68,9 +69,9 @@ def reference_timeslice( - settings: Union[Mapping, Text], - level_names: Sequence[Text] = ("month", "day", "hour"), - name: Text = "timeslice", + settings: Union[Mapping, str], + level_names: Sequence[str] = ("month", "day", "hour"), + name: str = "timeslice", ) -> DataArray: '''Reads reference timeslice from toml like input. @@ -113,11 +114,10 @@ def reference_timeslice( * week (timeslice) object 64B 'weekday' 'weekend' ... 'weekend' ''' from functools import reduce - from typing import List, Tuple from toml import loads - if isinstance(settings, Text): + if isinstance(settings, str): settings = loads(settings) settings = dict(**settings.get("timeslices", settings)) if "level_names" in settings: @@ -125,7 +125,7 @@ def reference_timeslice( settings.pop("aggregates", {}) # figures out levels - levels: List[Tuple] = [(level,) for level in settings] + levels: list[tuple] = [(level,) for level in settings] ts = list(settings.values()) while all(isinstance(v, Mapping) for v in ts): levels = [(*previous, b) for previous, a in zip(levels, ts) for b in a] @@ -147,9 +147,9 @@ def reference_timeslice( def aggregate_transforms( - settings: Optional[Union[Mapping, Text]] = None, + settings: Optional[Union[Mapping, str]] = None, timeslice: Optional[DataArray] = None, -) -> Dict[Tuple, ndarray]: +) -> dict[tuple, ndarray]: '''Creates dictionary of transforms for aggregate levels. The transforms are used to create the projectors towards the finest timeslice. @@ -201,13 +201,13 @@ def aggregate_transforms( timeslice = TIMESLICE if settings is None: settings = {} - elif isinstance(settings, Text): + elif isinstance(settings, str): settings = loads(settings) # get timeslice dimension Id = identity(len(timeslice), dtype=int) indices = timeslice.get_index("timeslice") - unitvecs: Dict[Tuple, ndarray] = {index: Id[i] for (i, index) in enumerate(indices)} + unitvecs: dict[tuple, ndarray] = {index: Id[i] for (i, index) in enumerate(indices)} if "timeslices" in settings or "aggregates" in settings: settings = settings.get("timeslices", settings).get("aggregates", {}) assert isinstance(settings, Mapping) @@ -225,7 +225,7 @@ def aggregate_transforms( level = matching_levels.index(True) levels[level].append(name) - result: Dict[Tuple, ndarray] = {} + result: dict[tuple, ndarray] = {} for index in set(product(*levels)).difference(unitvecs): if not any(level in settings for level in index): continue @@ -237,7 +237,7 @@ def aggregate_transforms( return result -def setup_module(settings: Union[Text, Mapping]): +def setup_module(settings: Union[str, Mapping]): """Sets up module singletons.""" global TIMESLICE global TRANSFORMS @@ -248,7 +248,7 @@ def setup_module(settings: Union[Text, Mapping]): def timeslice_projector( x: Union[DataArray, MultiIndex], finest: Optional[DataArray] = None, - transforms: Optional[Dict[Tuple, ndarray]] = None, + transforms: Optional[dict[tuple, ndarray]] = None, ) -> DataArray: '''Project time-slice to standardized finest time-slices. @@ -355,7 +355,7 @@ def timeslice_projector( transforms = TRANSFORMS index = finest.get_index("timeslice") - index = index.set_names((f"finest_{u}" for u in index.names)) + index = index.set_names(f"finest_{u}" for u in index.names) if isinstance(x, MultiIndex): timeslices = x @@ -399,9 +399,9 @@ class QuantityType(Enum): def convert_timeslice( x: Union[DataArray, Dataset], ts: Union[DataArray, Dataset, MultiIndex], - quantity: Union[QuantityType, Text] = QuantityType.EXTENSIVE, + quantity: Union[QuantityType, str] = QuantityType.EXTENSIVE, finest: Optional[DataArray] = None, - transforms: Optional[Dict[Tuple, ndarray]] = None, + transforms: Optional[dict[tuple, ndarray]] = None, ) -> Union[DataArray, Dataset]: '''Adjusts the timeslice of x to match that of ts. @@ -546,7 +546,7 @@ def convert_timeslice( if quantity is QuantityType.EXTENSIVE: finest = finest.rename(timeslice="finest_timeslice") index = finest.get_index("finest_timeslice") - index = index.set_names((f"finest_{u}" for u in index.names)) + index = index.set_names(f"finest_{u}" for u in index.names) mindex_coords = xr.Coordinates.from_pandas_multiindex(index, "finest_timeslice") finest = finest.drop_vars(list(finest.coords)).assign_coords(mindex_coords) proj0 *= finest diff --git a/src/muse/utilities.py b/src/muse/utilities.py index de377c097..a2afd0093 100644 --- a/src/muse/utilities.py +++ b/src/muse/utilities.py @@ -1,16 +1,11 @@ """Collection of functions and stand-alone algorithms.""" +from collections.abc import Hashable, Iterable, Iterator, Mapping, Sequence from typing import ( Any, Callable, - Hashable, - Iterable, - Iterator, - Mapping, NamedTuple, Optional, - Sequence, - Text, Union, cast, ) @@ -20,7 +15,7 @@ def multiindex_to_coords( - data: Union[xr.Dataset, xr.DataArray], dimension: Text = "asset" + data: Union[xr.Dataset, xr.DataArray], dimension: str = "asset" ): """Flattens multi-index dimension into multi-coord dimension.""" from pandas import MultiIndex @@ -38,7 +33,7 @@ def multiindex_to_coords( def coords_to_multiindex( - data: Union[xr.Dataset, xr.DataArray], dimension: Text = "asset" + data: Union[xr.Dataset, xr.DataArray], dimension: str = "asset" ) -> Union[xr.Dataset, xr.DataArray]: """Creates a multi-index from flattened multiple coords.""" from pandas import MultiIndex @@ -53,8 +48,8 @@ def coords_to_multiindex( def reduce_assets( assets: Union[xr.DataArray, xr.Dataset, Sequence[Union[xr.Dataset, xr.DataArray]]], - coords: Optional[Union[Text, Sequence[Text], Iterable[Text]]] = None, - dim: Text = "asset", + coords: Optional[Union[str, Sequence[str], Iterable[str]]] = None, + dim: str = "asset", operation: Optional[Callable] = None, ) -> Union[xr.DataArray, xr.Dataset]: r"""Combine assets along given asset dimension. @@ -159,8 +154,8 @@ def operation(x): if assets[dim].size == 0: return assets if coords is None: - coords = [cast(Text, k) for k, v in assets.coords.items() if v.dims == (dim,)] - elif isinstance(coords, Text): + coords = [cast(str, k) for k, v in assets.coords.items() if v.dims == (dim,)] + elif isinstance(coords, str): coords = (coords,) coords = [k for k in coords if k in assets.coords and assets[k].dims == (dim,)] assets = copy(assets) @@ -179,8 +174,8 @@ def operation(x): def broadcast_techs( technologies: Union[xr.Dataset, xr.DataArray], template: Union[xr.DataArray, xr.Dataset], - dimension: Text = "asset", - interpolation: Text = "linear", + dimension: str = "asset", + interpolation: str = "linear", installed_as_year: bool = True, **kwargs, ) -> Union[xr.Dataset, xr.DataArray]: @@ -262,14 +257,12 @@ def clean_assets(assets: xr.Dataset, years: Union[int, Sequence[int]]): def filter_input( dataset: Union[xr.Dataset, xr.DataArray], year: Optional[Union[int, Iterable[int]]] = None, - interpolation: Text = "linear", + interpolation: str = "linear", **kwargs, ) -> Union[xr.Dataset, xr.DataArray]: """Filter inputs, taking care to interpolate years.""" - from typing import Set - if year is None: - setyear: Set[int] = set() + setyear: set[int] = set() else: try: setyear = {int(year)} # type: ignore @@ -299,7 +292,7 @@ def filter_input( def filter_with_template( data: Union[xr.Dataset, xr.DataArray], template: Union[xr.DataArray, xr.Dataset], - asset_dimension: Text = "asset", + asset_dimension: str = "asset", **kwargs, ): """Filters data to match template. @@ -388,8 +381,8 @@ def lexical_comparison( def merge_assets( capa_a: xr.DataArray, capa_b: xr.DataArray, - interpolation: Text = "linear", - dimension: Text = "asset", + interpolation: str = "linear", + dimension: str = "asset", ) -> xr.DataArray: """Merge two capacity arrays.""" years = sorted(set(capa_a.year.values).union(capa_b.year.values)) @@ -429,7 +422,7 @@ def merge_assets( return result -def avoid_repetitions(data: xr.DataArray, dim: Text = "year") -> xr.DataArray: +def avoid_repetitions(data: xr.DataArray, dim: str = "year") -> xr.DataArray: """List of years such that there is no repetition in the data. It removes the central year of any three consecutive years where all data is @@ -464,7 +457,7 @@ def future_propagation( data: xr.DataArray, future: xr.DataArray, threshold: float = 1e-12, - dim: Text = "year", + dim: str = "year", ) -> xr.DataArray: """Propagates values into the future. @@ -543,8 +536,8 @@ def future_propagation( def agent_concatenation( data: Mapping[Hashable, Union[xr.DataArray, xr.Dataset]], - dim: Text = "asset", - name: Text = "agent", + dim: str = "asset", + name: str = "agent", fill_value: Any = 0, ) -> Union[xr.DataArray, xr.Dataset]: """Concatenates input map along given dimension. @@ -619,8 +612,8 @@ def agent_concatenation( def aggregate_technology_model( data: Union[xr.DataArray, xr.Dataset], - dim: Text = "asset", - drop: Union[Text, Sequence[Text]] = "installed", + dim: str = "asset", + drop: Union[str, Sequence[str]] = "installed", ) -> Union[xr.DataArray, xr.Dataset]: """Aggregate together assets with the same installation year. @@ -658,13 +651,9 @@ def aggregate_technology_model( ... assert len(actual.asset) == 1 ... assert (actual == expected).all() """ - if isinstance(drop, Text): + if isinstance(drop, str): drop = (drop,) return reduce_assets( data, - [ - cast(Text, u) - for u in data.coords - if u not in drop and data[u].dims == (dim,) - ], + [cast(str, u) for u in data.coords if u not in drop and data[u].dims == (dim,)], ) diff --git a/src/muse/wizard.py b/src/muse/wizard.py index e0b1d669f..c353ecfd1 100644 --- a/src/muse/wizard.py +++ b/src/muse/wizard.py @@ -1,7 +1,7 @@ import os from itertools import chain from pathlib import Path -from typing import Callable, List +from typing import Callable import pandas as pd from tomlkit import dumps, parse @@ -21,7 +21,7 @@ def modify_toml(path_to_toml: Path, function: Callable): path_to_toml.write_text(dumps(data)) -def get_sectors(model_path: Path) -> List[str]: +def get_sectors(model_path: Path) -> list[str]: """Get a list of sector names for a model. Args: diff --git a/tests/conftest.py b/tests/conftest.py index 523bea0d8..620a50c2f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ +from collections.abc import Mapping, Sequence from pathlib import Path -from typing import Callable, Mapping, Optional, Sequence, Text +from typing import Callable, Optional import numpy as np from muse.agents import Agent @@ -28,7 +29,7 @@ def cases_directory() -> Optional[Path]: @fixture(scope="session") -def regression_directories(cases_directory) -> Mapping[Text, Path]: +def regression_directories(cases_directory) -> Mapping[str, Path]: if cases_directory is None: return {} return { @@ -420,7 +421,7 @@ def stock_factory() -> Callable: def _stock( coords, technologies, - region: Optional[Sequence[Text]] = None, + region: Optional[Sequence[str]] = None, nassets: Optional[int] = None, ) -> Dataset: from numpy import cumprod, stack @@ -604,7 +605,7 @@ def save_registries(): from contextlib import contextmanager @contextmanager - def saveme(module_name: Text, registry_name: Text): + def saveme(module_name: str, registry_name: str): from copy import deepcopy from importlib import import_module diff --git a/tests/test_aggregoutput.py b/tests/test_aggregoutput.py index d1f2f83f8..8e44975a8 100644 --- a/tests/test_aggregoutput.py +++ b/tests/test_aggregoutput.py @@ -82,9 +82,7 @@ def test_aggregate_sector_manyregions(): from pandas import DataFrame, concat mca = examples.model("multiple_agents") - residential = next( - (sector for sector in mca.sectors if sector.name == "residential") - ) + residential = next(sector for sector in mca.sectors if sector.name == "residential") agents = list(residential.agents) agents[0].assets["region"] = "BELARUS" agents[1].assets["region"] = "BELARUS" diff --git a/tests/test_demand_share.py b/tests/test_demand_share.py index cb9ccbd63..6e835f7ab 100644 --- a/tests/test_demand_share.py +++ b/tests/test_demand_share.py @@ -224,7 +224,6 @@ def method(capacity): def test_new_retro_demand_share(technologies, coords, market, timeslice, stock_factory): from dataclasses import dataclass - from typing import Text from uuid import UUID, uuid4 from muse.commodities import is_enduse @@ -242,10 +241,10 @@ def test_new_retro_demand_share(technologies, coords, market, timeslice, stock_f @dataclass class Agent: assets: xr.Dataset - category: Text + category: str uuid: UUID - name: Text - region: Text + name: str + region: str quantity: float agents = [ @@ -278,7 +277,6 @@ class Agent: def test_standard_demand_share(technologies, coords, market, timeslice, stock_factory): from dataclasses import dataclass - from typing import Text from uuid import UUID, uuid4 from muse.commodities import is_enduse @@ -297,10 +295,10 @@ def test_standard_demand_share(technologies, coords, market, timeslice, stock_fa @dataclass class Agent: assets: xr.Dataset - category: Text + category: str uuid: UUID - name: Text - region: Text + name: str + region: str quantity: float agents = [ diff --git a/tests/test_docs_notebooks.py b/tests/test_docs_notebooks.py index a06e023d6..2d2d94cd2 100644 --- a/tests/test_docs_notebooks.py +++ b/tests/test_docs_notebooks.py @@ -1,12 +1,11 @@ from pathlib import Path -from typing import List import nbformat from nbconvert.preprocessors import ExecutePreprocessor from pytest import mark -def available_notebooks() -> List[Path]: +def available_notebooks() -> list[Path]: """Locate the available notebooks in the docs.""" base_path = Path(__file__).parent.parent / "docs" return [p for p in base_path.rglob("*.ipynb") if "build" not in str(p)] diff --git a/tests/test_legacy_sector.py b/tests/test_legacy_sector.py index d0c6c8824..d88562a31 100644 --- a/tests/test_legacy_sector.py +++ b/tests/test_legacy_sector.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Optional, Text +from typing import Optional from pytest import approx, mark @@ -44,7 +44,7 @@ def legacy_inputs(): ] -def legacy_input_file(sector: Text) -> Optional[Path]: +def legacy_input_file(sector: str) -> Optional[Path]: """Gets the legacy sector settings file.""" input_file = ( Path(__file__).parent diff --git a/tests/test_mca.py b/tests/test_mca.py index d1b4fb8ea..ee07782bb 100644 --- a/tests/test_mca.py +++ b/tests/test_mca.py @@ -1,4 +1,4 @@ -from typing import Sequence +from collections.abc import Sequence from muse.commodities import CommodityUsage from xarray import Dataset diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 3e01123df..6f19d50e2 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -1,7 +1,6 @@ """Test saving outputs to file.""" from pathlib import Path -from typing import Dict, Text from unittest.mock import patch import numpy as np @@ -217,7 +216,7 @@ class MySpecialReturn: pass @register_output_sink(overwrite=True) - def dummy(data, year: int, sector: Text, overwrite: bool) -> MySpecialReturn: + def dummy(data, year: int, sector: str, overwrite: bool) -> MySpecialReturn: nonlocal received_data, gyear, gsector, goverwrite received_data = data gyear = year @@ -496,7 +495,7 @@ def test_match_quantities(): da = xr.DataArray(name=q) ds = xr.Dataset({q: da}) - def assert_equal(a: Dict[str, xr.DataArray], b: Dict[str, xr.DataArray]): + def assert_equal(a: dict[str, xr.DataArray], b: dict[str, xr.DataArray]): assert set(a.keys()) == set(b.keys()) for k in a: xr.testing.assert_equal(a[k], b[k]) @@ -586,7 +585,7 @@ def test_aggregate_cache(): c = a.copy() c.assign_coords(dim_0=c.dim_0.data * 10) - dc, da = [da.to_dataframe().reset_index() for da in [c, a]] + dc, da = (da.to_dataframe().reset_index() for da in [c, a]) actual = _aggregate_cache(quantity, [c, a]) expected = pd.DataFrame.merge(dc, da, how="outer").astype(float) @@ -635,10 +634,7 @@ def test_consolidate_quantity(newcapa_agent, retro_agent): assert all(actual.year == newcapa_agent.forecast_year) assert all(actual.installed == newcapa_agent.year) assert all( - ( - name in (newcapa_agent.name, retro_agent.name) - for name in actual.agent.unique() - ) + name in (newcapa_agent.name, retro_agent.name) for name in actual.agent.unique() ) diff --git a/tests/test_quantities.py b/tests/test_quantities.py index de16b0c0b..74439b1b0 100644 --- a/tests/test_quantities.py +++ b/tests/test_quantities.py @@ -9,7 +9,8 @@ def demand( technologies: xr.Dataset, capacity: xr.DataArray, market: xr.DataArray ) -> xr.DataArray: - from typing import Any, Hashable, Mapping + from collections.abc import Hashable, Mapping + from typing import Any region = xr.DataArray(list(set(capacity.region.values)), dims="region") coords: Mapping[Hashable, Any] = { diff --git a/tests/test_readers.py b/tests/test_readers.py index 467e82632..ddf986276 100644 --- a/tests/test_readers.py +++ b/tests/test_readers.py @@ -22,11 +22,9 @@ def user_data_files(settings: dict) -> None: @fixture def sectors_files(settings: dict): """Creates the files related to the sector.""" - from typing import Text - for data in settings["sectors"].values(): for path in data.values(): - if not isinstance(path, (Path, Text)): + if not isinstance(path, (Path, str)): continue path = Path(path) if path.suffix != ".csv": diff --git a/tests/test_subsector.py b/tests/test_subsector.py index 356aad3b4..7f664fdf1 100644 --- a/tests/test_subsector.py +++ b/tests/test_subsector.py @@ -1,11 +1,11 @@ -from typing import Sequence, Text +from collections.abc import Sequence import xarray as xr from pytest import fixture @fixture -def model() -> Text: +def model() -> str: return "medium" @@ -36,7 +36,7 @@ def test_subsector_investing_aggregation(): mca = examples.model(model) for sname in sector_list: agents = list(examples.sector(sname, model).agents) - sector = next((sector for sector in mca.sectors if sector.name == sname)) + sector = next(sector for sector in mca.sectors if sector.name == sname) technologies = sector.technologies commodities = aggregate_enduses( (agent.assets for agent in agents), technologies @@ -115,7 +115,7 @@ def test_subsector_noninvesting_aggregation(market, model, technologies, tmp_pat assert "agent" in lpcosts.coords assert isinstance(lpconstraints, Sequence) assert len(lpconstraints) == 1 - assert all((isinstance(u, xr.Dataset) for u in lpconstraints)) + assert all(isinstance(u, xr.Dataset) for u in lpconstraints) # makes sure agent investment got called assert all(agent.year == 2025 for agent in agents) diff --git a/tests/test_timeslices.py b/tests/test_timeslices.py index ed7b89cc7..49d548f05 100644 --- a/tests/test_timeslices.py +++ b/tests/test_timeslices.py @@ -156,7 +156,6 @@ def test_no_overlap(): def test_aggregate_transforms_no_aggregates(): from itertools import product - from typing import Dict from muse.timeslices import aggregate_transforms, reference_timeslice from numpy import ndarray, zeros @@ -172,7 +171,7 @@ def test_aggregate_transforms_no_aggregates(): ) vectors = aggregate_transforms(timeslice=reference) - assert isinstance(vectors, Dict) + assert isinstance(vectors, dict) assert set(vectors) == set(product(["spring", "autumn"], ["weekday", "weekend"])) for i in range(reference.shape[0]): index = reference.timeslice[i].values.tolist() @@ -185,7 +184,6 @@ def test_aggregate_transforms_no_aggregates(): def test_aggregate_transforms_with_aggregates(): from itertools import product - from typing import Dict from muse.timeslices import aggregate_transforms, reference_timeslice from toml import loads @@ -219,7 +217,7 @@ def test_aggregate_transforms_with_aggregates(): reference = reference_timeslice(toml) vectors = aggregate_transforms(toml, reference) - assert isinstance(vectors, Dict) + assert isinstance(vectors, dict) assert set(vectors) == set( product( ["winter", "spring", "summer", "autumn", "springautumn"], diff --git a/tests/test_trade.py b/tests/test_trade.py index 3e9bd5cb7..bafa07db9 100644 --- a/tests/test_trade.py +++ b/tests/test_trade.py @@ -1,4 +1,5 @@ -from typing import Any, Mapping, Text +from collections.abc import Mapping +from typing import Any import numpy as np import xarray as xr @@ -6,7 +7,7 @@ @fixture -def constraints_args(sector="power", model="trade") -> Mapping[Text, Any]: +def constraints_args(sector="power", model="trade") -> Mapping[str, Any]: from muse import examples from muse.utilities import agent_concatenation, reduce_assets diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 1cb8caa3e..9a05248de 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -1,5 +1,3 @@ -from typing import Tuple - import numpy as np import xarray as xr from pytest import approx, mark @@ -16,7 +14,7 @@ def make_array(array): "coordinates", [("technology", "installed", "region"), ("technology", "installed"), ("region",)], ) -def test_reduce_assets(coordinates: Tuple, capacity: xr.DataArray): +def test_reduce_assets(coordinates: tuple, capacity: xr.DataArray): from muse.utilities import reduce_assets actual = reduce_assets(capacity, coords=coordinates) diff --git a/tests/test_wizard.py b/tests/test_wizard.py index 978973f66..37a073b0b 100644 --- a/tests/test_wizard.py +++ b/tests/test_wizard.py @@ -153,7 +153,7 @@ def test_add_region(model_path): add_region(model_path, "R2", "R1") # Check if the new region is added to the settings.toml file - with open(model_path / "settings.toml", "r") as f: + with open(model_path / "settings.toml") as f: modified_settings_data = parse(f.read()) assert "R2" in modified_settings_data["regions"] @@ -178,7 +178,7 @@ def test_add_timeslice(model_path): add_timeslice(model_path, "midnight", "evening") # Check if the new timeslice is added to the settings.toml file - with open(model_path / "settings.toml", "r") as f: + with open(model_path / "settings.toml") as f: modified_settings_data = parse(f.read()) assert "midnight" in modified_settings_data["timeslices"]["all-year"]["all-week"] n_timeslices = len(modified_settings_data["timeslices"]["all-year"]["all-week"])