diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 000000000..e5a1f9233 --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,8 @@ +[bumpversion] +current_version = 1.0.1 +commit = True +tag = True + +[bumpversion:file:src/muse/__init__.py] + +[bumpversion:file:docs/conf.py] diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..0e75ca2d6 --- /dev/null +++ b/.flake8 @@ -0,0 +1,11 @@ +[flake8] +max-line-length = 88 +exclude = + .tox, + .git, + .venv, + venv, + build, + __pycache__, +extend-ignore = + E203,W503 \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 08376a6bc..534959e7e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -20,7 +20,7 @@ document the change (include PR #) - note reverse order of PR #s. ## Key checklist - [ ] All tests pass: `$ python -m pytest` -- [ ] The documentation builds and looks OK: `$ python setup.py build_sphinx` +- [ ] The documentation builds and looks OK: `$ python -m sphinx -b html docs docs/build` ## Further checks diff --git a/.github/workflows/broken-link-check.yml b/.github/workflows/broken-link-check.yml new file mode 100644 index 000000000..7b2e182aa --- /dev/null +++ b/.github/workflows/broken-link-check.yml @@ -0,0 +1,21 @@ +on: + schedule: + - cron: 0 0 1 * * # run monthly + repository_dispatch: # run manually + types: [check-link] + push: + branches: [main, develop] + pull_request: + types: [opened, synchronize, reopened] + +name: Broken Link Check +jobs: + check: + name: Broken Link Check + runs-on: ubuntu-latest + steps: + - name: Broken Link Check + uses: ruzickap/action-my-broken-link-checker@v2 + with: + url: https://muse-os.readthedocs.io/en/latest/ + cmd_params: '--timeout=20 --buffer-size=8192 --max-connections=3 --color=always --skip-tls-verification --header="User-Agent:curl/7.54.0"' # muffet parameters diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68351191a..5aadab0a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,12 @@ -name: QA and tests +name: QA, tests and publishing on: pull_request: types: [opened, synchronize, reopened] push: - branches: [main, develop] - + branches: [main, develop] + tags: + - '*' jobs: # Checks the style using the pre-commit hooks qa: @@ -17,14 +18,21 @@ jobs: # Only then, normal testing proceeds unit-tests: needs: qa - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.8", "3.9"] steps: - uses: actions/checkout@v3 - - name: Set up Python 3.8 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: ${{ matrix.python-version }} - name: Install dependencies run: | @@ -48,14 +56,20 @@ jobs: regression-tests: needs: qa - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.8", "3.9"] steps: - uses: actions/checkout@v3 - - name: Set up Python 3.8 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: ${{ matrix.python-version }} - name: Install dependencies run: | @@ -66,3 +80,68 @@ jobs: # The regression tests (with non coverage) - name: Regression tests run: pytest -m "regression" + + # If all tests pass, we try to build a wheel + build-wheel: + # needs: [regression-tests, unit-tests] + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Build sdist + run: | + python -m pip install --upgrade build + python -m build + + - uses: actions/upload-artifact@v3 + with: + path: dist/MUSE* + + # And if we are pushing a tag, then we try to publish it + publish-TestPyPI: + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + needs: build-wheel + name: Publish MUSE to TestPyPI + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + + - name: Download sdist artifact + uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + + - name: Display structure of downloaded files + run: ls -R dist + + - name: Publish package distributions to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + skip-existing: true + + publish-PyPI: + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + needs: publish-TestPyPI + name: Publish MUSE to PyPI + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + + - name: Download sdist artifact + uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + + - name: Display structure of downloaded files + run: ls -R dist + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e42afc4b..a2a352607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Features +- Updating pyproject.toml with valid python versions ([#121](https://github.com/SGIModel/MUSE_OS/pull/121)) +- Expand CI workflow ([#119](https://github.com/SGIModel/MUSE_OS/pull/119)) +- Convert setup.cfg to pyproject.toml and add entrypoint ([#118](https://github.com/SGIModel/MUSE_OS/pull/118)) +- Add GitHub action to check for broken links ([#115](https://github.com/SGIModel/MUSE_OS/pull/115)) - Inconsistent trade case study ([#58](https://github.com/SGIModel/MUSE_OS/pull/58)) - Update README.txt in installation path ([#57](https://github.com/SGIModel/MUSE_OS/pull/57)) - Add tests to check the case of not using retrofit agents ([#53](https://github.com/SGIModel/MUSE_OS/pull/53)) diff --git a/README.md b/README.md index 80e4e2417..f893001c0 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,139 @@ +# ModUlar energy system Simulation Environment: MUSE -Installation -============ +## Installation -Pre-requisite: A virtual environment ------------------------------------- +### Recommended way -Although not strictly necessary, creating a [conda](https://www.anaconda.com/what-is-anaconda/) -virtual environment is highly recommended: it will isolate users and developers from changes -occuring on their operating system, and from conflicts between python packages. It ensures -reproducibility from day to day. +The recommended way for **end users** to access and use the tool is via `pipx`. +This will create an isolated environment and install MUSE-OS within in one go, +also letting you to invoke `muse` anywhere in your system. -Create a virtual env including python with: +1. Install and configure [`pipx`](https://pypa.github.io/pipx/) following the + instructions appropriate for your operative system. Make sure it works well before + moving on. +2. Install MUSE-OS with `pipx install MUSE-OS`. It might take a + while to complete, but afterwards updates should be pretty fast. +3. To run MUSE-OS just open a terminal and execute `muse`, with the appropriate input + arguments, if relevant. See section below about usage. + +Whenever there is a new version of MUSE-OS, just run `pipx upgrade MUSE-OS` and +it will be downloaded and installed with no fuss. + +### Alternative way + +If you want to have a bit more control - or you don't want to use `pipx`, +just create a virtual environment first and then install `MUSE-OS`. + +Although not strictly necessary, **creating a virtual environment is highly recommended**: +it will isolate users and developers from changes occuring on their operating system, +and from conflicts between python packages. It ensures reproducibility from day to day. + +There are several ways of creating a virtual environment - below we list two of them. +Regardless of the method used, **once it has been created and activated**, you can install +`MUSE-OS` within using: ```bash -> conda create -n muse python=3.8 +python -m pip install MUSE-OS +``` + +And then use it by invoking `muse` with the relevant input arguments. + +#### Creating a virtual environment using `conda` + +Create a virtual environment including python with: + +```bash +conda create -n muse_env python=3.9 ``` Activate the environment with: ```bash -> conda activate muse +conda activate muse_env ``` Later, to recover the system-wide "normal" python, deactivate the environment with: ```bash -> conda deactivate +conda deactivate ``` -Installing muse itself ----------------------- +#### Creating a virtual environment using `venv` -Once a virtual environment has been *activated*, as describe above, we can -install muse without fear of interfering with other python jobs. Run: +Create a virtual environment with: ```bash -> python -m pip install https://github.com/SGIModel/MUSE_OS.git#egg=muse +python -m pip install venv +python -m venv muse_env ``` -Usage ------ +Activate the environment with: + +```powershell +# In Powershell +muse_env\Scripts\Activate.ps1 + +# In Linux/MacOS +source muse_env/bin/activate +``` + +Later, to recover the system-wide "normal" python, deactivate the environment with: + +```bash +deactivate +``` + +## Usage Once installed, users can: - activate the virtual environment (needed only once per session) as explained above -- run `python -m muse --model default` to run the default example model -- run `python -m muse --model default --copy XXX` to copy the model to subfolder `XXX`. -- Alternatively, run `python -m muse settings.toml`, where `settings.toml` is an input +- run `muse --model default` to run the default example model +- run `muse --model default --copy XXX` to copy the model to subfolder `XXX`. +- Alternatively, run `muse settings.toml`, where `settings.toml` is an input file for a custom model -- run `python -m muse --help` to get a description of the command-line arguments, +- run `muse --help` to get a description of the command-line arguments, including the name of any additional models provided with MUSE. -Development ------------ +## Development -It is strongly recommened to use a conda virtual environment, as above. The simplest approach is to +It is strongly recommened to use a virtual environment, as above. The simplest approach is to first download the muse code with: ```bash -> git clone https://github.com/SGIModel/MUSE_OS.git muse +> git clone https://github.com/SGIModel/MUSE_OS.git +``` + +```bash +cd MUSE_OS +# Create virtual environment - for development, this is typically called ".venv" +# Activate virtual environment. Finally, install muse: +python -m pip install -e ."[dev,doc]" ``` -And then install the working directory into the conda environment: +Please note the quotation marks. The downloaded code can then be modified. The changes will be +automatically reflected in the environment. + +To ensure the consistency of the code with other developers, install the pre-commit hooks with: ```bash -> # after activating the virtual environment with: -> # conda activate muse -> python -m pip install -e ."muse[dev,doc]" +python -m pip install pre-commit +pre-commit install ``` -Please note the quotation marks. `muse` in the last line above is the path to source code that was -just downloaded with `git`. The downloaded code can then be modified. The changes will be -automatically reflected in the conda environment. +This will ensure that a series of quality assurance tools are run with every commit you make. -In the developing phase, MUSE can also be used to run test cases to check that the model would reproduce expected results from a defined set of input data. -Tests can be run with the command [pytest](https://docs.pytest.org/en/latest/), from -theb testing framework of the same name. +In the developing phase, MUSE can also be used to run test cases to check that the model would reproduce expected results from a defined set of input data. Tests can be run with the command [pytest](https://docs.pytest.org/en/latest/), from the testing framework of the same name. The documentation can be built with: ```bash -> python setup.py docs +python -m sphinx -b html docs docs/build ``` The main page for the documentation can then be found at -`build\\sphinx\\html\\index.html` (or `build/sphinx/html/index.html` on Mac and Linux). +`docs\\build\\html\\index.html` (or `docs/build/html/index.html` on Mac and Linux). The file can viewed from any web browser. [vscode](https://code.visualstudio.com/) users will find that the repository is setup @@ -95,7 +143,6 @@ or conda environment where to run the code. This will change the `.vscode/settin file and add a user-specific path to it. Users should try and avoid commiting changes to `.vscode/settings.json` indiscriminately. -Copyright ---------- +## Copyrigh -Copyright © 2021 Imperial College London +Copyright © 2023 Imperial College London diff --git a/docs/conf.py b/docs/conf.py index 738bc1b47..c8ae25d54 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,7 +9,7 @@ project = "MUSE" copyright = "2022, Sustainable Gas Institute" author = "Imperial College London" -release = "1.0.0" +release = "1.0.1" version = ".".join(release.split(".")[:2]) # -- General configuration --------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 5450808c2..77e9330d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,108 @@ [build-system] -requires = ["setuptools>=45", "wheel", "setuptools_scm>=6.2"] +requires = ["setuptools>=61", "wheel", "setuptools_scm>=6.2"] build-backend = "setuptools.build_meta" -[tool.setuptools_scm] \ No newline at end of file +[project] +name = "MUSE_OS" +authors = [ + {name = "Sustainable Gas Institute", email = "sgi@imperial.ac.uk"}, +] +description = "Energy System Model" +readme = "README.md" +requires-python = ">= 3.8, <3.10" +keywords = ["energy", "modelling"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Intended Audience :: Science/Research", + "Intended Audience :: Other Audience", +] +dependencies = [ + "numpy==1.23.0", + "scipy", + "pandas<=1.3", + "click", + "xarray==2022.3.0", + "bottleneck", + "coloredlogs", + "toml", + "xlrd==1.2.0", + "mypy-extensions", + "pypubsub", +] +dynamic = ["version"] + +[project.optional-dependencies] +all = ["MUSE_OS[dev,doc,excel]"] +dev = [ + "pytest>4.0.2", + "flake8!=3.8.1,!=3.8.0,<4.0.0", + "black", + "pytest-flake8", + "IPython", + "jupyter", + "nbconvert", + "nbformat", + "mypy", + "numpy>=1.17", + "pytest-xdist", + "bump2version", +] +doc = [ + "sphinx", + "ipykernel", + "nbsphinx", + "myst-parser", + "sphinxcontrib-bibtex", + "ipython", + "pandoc", +] +excel = ["openpyxl"] + +[project.urls] +Homepage = "http://www.sustainablegasinstitute.org/home/muse-energy-model/" + +[project.scripts] +muse = "muse.__main__:muse_main" + +[tool.setuptools] +license-files = ["LICENSE"] +include-package-data = true + +[tool.setuptools.packages.find] +where = ["src"] +include = ["*"] +namespaces = true + +[tool.setuptools.dynamic] +version = {attr = "muse.VERSION"} + +[tool.setuptools_scm] + +[tool.pyls] +configurationsources = ["flake8"] + +[tool.pytest.ini_options] +testpaths = ["tests", "src/muse"] +addopts = "--doctest-modules --flake8 -rfE -n auto" +markers = [ + "sgidata: test that require legacy data", + "legacy: test that require legacy modules", + "regression: a regression test", + "notebook: a test which consist in running a jupyter notebook", +] + +[tool.mypy] +ignore_missing_imports = true +strict_optional = true +files = ["src/**/*.py", "tests/**/*.py"] + +[[tool.mypy.overrides]] +module = ["setup"] +ignore_errors = true + +[tool.isort] +line_length = 88 +multi_line_output = 3 +include_trailing_comma = true \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 64ce969c7..000000000 --- a/setup.cfg +++ /dev/null @@ -1,131 +0,0 @@ -[bumpversion] -current_version = "1.0" -commit = True -tag = True - -[metadata] -name = MUSE -version = attr: src.muse.VERSION -description = Energy System Model -classifiers = - Development Status :: 3 - Alpha - Programming Language :: Python :: 3.8 - Intended Audience :: Science - Intended Audience :: Research - Intended Audience :: Economists - Intended Audience :: Energy Experts - Intended Audience :: Climate Mitigation Experts - Intended Audience :: Industry -keywords = ["energy", "modelling"] -author = Sustainable Gas Institute -author_email = sgi@imperial.ac.uk -url = http://www.sustainablegasinstitute.org/home/muse-energy-model/ - -[options] -python_requires = >= 3.7 -package_dir = - =src -packages = find: -include_package_data = True -install_requires = - numpy==1.23.0 - scipy - pandas<=1.3 - click - xarray==2022.3.0 - bottleneck - coloredlogs - toml - xlrd==1.2.0 - mypy-extensions - pypubsub - -[options.packages.find] -where = src - -[options.package_data] -* = **/*.toml, **/*.csv - -[options.extras_require] -all = - %(dev)s - %(doc)s - %(excel)s -dev = - pytest>4.0.2 - flake8!=3.8.1,!=3.8.0,<4.0.0 - black - pytest-flake8 - IPython - jupyter - nbconvert - nbformat - mypy - numpy>=1.17 - pytest-xdist - bump2version -doc = - sphinx==5.0.2 - ipykernel - nbsphinx - myst-parser - sphinxcontrib-bibtex - ipython - pandoc -excel = - openpyxl -private_sgi_model = - SGIModelData @ git+https://github.com/SGIModel/SGIModelData.git@master - StarMUSELegacy @ git+https://github.com/SGIModel/StarMuse.git@archive/legacy - -[bumpversion:file:src/muse/__init__.py] - -[bumpversion:file:docs/conf.py] - -[pyls] -configurationSources = ['flake8'] - -[aliases] -test = pytest - -[tool:pytest] -testpaths = tests src/muse -addopts = --doctest-modules --flake8 -rfE -n auto -markers = - sgidata: test that require legacy data - legacy: test that require legacy modules - regression: a regression test - notebook: a test which consist in running a jupyter notebook - -[pycodestyle] -max-line-length = 88 -ignore = E203,W503 - -[flake8] -max-line-length = 88 -exclude = - .tox, - .git, - build, - __pycache__, -ignore = E203,W503 - -[mypy] -ignore_missing_imports = True -strict_optional = True -files = src/**/*.py,tests/**/*.py - -[mypy-setup] -ignore_errors = True - -[isort] -line_length = 88 -multi_line_output = 3 -include_trailing_comma = true - -[build_sphinx] -project = 'MUSE' -version = attr: src.muse.VERSION -release = attr: src.muse.VERSION -source-dir = docs -build-dir = docs/build diff --git a/setup.py b/setup.py deleted file mode 100644 index b908cbe55..000000000 --- a/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -import setuptools - -setuptools.setup() diff --git a/src/muse/__init__.py b/src/muse/__init__.py index 91a02d813..16633f2de 100644 --- a/src/muse/__init__.py +++ b/src/muse/__init__.py @@ -1,5 +1,5 @@ """MUSE model""" -VERSION = "1.0.0" +VERSION = "1.0.1" def _create_logger(): diff --git a/src/muse/__main__.py b/src/muse/__main__.py index fc0346507..7a12cc995 100644 --- a/src/muse/__main__.py +++ b/src/muse/__main__.py @@ -1,55 +1,57 @@ """Makes MUSE executable.""" +import click + +INPUT_PATH = click.Path(exists=False, file_okay=True, resolve_path=True) +MODEL_PATH = click.Path(file_okay=False, resolve_path=True) +MODELS = click.Choice( + ["default", "multiple-agents", "medium", "minimum-service", "trade"] +) + + +@click.command(context_settings=dict(help_option_names=["-h", "--help"])) +@click.argument("settings", default="settings.toml", type=INPUT_PATH) +@click.option( + "--model", + type=MODELS, + help="Runs a model distributed with MUSE. SETTINGS is ignored.", +) +@click.option( + "--copy", + type=MODEL_PATH, + help=( + "Folder where to copy the model specified by the '--model' option. " + "The folder must not exist: this command will refuse to overwrite existing " + "data. Exits without running the model." + ), +) +def muse_main(settings, model, copy): + """Runs a MUSE simulation. + + SETTINGS should be a .toml file. + """ + from logging import getLogger + from pathlib import Path + + from muse import examples + from muse.mca import MCA + from muse.readers.toml import read_settings + + if (not model) and not Path(settings).exists(): + print(f"Invalid or missing input: file {settings} does not exist.") + return + + if copy: + examples.copy_model(name=model if model else "default", path=copy) + elif model: + examples.model(model).run() + else: + settings = read_settings(settings) + getLogger("muse").setLevel(settings.log_level) + MCA.factory(settings).run() + + if "__main__" == __name__: from sys import argv, executable - import click - - INPUT_PATH = click.Path(exists=False, file_okay=True, resolve_path=True) - MODEL_PATH = click.Path(file_okay=False, resolve_path=True) - MODELS = click.Choice( - ["default", "multiple-agents", "medium", "minimum-service", "trade"] - ) - - @click.command(context_settings=dict(help_option_names=["-h", "--help"])) - @click.argument("settings", default="settings.toml", type=INPUT_PATH) - @click.option( - "--model", - type=MODELS, - help="Runs a model distributed with MUSE. SETTINGS is ignored.", - ) - @click.option( - "--copy", - type=MODEL_PATH, - help=( - "Folder where to copy the model specified by the '--model' option. " - "The folder must not exist: this command will refuse to overwrite existing " - "data. Exits without running the model." - ), - ) - def muse_main(settings, model, copy): - """Runs a MUSE simulation. - - SETTINGS should be a .toml file. - """ - from logging import getLogger - from pathlib import Path - - from muse import examples - from muse.mca import MCA - from muse.readers.toml import read_settings - - if (not model) and not Path(settings).exists(): - print(f"Invalid or missing input: file {settings} does not exist.") - return - - if copy: - examples.copy_model(name=model if model else "default", path=copy) - elif model: - examples.model(model).run() - else: - settings = read_settings(settings) - getLogger("muse").setLevel(settings.log_level) - MCA.factory(settings).run() - argv[0] = "%s -m muse" % executable muse_main() diff --git a/src/muse/demand_share.py b/src/muse/demand_share.py index 0b09d0a74..5ab0a1b51 100644 --- a/src/muse/demand_share.py +++ b/src/muse/demand_share.py @@ -99,7 +99,7 @@ def demand_share( agents: Sequence[AbstractAgent], market: xr.Dataset, technologies: xr.Dataset, - **kwargs + **kwargs, ) -> xr.DataArray: from copy import copy diff --git a/src/muse/filters.py b/src/muse/filters.py index bc36e344d..bb8dc905f 100644 --- a/src/muse/filters.py +++ b/src/muse/filters.py @@ -214,12 +214,12 @@ def factory( functions = [ partial( SEARCH_SPACE_INITIALIZERS[initial_settings["name"]], - **{k: v for k, v in initial_settings.items() if k != "name"} + **{k: v for k, v in initial_settings.items() if k != "name"}, ), *( partial( SEARCH_SPACE_FILTERS[setting["name"]], - **{k: v for k, v in setting.items() if k != "name"} + **{k: v for k, v in setting.items() if k != "name"}, ) for setting in parameters ), @@ -242,7 +242,7 @@ def same_enduse( technologies: xr.Dataset, *args, enduse_label: Text = "service", - **kwargs + **kwargs, ) -> xr.DataArray: """Only allow for technologies with at least the same end-use.""" from muse.commodities import is_enduse @@ -335,7 +335,7 @@ def maturity( technologies: xr.Dataset, market: xr.Dataset, enduse_label: Text = "service", - **kwargs + **kwargs, ) -> xr.DataArray: """Only allows technologies that have achieve a given market share. @@ -359,7 +359,7 @@ def spend_limit( technologies: xr.Dataset, market: xr.Dataset, enduse_label: Text = "service", - **kwargs + **kwargs, ) -> xr.DataArray: """Only allows technologies that have achieve a given market share. @@ -381,7 +381,7 @@ def compress( search_space: xr.DataArray, technologies: xr.Dataset, market: xr.Dataset, - **kwargs + **kwargs, ) -> xr.DataArray: """Compress search space to include only potential technologies. @@ -405,7 +405,7 @@ def reduce_asset( search_space: xr.DataArray, technologies: xr.Dataset, market: xr.Dataset, - **kwargs + **kwargs, ) -> xr.DataArray: """Reduce over assets.""" return search_space.any("asset") if "asset" in search_space.dims else search_space @@ -417,7 +417,7 @@ def with_asset_technology( search_space: xr.DataArray, technologies: xr.Dataset, market: xr.Dataset, - **kwargs + **kwargs, ) -> xr.DataArray: """Search space *also* contains its asset technology for each asset.""" return search_space | (search_space.asset == search_space.replacement) @@ -447,7 +447,7 @@ def initialize_from_assets( technologies: xr.Dataset, *args, coords: Sequence[Text] = ("region", "technology"), - **kwargs + **kwargs, ): """Initialize a search space from existing technologies.""" from muse.utilities import reduce_assets