diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index cae59ac5d..0fff3445d 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -30,16 +30,7 @@ jobs: - "3.12" - "3.13" # Latest supported by ixmp gams-version: - # Version used until 2024-07; disabled - # - 25.1.1 - # First version including a macOS arm64 distribution - - 43.4.1 - - # commented: force a specific version of pandas, for e.g. pre-release - # testing - # pandas-version: - # - "" - # - "==2.0.0rc0" + - "43.4.1" # First version including a macOS arm64 distribution exclude: # Specific version combinations that are invalid / not to be used @@ -55,8 +46,6 @@ jobs: runs-on: ${{ matrix.os }} name: ${{ matrix.os }}-py${{ matrix.python-version }} - # commented: use with "pandas-version" in the matrix, above - # name: ${{ matrix.os }}-py${{ matrix.python-version }}-pandas${{ matrix.pandas-version }} steps: - uses: actions/checkout@v4 @@ -64,20 +53,22 @@ jobs: fetch-depth: ${{ env.depth }} fetch-tags: true - - uses: actions/setup-python@v5 + - name: TEMPORARY Work around actions/checkout#2041 + run: git fetch --tags + + - name: Set up uv, Python + uses: astral-sh/setup-uv@v5 with: + cache-dependency-glob: "**/pyproject.toml" python-version: ${{ matrix.python-version }} - cache: pip - cache-dependency-path: "**/pyproject.toml" - uses: ts-graphviz/setup-graphviz@v2 - # TEMPORARY Work around ts-graphviz/setup-graphviz#630 - if: ${{ ! startswith(matrix.os, 'macos-') }} + # Work around ts-graphviz/setup-graphviz#630 + if: matrix.os != 'macos-13' - uses: r-lib/actions/setup-r@v2 id: setup-r - with: - r-version: "4.4.1" + with: { r-version: "4.4.1" } - name: Cache GAMS installer and R packages uses: actions/cache@v4 @@ -95,45 +86,21 @@ jobs: license: ${{ secrets.GAMS_LICENSE }} - name: Set RETICULATE_PYTHON - # Use the environment variable set by the setup-python action, above. - run: echo "RETICULATE_PYTHON=$pythonLocation" >> $GITHUB_ENV + # Retrieve the Python executable set up above + run: echo "RETICULATE_PYTHON=$(uv python find)" >> $GITHUB_ENV shell: bash - - name: Install Python package and dependencies - # [docs] contains [tests], which contains [report,tutorial] - run: | - pip install .[docs] - - # commented: use with "pandas-version" in the matrix, above - # pip install --upgrade pandas${{ matrix.pandas-version }} - - # TEMPORARY With Python 3.13 pyam-iamc resolves to 1.3.1, which in turn - # limits pint < 0.17. Override. cf. iiasa/ixmp#544 - pip install --upgrade pint + - name: Install the package and dependencies + # [docs] requires [tests] which requires [report,tutorial] + run: uv pip install .[docs] - - name: Install R dependencies and tutorial requirements - # Workaround for https://github.com/actions/runner-images/issues/11137 - if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' }} - run: | - install.packages(c("remotes", "Rcpp")) - remotes::install_cran( - c("IRkernel", "reticulate"), - dependencies = TRUE, - # force = TRUE, - ) - - reticulate::py_config() - shell: Rscript {0} + - name: "Install libpng-dev" # for R 'png', required by reticulate + if: startsWith(matrix.os, 'ubuntu-') + run: sudo apt install libpng-dev - name: Install R dependencies and tutorial requirements - if: ${{ ! (matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12') }} run: | - install.packages(c("remotes", "Rcpp")) - remotes::install_cran( - c("IRkernel", "reticulate"), - dependencies = TRUE, - # force = TRUE, - ) + install.packages(c("IRkernel", "reticulate")) # commented: for debugging # print(reticulate::py_config()) @@ -142,23 +109,20 @@ jobs: IRkernel::installspec() shell: Rscript {0} - - name: Run test suite using pytest + - name: Run tests run: | - pytest ixmp \ + uv run --no-sync \ + pytest ixmp \ -m "not performance" \ - --color=yes -rA --verbose \ + --color=yes --durations=20 -rA --verbose \ --cov-report=xml \ --numprocesses=auto --dist=loadgroup shell: bash - name: Upload test coverage to Codecov.io uses: codecov/codecov-action@v5 - # FIXME Limit runtime until - # https://github.com/codecov/codecov-action/issues/1316 is resolved - timeout-minutes: 1 - continue-on-error: true with: - token: ${{ secrets.CODECOV_TOKEN }} # required + token: ${{ secrets.CODECOV_TOKEN}} pre-commit: name: Code quality @@ -167,12 +131,11 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: { python-version: "3.12" } - - - name: Force recreation of pre-commit virtual environment for mypy - if: github.event_name == 'schedule' - run: gh cache list -L 999 | cut -f2 | grep pre-commit | xargs -I{} gh cache delete "{}" || true - env: { GH_TOKEN: "${{ github.token }}" } - - - uses: pre-commit/action@v3.0.1 + - uses: astral-sh/setup-uv@v5 + with: { cache-dependency-glob: "**/pyproject.toml" } + - uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: pre-commit|${{ env.UV_PYTHON }}|${{ hashFiles('.pre-commit-config.yaml') }} + lookup-only: ${{ github.event_name == 'schedule' }} # Set 'true' to recreate cache + - run: uvx pre-commit run --all-files --color=always --show-diff-on-failure diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 89b28ca82..8cb52c583 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,27 +1,20 @@ repos: -- repo: local +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.14.1 hooks: - id: mypy - name: mypy - always_run: true - require_serial: true pass_filenames: false - - language: python - entry: bash -c ". ${PRE_COMMIT_MYPY_VENV:-/dev/null}/bin/activate 2>/dev/null; mypy $0 $@" additional_dependencies: - - mypy >= 1.9.0 - genno - GitPython - nbclient - pandas-stubs - pytest - - sphinx + - Sphinx - werkzeug - xarray - args: ["."] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.4 + rev: v0.9.1 hooks: - id: ruff - id: ruff-format diff --git a/README.md b/README.md index 359a98659..5bcb4e5a9 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ See [`doc/README.rst`](doc/README.rst) for further details. ## License -Copyright © 2017–2024 IIASA Energy, Climate, and Environment (ECE) program +Copyright © 2017–2025 IIASA Energy, Climate, and Environment (ECE) program `ixmp` is licensed under the Apache License, Version 2.0 (the "License"); you may not use the files in this repository except in compliance with the License. diff --git a/doc/conf.py b/doc/conf.py index a705e4c2c..57c34ddfa 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -10,7 +10,7 @@ # -- Project information --------------------------------------------------------------- project = "ixmp" -copyright = "2017–2024, IIASA Energy, Climate, and Environment (ECE) program" +copyright = "2017–%Y, IIASA Energy, Climate, and Environment (ECE) program" author = "ixmp Developers" diff --git a/ixmp/backend/jdbc.py b/ixmp/backend/jdbc.py index 5c4e83ad8..ac8c864dc 100644 --- a/ixmp/backend/jdbc.py +++ b/ixmp/backend/jdbc.py @@ -170,7 +170,7 @@ def _domain_enum(domain): return domain_enum.valueOf(domain.upper()) except java.IllegalArgumentException: domains = ", ".join([d.name().lower() for d in domain_enum.values()]) - raise ValueError(f"No such domain: {domain}, " f"existing domains: {domains}") + raise ValueError(f"No such domain: {domain}, existing domains: {domains}") def _unwrap(v): @@ -886,8 +886,7 @@ def clone( # Raise exceptions for limitations of JDBCBackend if not isinstance(platform_dest._backend, self.__class__): raise NotImplementedError( # pragma: no cover - f"Clone between {self.__class__} and" - f"{platform_dest._backend.__class__}" + f"Clone between {self.__class__} and{platform_dest._backend.__class__}" ) elif platform_dest._backend is not self: package = s.__class__.__module__.split(".")[0] diff --git a/ixmp/cli.py b/ixmp/cli.py index d4649d28b..9fa61d28c 100644 --- a/ixmp/cli.py +++ b/ixmp/cli.py @@ -91,7 +91,7 @@ def report(context, config, key): if not context: raise click.UsageError( - "give either --url, --platform or --dbprops " "before command report" + "give either --url, --platform or --dbprops before command report" ) # Instantiate the Reporter with the Scenario loaded by main() @@ -227,8 +227,7 @@ def import_group(context): """ if not context or "scen" not in context: raise click.UsageError( - "give --url, or --platform, --model, and " - "--scenario, before command import" + "give --url, or --platform, --model, and --scenario, before command import" ) @@ -407,7 +406,7 @@ def list_scenarios(context, **kwargs): if not context: raise click.UsageError( - "give either --url, --platform or --dbprops " "before command list" + "give either --url, --platform or --dbprops before command list" ) print( diff --git a/ixmp/tests/backend/test_jdbc.py b/ixmp/tests/backend/test_jdbc.py index efc765c27..3642ee80c 100644 --- a/ixmp/tests/backend/test_jdbc.py +++ b/ixmp/tests/backend/test_jdbc.py @@ -373,8 +373,7 @@ def test_verbose_exception(test_mp, exception_verbose_true): exc_msg = exc_info.value.args[0] assert ( - "There exists no Scenario 'foo|bar' " - "(version: -1) in the database!" in exc_msg + "There exists no Scenario 'foo|bar' (version: -1) in the database!" in exc_msg ) assert "at.ac.iiasa.ixmp.database.DbDAO.getRunId" in exc_msg assert "at.ac.iiasa.ixmp.Platform.getScenario" in exc_msg @@ -595,7 +594,7 @@ def test_reload_cycle( mp = ixmp.Platform(**platform_args) # Load existing Scenario - s0 = ixmp.Scenario(mp, model="foo", scenario=f"bar {i-1}", version=1) + s0 = ixmp.Scenario(mp, model="foo", scenario=f"bar {i - 1}", version=1) memory_usage(f"pass {i} -- platform instantiated") diff --git a/ixmp/tests/core/test_scenario.py b/ixmp/tests/core/test_scenario.py index 3592280f0..f2b17b1ef 100644 --- a/ixmp/tests/core/test_scenario.py +++ b/ixmp/tests/core/test_scenario.py @@ -137,7 +137,7 @@ def test_init_set(self, scen): # Add set on a locked scenario with pytest.raises( RuntimeError, - match="This Scenario cannot be edited" ", do a checkout first!", + match="This Scenario cannot be edited, do a checkout first!", ): scen.init_set("foo") @@ -399,7 +399,7 @@ def test_excel_io(self, scen, scen_empty, tmp_path, caplog): # With init_items=False, can't be read into an empty Scenario. # Exception raised is the first index set, alphabetically - with pytest.raises(ValueError, match="no set 'i'; " "try init_items=True"): + with pytest.raises(ValueError, match="no set 'i'; try init_items=True"): scen_empty.read_excel(tmp_path) # File can be read with init_items=True @@ -441,7 +441,7 @@ def test_excel_io(self, scen, scen_empty, tmp_path, caplog): # Fails with add_units=False with pytest.raises( - ValueError, match="The unit 'pounds' does not exist" " in the database!" + ValueError, match="The unit 'pounds' does not exist in the database!" ): s.read_excel(tmp_path, init_items=True) @@ -623,7 +623,7 @@ def test_set(scen_empty) -> None: scen.add_set("i", ["i9", "extra"], ["i9 comment"]) # Missing element in the index set with pytest.raises( - ValueError, match="The index set 'i' does not have an " "element 'bar'!" + ValueError, match="The index set 'i' does not have an element 'bar'!" ): scen.add_set("foo", "bar") diff --git a/pyproject.toml b/pyproject.toml index ede4e147c..13989297e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,8 +82,9 @@ exclude_also = [ omit = ["ixmp/util/sphinx_linkcode_github.py"] [tool.mypy] -exclude = [ - "build/", +files = [ + "doc", + "ixmp", ] [[tool.mypy.overrides]] diff --git a/tutorial/transport/py_transport.ipynb b/tutorial/transport/py_transport.ipynb index efac6e40d..a36cd9fa6 100644 --- a/tutorial/transport/py_transport.ipynb +++ b/tutorial/transport/py_transport.ipynb @@ -54,8 +54,9 @@ "metadata": {}, "outputs": [], "source": [ - "# load required packages \n", + "# Import required packages\n", "import pandas as pd\n", + "\n", "import ixmp" ] }, @@ -65,7 +66,7 @@ "metadata": {}, "outputs": [], "source": [ - "# launch the ix modeling platform using the default back end\n", + "# Launch the ix modeling platform using the default back end\n", "mp = ixmp.Platform()\n", "\n", "# The following lines have the same effect:\n", @@ -79,14 +80,14 @@ "metadata": {}, "outputs": [], "source": [ - "# details for creating a new scenario in the ix modeling platform \n", + "# details for creating a new scenario in the ix modeling platform\n", "model = \"transport problem\"\n", "scenario = \"standard\"\n", - "annot = \"Dantzig's transportation problem for illustration and testing\" \n", + "annot = \"Dantzig's transportation problem for illustration and testing\"\n", "\n", "# initialize a new ixmp.Scenario\n", "# the parameter version='new' indicates that this is a new scenario instamce\n", - "scen = ixmp.Scenario(mp, model, scenario, version='new', annotation=annot)" + "scen = ixmp.Scenario(mp, model, scenario, version=\"new\", annotation=annot)" ] }, { @@ -114,11 +115,11 @@ "metadata": {}, "outputs": [], "source": [ - "# define the sets of locations of canning plants and markets \n", + "# define the sets of locations of canning plants and markets\n", "scen.init_set(\"i\")\n", "scen.add_set(\"i\", [\"seattle\", \"san-diego\"])\n", "scen.init_set(\"j\")\n", - "scen.add_set(\"j\", [\"new-york\", \"chicago\", \"topeka\"]) " + "scen.add_set(\"j\", [\"new-york\", \"chicago\", \"topeka\"])" ] }, { @@ -128,7 +129,7 @@ "outputs": [], "source": [ "# display the set 'i'\n", - "scen.set('i')" + "scen.set(\"i\")" ] }, { @@ -161,19 +162,19 @@ "metadata": {}, "outputs": [], "source": [ - "# capacity of plant i in cases \n", - "# add parameter elements one-by-one (string and value) \n", + "# capacity of plant i in cases\n", + "# add parameter elements one-by-one (string and value)\n", "scen.init_par(\"a\", idx_sets=\"i\")\n", "scen.add_par(\"a\", \"seattle\", 350, \"cases\")\n", "scen.add_par(\"a\", \"san-diego\", 600, \"cases\")\n", "\n", - "# demand at market j in cases \n", - "# add parameter elements as dataframe (with index names) \n", + "# demand at market j in cases\n", + "# add parameter elements as dataframe (with index names)\n", "scen.init_par(\"b\", idx_sets=\"j\")\n", "b_data = [\n", - " {'j': \"new-york\", 'value': 325, 'unit': \"cases\"},\n", - " {'j': \"chicago\", 'value': 300, 'unit': \"cases\"},\n", - " {'j': \"topeka\", 'value': 275, 'unit': \"cases\"}\n", + " {\"j\": \"new-york\", \"value\": 325, \"unit\": \"cases\"},\n", + " {\"j\": \"chicago\", \"value\": 300, \"unit\": \"cases\"},\n", + " {\"j\": \"topeka\", \"value\": 275, \"unit\": \"cases\"},\n", "]\n", "b = pd.DataFrame(b_data)\n", "scen.add_par(\"b\", b)" @@ -185,7 +186,7 @@ "metadata": {}, "outputs": [], "source": [ - "scen.par('b')" + "scen.par(\"b\")" ] }, { @@ -204,14 +205,14 @@ "metadata": {}, "outputs": [], "source": [ - "# distance in thousands of miles \n", + "# distance in thousands of miles\n", "scen.init_par(\"d\", idx_sets=[\"i\", \"j\"])\n", - "# add more parameter elements as dataframe by index names \n", + "# add more parameter elements as dataframe by index names\n", "d_data = [\n", - " {'i': \"seattle\", 'j': \"new-york\", 'value': 2.5, 'unit': \"km\"},\n", - " {'i': \"seattle\", 'j': \"chicago\", 'value': 1.7, 'unit': \"km\"},\n", - " {'i': \"seattle\", 'j': \"topeka\", 'value': 1.8, 'unit': \"km\"},\n", - " {'i': \"san-diego\", 'j': \"new-york\", 'value': 2.5, 'unit': \"km\"},\n", + " {\"i\": \"seattle\", \"j\": \"new-york\", \"value\": 2.5, \"unit\": \"km\"},\n", + " {\"i\": \"seattle\", \"j\": \"chicago\", \"value\": 1.7, \"unit\": \"km\"},\n", + " {\"i\": \"seattle\", \"j\": \"topeka\", \"value\": 1.8, \"unit\": \"km\"},\n", + " {\"i\": \"san-diego\", \"j\": \"new-york\", \"value\": 2.5, \"unit\": \"km\"},\n", "]\n", "d = pd.DataFrame(d_data)\n", "scen.add_par(\"d\", d)\n", @@ -223,9 +224,13 @@ }, { "cell_type": "raw", - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "raw" + } + }, "source": [ - "Scalar f freight in dollars per case per thousand miles /90/ ; " + "Scalar f freight in dollars per case per thousand miles /90/ ;" ] }, { @@ -234,8 +239,8 @@ "metadata": {}, "outputs": [], "source": [ - "# cost per case per 1000 miles \n", - "# initialize scalar with a value and a unit (and optionally a comment) \n", + "# cost per case per 1000 miles\n", + "# initialize scalar with a value and a unit (and optionally a comment)\n", "scen.init_scalar(\"f\", 90.0, \"USD/km\")" ] }, @@ -255,8 +260,8 @@ "# commit new scenario to the database\n", "# no changes can then be made to the scenario data until a check-out is performed\n", "comment = \"importing Dantzig's transport problem for illustration\"\n", - "comment += \" and testing of the Python interface using a generic datastructure\" \n", - "scen.commit(comment) \n", + "comment += \" and testing of the Python interface using a generic datastructure\"\n", + "scen.commit(comment)\n", "\n", "# set this new scenario as the default version for the model/scenario name\n", "scen.set_as_default()" @@ -273,12 +278,16 @@ }, { "cell_type": "raw", - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "raw" + } + }, "source": [ "Variables\n", " x(i,j) shipment quantities in cases\n", " z total transportation costs in thousands of dollars ;\n", - " \n", + "\n", "Equations\n", " cost define objective function\n", " supply(i) observe supply limit at plant i\n", @@ -321,7 +330,7 @@ "metadata": {}, "outputs": [], "source": [ - "scen.solve(model='dantzig')" + "scen.solve(model=\"dantzig\")" ] }, { @@ -357,7 +366,8 @@ "metadata": {}, "outputs": [], "source": [ - "# display the quantities and marginals (=shadow prices) of the demand balance constraints\n", + "# Display the quantities and marginals (=shadow prices) of the demand balance\n", + "# constraints\n", "scen.equ(\"demand\")" ] }, diff --git a/tutorial/transport/py_transport_scenario.ipynb b/tutorial/transport/py_transport_scenario.ipynb index 23d2e075c..e75cd95ba 100644 --- a/tutorial/transport/py_transport_scenario.ipynb +++ b/tutorial/transport/py_transport_scenario.ipynb @@ -38,8 +38,7 @@ "metadata": {}, "outputs": [], "source": [ - "# load required packages\n", - "import pandas as pd\n", + "# Import required packages\n", "import ixmp" ] }, @@ -60,8 +59,8 @@ "outputs": [], "source": [ "# Model and scenario name for Dantzig's transport problem\n", - "model = 'canning problem'\n", - "scenario = 'standard'" + "model = \"canning problem\"\n", + "scenario = \"standard\"" ] }, { @@ -106,7 +105,8 @@ "outputs": [], "source": [ "from ixmp.testing import make_dantzig\n", - "scen = make_dantzig(mp, solve='.')" + "\n", + "scen = make_dantzig(mp, solve=\".\")" ] }, { @@ -135,8 +135,9 @@ "metadata": {}, "outputs": [], "source": [ - "# show only the distances for connections from Seattle by filtering the pandas.DataFrame returned above\n", - "d[d['i'] == \"seattle\"]" + "# Show only the distances for connections from Seattle by filtering the pandas.DataFrame\n", + "# returned above\n", + "d[d[\"i\"] == \"seattle\"]" ] }, { @@ -148,8 +149,8 @@ "# for faster access or more complex filtering,\n", "# it may be easier to only load specific parameter elements using a dictionary\n", "ele_filter = {}\n", - "ele_filter['i'] = ['seattle']\n", - "ele_filter['j'] = ['chicago', 'topeka']\n", + "ele_filter[\"i\"] = [\"seattle\"]\n", + "ele_filter[\"j\"] = [\"chicago\", \"topeka\"]\n", "\n", "d_filtered = scen.par(\"d\", ele_filter)\n", "d_filtered" @@ -172,7 +173,12 @@ "outputs": [], "source": [ "# create a new scenario by cloning the scenario (without keeping the solution)\n", - "scen_detroit = scen.clone(model=model, scenario='detroit', annotation='extend the Transport problem by a new city', keep_solution=False)" + "scen_detroit = scen.clone(\n", + " model=model,\n", + " scenario=\"detroit\",\n", + " annotation=\"extend the Transport problem by a new city\",\n", + " keep_solution=False,\n", + ")" ] }, { @@ -192,13 +198,13 @@ "outputs": [], "source": [ "# reduce demand in chicago\n", - "scen_detroit.add_par('b', 'chicago', 200, 'cases')\n", + "scen_detroit.add_par(\"b\", \"chicago\", 200, \"cases\")\n", "\n", "# add a new city with demand and distances\n", - "scen_detroit.add_set('j', 'detroit')\n", - "scen_detroit.add_par('b', 'detroit', 150, 'cases')\n", - "scen_detroit.add_par('d', ['seattle', 'detroit'], 1.7, 'cases')\n", - "scen_detroit.add_par('d', ['san-diego', 'detroit'], 1.9, 'cases')" + "scen_detroit.add_set(\"j\", \"detroit\")\n", + "scen_detroit.add_par(\"b\", \"detroit\", 150, \"cases\")\n", + "scen_detroit.add_par(\"d\", [\"seattle\", \"detroit\"], 1.7, \"cases\")\n", + "scen_detroit.add_par(\"d\", [\"san-diego\", \"detroit\"], 1.9, \"cases\")" ] }, { @@ -226,7 +232,7 @@ "metadata": {}, "outputs": [], "source": [ - "scen_detroit.solve(model='dantzig')" + "scen_detroit.solve(model=\"dantzig\")" ] }, { @@ -249,21 +255,21 @@ "outputs": [], "source": [ "# display the objective value of the solution in the baseline scenario\n", - "scen.var('z')" + "scen.var(\"z\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "name": "scen-detroit-z" - } + "jupyter": { + "name": "scen-detroit-z" + } }, "outputs": [], "source": [ "# display the objective value of the solution in the \"detroit\" scenario\n", - "scen_detroit.var('z')" + "scen_detroit.var(\"z\")" ] }, { @@ -272,8 +278,9 @@ "metadata": {}, "outputs": [], "source": [ - "# display the quantities transported from canning plants to demand locations in the baseline scenario\n", - "scen.var('x')" + "# Display the quantities transported from canning plants to demand locations in the\n", + "# baseline scenario\n", + "scen.var(\"x\")" ] }, { @@ -282,8 +289,9 @@ "metadata": {}, "outputs": [], "source": [ - "# display the quantities transported from canning plants to demand locations in the \"detroit\" scenario\n", - "scen_detroit.var('x')" + "# Display the quantities transported from canning plants to demand locations in the\n", + "# \"detroit\" scenario\n", + "scen_detroit.var(\"x\")" ] }, { @@ -292,7 +300,8 @@ "metadata": {}, "outputs": [], "source": [ - "# display the quantities and marginals (=shadow prices) of the demand balance constraints in the baseline scenario\n", + "# Display the quantities and marginals (=shadow prices) of the demand balance\n", + "# constraints in the baseline scenario\n", "scen.equ(\"demand\")" ] }, @@ -302,7 +311,8 @@ "metadata": {}, "outputs": [], "source": [ - "# display the quantities and marginals (=shadow prices) of the demand balance constraints in the \"detroit\" scenario\n", + "# Display the quantities and marginals (=shadow prices) of the demand balance\n", + "# constraints in the \"detroit\" scenario\n", "scen_detroit.equ(\"demand\")" ] },