diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..419c478a3 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,43 @@ +name: Black + +on: + push: + branches: + - master + - dev + pull_request: + branches: + - master + - dev + workflow_dispatch: + schedule: + - cron: "0 5 * * 6" # 5:00 UTC every Saturday + +jobs: + run-linters: + name: Run linters + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.9 + + - name: Install Python dependencies + run: pip install black flake8 + + - name: Run linters + uses: samuelmeuli/lint-action@v1 + with: + github_token: ${{ secrets.github_token }} + # Enable linters + black: true + flake8: false + # Mark the following line true if you want linters to attempt to autocorrect your code + auto_fix: false + git_name: "Greene Lab Linter" + git_email: "csgreene@upenn.edu" diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml new file mode 100644 index 000000000..68e876b30 --- /dev/null +++ b/.github/workflows/packaging.yml @@ -0,0 +1,37 @@ +name: packaging + +on: + # Make sure packaging process is not broken + push: + branches: [master, dev] + pull_request: + # Make a package for release + release: + types: [published] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: [3.9] + + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools setuptools_scm twine wheel + - name: Create packages + run: python setup.py sdist bdist_wheel + - name: Run twine check + run: twine check dist/* + - uses: actions/upload-artifact@v2 + with: + name: tox-gh-actions-dist + path: dist diff --git a/.github/workflows/tox_checks.yml b/.github/workflows/tox_checks.yml new file mode 100644 index 000000000..ea394c23a --- /dev/null +++ b/.github/workflows/tox_checks.yml @@ -0,0 +1,54 @@ +# NB: this name is used in the status badge +name: tox checks + +on: + push: + branches: + - master + - dev + pull_request: + branches: + - master + - dev + workflow_dispatch: + schedule: + - cron: "0 5 * * 6" # 5:00 UTC every Saturday + +jobs: + lint: + name: ${{ matrix.toxenv }} + runs-on: ubuntu-latest + + strategy: + matrix: + toxenv: + - clean + - check + - docs + + steps: + - name: Git clone + uses: actions/checkout@v2 + + - name: Set up Python ${{ env.default_python || '3.9' }} + uses: actions/setup-python@v2 + with: + python-version: "${{ env.default_python || '3.9' }}" + + - name: Pip cache + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.toxenv }}-${{ hashFiles('tox.ini', 'setup.py') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.toxenv }}- + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U setuptools wheel + python -m pip install -U tox + + - name: Run ${{ matrix.toxenv }} + run: python -m tox -e ${{ matrix.toxenv }} diff --git a/.github/workflows/tox_pytests.yml b/.github/workflows/tox_pytests.yml new file mode 100644 index 000000000..9f9ff0336 --- /dev/null +++ b/.github/workflows/tox_pytests.yml @@ -0,0 +1,45 @@ +name: tox pytests + +on: + push: + branches: + - master + - dev + pull_request: + branches: + - master + - dev + workflow_dispatch: + schedule: + - cron: "0 5 * * 6" # 5:00 UTC every Saturday + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v1 + - name: Install xmllint + run: sudo apt install coinor-cbc + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox tox-gh-actions coverage coveralls + - name: Test with tox + run: tox + + - name: Check test coverage + run: coverage report -m --fail-under=${{ matrix.vcs == 'bzr' && 89 || 90 }} + + - name: Report to coveralls + run: coveralls + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_SERVICE_NAME: github diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 712958a05..000000000 --- a/.travis.yml +++ /dev/null @@ -1,62 +0,0 @@ -language: python -dist: xenial -cache: false -env: - global: - - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - - SEGFAULT_SIGNALS=all - -addons: - apt: - packages: - - coinor-cbc - -matrix: - include: - - python: '3.6' - env: - - TOXENV=check - - python: '3.7' - env: - - TOXENV=docs - - env: - - TOXENV=py36,codecov,coveralls - python: '3.6' - - env: - - TOXENV=py37,codecov,coveralls - python: '3.7' -# - os: osx -# language: generic -# env: -# - TOXENV=py37 - - env: - - TOXENV=py38,codecov,coveralls - python: '3.8' - - env: - - TOXENV=py3-nocov - python: '3.8' -before_install: - - python --version - - uname -a - - lsb_release -a || true - - | - if [[ $TRAVIS_OS_NAME == 'osx' ]]; then - [[ $TOXENV =~ py3 ]] && brew upgrade python - [[ $TOXENV =~ py2 ]] && brew install python@2 - export PATH="/usr/local/opt/python/libexec/bin:${PATH}" - fi -install: - - python -mpip install --progress-bar=off tox -rci/requirements.txt - - virtualenv --version - - easy_install --version - - pip --version - - tox --version -script: - - tox -v -after_failure: - - more .tox/log/* | cat - - more .tox/*/log/* | cat -notifications: - email: - on_success: never - on_failure: always diff --git a/ci/bootstrap.py b/ci/bootstrap.py index d754e5262..e94ae1d80 100755 --- a/ci/bootstrap.py +++ b/ci/bootstrap.py @@ -53,7 +53,6 @@ def main(): import jinja2 import matrix - print("Project path: {0}".format(base_path)) jinja = jinja2.Environment( @@ -101,4 +100,3 @@ def main(): else: print("Unexpected arguments {0}".format(args), file=sys.stderr) sys.exit(1) - diff --git a/docs/conf.py b/docs/conf.py index 42dcb525d..a22f63fed 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,50 +5,50 @@ extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.coverage', - 'sphinx.ext.doctest', - 'sphinx.ext.extlinks', - 'sphinx.ext.ifconfig', - 'sphinx.ext.napoleon', - 'sphinx.ext.todo', - 'sphinx.ext.imgmath', - 'sphinx.ext.viewcode', + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.coverage", + "sphinx.ext.doctest", + "sphinx.ext.extlinks", + "sphinx.ext.ifconfig", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.imgmath", + "sphinx.ext.viewcode", ] -source_suffix = '.rst' -master_doc = 'index' -project = 'oemof.solph' -year = '2014-2020' -author = 'oemof-developer-group' -copyright = '{0}, {1}'.format(year, author) -version = release = '0.4.2.dev0' - -pygments_style = 'trac' -templates_path = ['.'] +source_suffix = ".rst" +master_doc = "index" +project = "oemof.solph" +year = "2014-2020" +author = "oemof-developer-group" +copyright = "{0}, {1}".format(year, author) +version = release = "0.4.2.dev0" + +pygments_style = "trac" +templates_path = ["."] extlinks = { - 'issue': ('https://github.com/oemof/oemof-solph/issues/%s', '#'), - 'pr': ('https://github.com/oemof/oemof-solph/pull/%s', 'PR #'), + "issue": ("https://github.com/oemof/oemof-solph/issues/%s", "#"), + "pr": ("https://github.com/oemof/oemof-solph/pull/%s", "PR #"), } # on_rtd is whether we are on readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +on_rtd = os.environ.get("READTHEDOCS", None) == "True" if not on_rtd: # only set the theme if we're building docs locally - html_theme = 'sphinx_rtd_theme' + html_theme = "sphinx_rtd_theme" html_use_smartypants = True -html_last_updated_fmt = '%b %d, %Y' +html_last_updated_fmt = "%b %d, %Y" html_split_index = False html_sidebars = { - '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'], + "**": ["searchbox.html", "globaltoc.html", "sourcelink.html"], } -html_short_title = '%s-%s' % (project, version) +html_short_title = "%s-%s" % (project, version) napoleon_use_ivar = True napoleon_use_rtype = False napoleon_use_param = False -exclude_patterns = ['_build', 'whatsnew/*'] +exclude_patterns = ["_build", "whatsnew/*"] linkcheck_ignore = [r"https://requires.io/.*"] + ( [ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..dc9c5ade2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.black] +line-length = 79 +target-version = ['py37', 'py38'] +include = '\.pyi?$' +exclude = ''' +/( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + | ci +)/ +''' diff --git a/setup.cfg b/setup.cfg index 44f97ebc3..88193bda7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,6 +4,7 @@ universal = 1 [flake8] max-line-length = 79 exclude = */migrations/*, docs/conf.py +ignore = E203, W503 [pydocstyle] ignore = D200, D203, D213, D406, D407 diff --git a/src/oemof/solph/blocks.py b/src/oemof/solph/blocks.py index 8de434cac..3064abaf0 100644 --- a/src/oemof/solph/blocks.py +++ b/src/oemof/solph/blocks.py @@ -94,11 +94,12 @@ class Flow(SimpleBlock): their value after optimization by :meth:`om.Flow.variable_costs()` . """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _create(self, group=None): - r""" Creates sets, variables and constraints for all standard flows. + r"""Creates sets, variables and constraints for all standard flows. Parameters ---------- @@ -114,116 +115,149 @@ def _create(self, group=None): # ########################## SETS ################################# # set for all flows with an global limit on the flow over time - self.SUMMED_MAX_FLOWS = Set(initialize=[ - (g[0], g[1]) for g in group if g[2].summed_max is not None and - g[2].nominal_value is not None]) - - self.SUMMED_MIN_FLOWS = Set(initialize=[ - (g[0], g[1]) for g in group if g[2].summed_min is not None and - g[2].nominal_value is not None]) + self.SUMMED_MAX_FLOWS = Set( + initialize=[ + (g[0], g[1]) + for g in group + if g[2].summed_max is not None + and g[2].nominal_value is not None + ] + ) + + self.SUMMED_MIN_FLOWS = Set( + initialize=[ + (g[0], g[1]) + for g in group + if g[2].summed_min is not None + and g[2].nominal_value is not None + ] + ) self.NEGATIVE_GRADIENT_FLOWS = Set( - initialize=[(g[0], g[1]) for g in group - if g[2].negative_gradient['ub'][0] is not None]) + initialize=[ + (g[0], g[1]) + for g in group + if g[2].negative_gradient["ub"][0] is not None + ] + ) self.POSITIVE_GRADIENT_FLOWS = Set( - initialize=[(g[0], g[1]) for g in group - if g[2].positive_gradient['ub'][0] is not None]) + initialize=[ + (g[0], g[1]) + for g in group + if g[2].positive_gradient["ub"][0] is not None + ] + ) self.INTEGER_FLOWS = Set( - initialize=[(g[0], g[1]) for g in group - if g[2].integer]) + initialize=[(g[0], g[1]) for g in group if g[2].integer] + ) # ######################### Variables ################################ - self.positive_gradient = Var(self.POSITIVE_GRADIENT_FLOWS, - m.TIMESTEPS) + self.positive_gradient = Var(self.POSITIVE_GRADIENT_FLOWS, m.TIMESTEPS) - self.negative_gradient = Var(self.NEGATIVE_GRADIENT_FLOWS, - m.TIMESTEPS) + self.negative_gradient = Var(self.NEGATIVE_GRADIENT_FLOWS, m.TIMESTEPS) - self.integer_flow = Var(self.INTEGER_FLOWS, - m.TIMESTEPS, within=NonNegativeIntegers) + self.integer_flow = Var( + self.INTEGER_FLOWS, m.TIMESTEPS, within=NonNegativeIntegers + ) # set upper bound of gradient variable for i, o, f in group: - if m.flows[i, o].positive_gradient['ub'][0] is not None: + if m.flows[i, o].positive_gradient["ub"][0] is not None: for t in m.TIMESTEPS: self.positive_gradient[i, o, t].setub( - f.positive_gradient['ub'][t] * f.nominal_value) - if m.flows[i, o].negative_gradient['ub'][0] is not None: + f.positive_gradient["ub"][t] * f.nominal_value + ) + if m.flows[i, o].negative_gradient["ub"][0] is not None: for t in m.TIMESTEPS: self.negative_gradient[i, o, t].setub( - f.negative_gradient['ub'][t] * f.nominal_value) + f.negative_gradient["ub"][t] * f.nominal_value + ) # ######################### CONSTRAINTS ############################### def _flow_summed_max_rule(model): - """Rule definition for build action of max. sum flow constraint. - """ + """Rule definition for build action of max. sum flow constraint.""" for inp, out in self.SUMMED_MAX_FLOWS: - lhs = sum(m.flow[inp, out, ts] * m.timeincrement[ts] - for ts in m.TIMESTEPS) - rhs = (m.flows[inp, out].summed_max * - m.flows[inp, out].nominal_value) + lhs = sum( + m.flow[inp, out, ts] * m.timeincrement[ts] + for ts in m.TIMESTEPS + ) + rhs = ( + m.flows[inp, out].summed_max + * m.flows[inp, out].nominal_value + ) self.summed_max.add((inp, out), lhs <= rhs) + self.summed_max = Constraint(self.SUMMED_MAX_FLOWS, noruleinit=True) self.summed_max_build = BuildAction(rule=_flow_summed_max_rule) def _flow_summed_min_rule(model): - """Rule definition for build action of min. sum flow constraint. - """ + """Rule definition for build action of min. sum flow constraint.""" for inp, out in self.SUMMED_MIN_FLOWS: - lhs = sum(m.flow[inp, out, ts] * m.timeincrement[ts] - for ts in m.TIMESTEPS) - rhs = (m.flows[inp, out].summed_min * - m.flows[inp, out].nominal_value) + lhs = sum( + m.flow[inp, out, ts] * m.timeincrement[ts] + for ts in m.TIMESTEPS + ) + rhs = ( + m.flows[inp, out].summed_min + * m.flows[inp, out].nominal_value + ) self.summed_min.add((inp, out), lhs >= rhs) + self.summed_min = Constraint(self.SUMMED_MIN_FLOWS, noruleinit=True) self.summed_min_build = BuildAction(rule=_flow_summed_min_rule) def _positive_gradient_flow_rule(model): - """Rule definition for positive gradient constraint. - """ + """Rule definition for positive gradient constraint.""" for inp, out in self.POSITIVE_GRADIENT_FLOWS: for ts in m.TIMESTEPS: if ts > 0: - lhs = m.flow[inp, out, ts] - m.flow[inp, out, ts-1] + lhs = m.flow[inp, out, ts] - m.flow[inp, out, ts - 1] rhs = self.positive_gradient[inp, out, ts] - self.positive_gradient_constr.add((inp, out, ts), - lhs <= rhs) + self.positive_gradient_constr.add( + (inp, out, ts), lhs <= rhs + ) else: pass # return(Constraint.Skip) + self.positive_gradient_constr = Constraint( - self.POSITIVE_GRADIENT_FLOWS, m.TIMESTEPS, noruleinit=True) + self.POSITIVE_GRADIENT_FLOWS, m.TIMESTEPS, noruleinit=True + ) self.positive_gradient_build = BuildAction( - rule=_positive_gradient_flow_rule) + rule=_positive_gradient_flow_rule + ) def _negative_gradient_flow_rule(model): - """Rule definition for negative gradient constraint. - """ + """Rule definition for negative gradient constraint.""" for inp, out in self.NEGATIVE_GRADIENT_FLOWS: for ts in m.TIMESTEPS: if ts > 0: - lhs = m.flow[inp, out, ts-1] - m.flow[inp, out, ts] + lhs = m.flow[inp, out, ts - 1] - m.flow[inp, out, ts] rhs = self.negative_gradient[inp, out, ts] - self.negative_gradient_constr.add((inp, out, ts), - lhs <= rhs) + self.negative_gradient_constr.add( + (inp, out, ts), lhs <= rhs + ) else: pass # return(Constraint.Skip) + self.negative_gradient_constr = Constraint( - self.NEGATIVE_GRADIENT_FLOWS, m.TIMESTEPS, noruleinit=True) + self.NEGATIVE_GRADIENT_FLOWS, m.TIMESTEPS, noruleinit=True + ) self.negative_gradient_build = BuildAction( - rule=_negative_gradient_flow_rule) + rule=_negative_gradient_flow_rule + ) def _integer_flow_rule(block, ii, oi, ti): - """Force flow variable to NonNegativeInteger values. - """ + """Force flow variable to NonNegativeInteger values.""" return self.integer_flow[ii, oi, ti] == m.flow[ii, oi, ti] - self.integer_flow_constr = Constraint(self.INTEGER_FLOWS, m.TIMESTEPS, - rule=_integer_flow_rule) + self.integer_flow_constr = Constraint( + self.INTEGER_FLOWS, m.TIMESTEPS, rule=_integer_flow_rule + ) def _objective_expression(self): - r""" Objective expression for all standard flows with fixed costs + r"""Objective expression for all standard flows with fixed costs and variable costs. """ m = self.parent_block() @@ -234,21 +268,25 @@ def _objective_expression(self): for i, o in m.FLOWS: if m.flows[i, o].variable_costs[0] is not None: for t in m.TIMESTEPS: - variable_costs += (m.flow[i, o, t] * - m.objective_weighting[t] * - m.flows[i, o].variable_costs[t]) + variable_costs += ( + m.flow[i, o, t] + * m.objective_weighting[t] + * m.flows[i, o].variable_costs[t] + ) - if m.flows[i, o].positive_gradient['ub'][0] is not None: + if m.flows[i, o].positive_gradient["ub"][0] is not None: for t in m.TIMESTEPS: - gradient_costs += (self.positive_gradient[i, o, t] * - m.flows[i, o].positive_gradient[ - 'costs']) + gradient_costs += ( + self.positive_gradient[i, o, t] + * m.flows[i, o].positive_gradient["costs"] + ) - if m.flows[i, o].negative_gradient['ub'][0] is not None: + if m.flows[i, o].negative_gradient["ub"][0] is not None: for t in m.TIMESTEPS: - gradient_costs += (self.negative_gradient[i, o, t] * - m.flows[i, o].negative_gradient[ - 'costs']) + gradient_costs += ( + self.negative_gradient[i, o, t] + * m.flows[i, o].negative_gradient["costs"] + ) return variable_costs + gradient_costs @@ -443,6 +481,7 @@ class InvestmentFlow(SimpleBlock): :class:`oemof.solph.options.Investment` """ # noqa: E501 + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -465,131 +504,175 @@ def _create(self, group=None): # ######################### SETS ##################################### self.INVESTFLOWS = Set(initialize=[(g[0], g[1]) for g in group]) - self.CONVEX_INVESTFLOWS = Set(initialize=[ - (g[0], g[1]) for g in group if g[2].investment.nonconvex is False]) - - self.NON_CONVEX_INVESTFLOWS = Set(initialize=[ - (g[0], g[1]) for g in group if g[2].investment.nonconvex is True]) + self.CONVEX_INVESTFLOWS = Set( + initialize=[ + (g[0], g[1]) + for g in group + if g[2].investment.nonconvex is False + ] + ) + + self.NON_CONVEX_INVESTFLOWS = Set( + initialize=[ + (g[0], g[1]) + for g in group + if g[2].investment.nonconvex is True + ] + ) self.FIXED_INVESTFLOWS = Set( - initialize=[(g[0], g[1]) for g in group if g[2].fix[0] is not - None]) + initialize=[(g[0], g[1]) for g in group if g[2].fix[0] is not None] + ) self.NON_FIXED_INVESTFLOWS = Set( - initialize=[(g[0], g[1]) for g in group if g[2].fix[0] is None]) - - self.SUMMED_MAX_INVESTFLOWS = Set(initialize=[ - (g[0], g[1]) for g in group if g[2].summed_max is not None]) - - self.SUMMED_MIN_INVESTFLOWS = Set(initialize=[ - (g[0], g[1]) for g in group if g[2].summed_min is not None]) - - self.MIN_INVESTFLOWS = Set(initialize=[ - (g[0], g[1]) for g in group if ( - g[2].min[0] != 0 or len(g[2].min) > 1)]) + initialize=[(g[0], g[1]) for g in group if g[2].fix[0] is None] + ) + + self.SUMMED_MAX_INVESTFLOWS = Set( + initialize=[ + (g[0], g[1]) for g in group if g[2].summed_max is not None + ] + ) + + self.SUMMED_MIN_INVESTFLOWS = Set( + initialize=[ + (g[0], g[1]) for g in group if g[2].summed_min is not None + ] + ) + + self.MIN_INVESTFLOWS = Set( + initialize=[ + (g[0], g[1]) + for g in group + if (g[2].min[0] != 0 or len(g[2].min) > 1) + ] + ) # ######################### VARIABLES ################################# def _investvar_bound_rule(block, i, o): - """Rule definition for bounds of invest variable. - """ + """Rule definition for bounds of invest variable.""" if (i, o) in self.CONVEX_INVESTFLOWS: - return (m.flows[i, o].investment.minimum, - m.flows[i, o].investment.maximum) + return ( + m.flows[i, o].investment.minimum, + m.flows[i, o].investment.maximum, + ) elif (i, o) in self.NON_CONVEX_INVESTFLOWS: return 0, m.flows[i, o].investment.maximum # create invest variable for a investment flow - self.invest = Var(self.INVESTFLOWS, within=NonNegativeReals, - bounds=_investvar_bound_rule) + self.invest = Var( + self.INVESTFLOWS, + within=NonNegativeReals, + bounds=_investvar_bound_rule, + ) # create status variable for a non-convex investment flow self.invest_status = Var(self.NON_CONVEX_INVESTFLOWS, within=Binary) # ######################### CONSTRAINTS ############################### def _min_invest_rule(block, i, o): - """Rule definition for applying a minimum investment - """ - expr = (m.flows[i, o].investment.minimum * - self.invest_status[i, o] <= self.invest[i, o]) + """Rule definition for applying a minimum investment""" + expr = ( + m.flows[i, o].investment.minimum * self.invest_status[i, o] + <= self.invest[i, o] + ) return expr + self.minimum_rule = Constraint( - self.NON_CONVEX_INVESTFLOWS, rule=_min_invest_rule) + self.NON_CONVEX_INVESTFLOWS, rule=_min_invest_rule + ) def _max_invest_rule(block, i, o): - """Rule definition for applying a minimum investment - """ + """Rule definition for applying a minimum investment""" expr = self.invest[i, o] <= ( - m.flows[i, o].investment.maximum * self.invest_status[i, o]) + m.flows[i, o].investment.maximum * self.invest_status[i, o] + ) return expr + self.maximum_rule = Constraint( - self.NON_CONVEX_INVESTFLOWS, rule=_max_invest_rule) + self.NON_CONVEX_INVESTFLOWS, rule=_max_invest_rule + ) def _investflow_fixed_rule(block, i, o, t): """Rule definition of constraint to fix flow variable of investment flow to (normed) actual value """ - expr = (m.flow[i, o, t] == ( - (m.flows[i, o].investment.existing + self.invest[i, o]) * - m.flows[i, o].fix[t])) + expr = m.flow[i, o, t] == ( + (m.flows[i, o].investment.existing + self.invest[i, o]) + * m.flows[i, o].fix[t] + ) return expr - self.fixed = Constraint(self.FIXED_INVESTFLOWS, m.TIMESTEPS, - rule=_investflow_fixed_rule) + + self.fixed = Constraint( + self.FIXED_INVESTFLOWS, m.TIMESTEPS, rule=_investflow_fixed_rule + ) def _max_investflow_rule(block, i, o, t): """Rule definition of constraint setting an upper bound of flow variable in investment case. """ - expr = (m.flow[i, o, t] <= ( - (m.flows[i, o].investment.existing + self.invest[i, o]) * - m.flows[i, o].max[t])) + expr = m.flow[i, o, t] <= ( + (m.flows[i, o].investment.existing + self.invest[i, o]) + * m.flows[i, o].max[t] + ) return expr - self.max = Constraint(self.NON_FIXED_INVESTFLOWS, m.TIMESTEPS, - rule=_max_investflow_rule) + + self.max = Constraint( + self.NON_FIXED_INVESTFLOWS, m.TIMESTEPS, rule=_max_investflow_rule + ) def _min_investflow_rule(block, i, o, t): """Rule definition of constraint setting a lower bound on flow variable in investment case. """ - expr = (m.flow[i, o, t] >= ( - (m.flows[i, o].investment.existing + self.invest[i, o]) * - m.flows[i, o].min[t])) + expr = m.flow[i, o, t] >= ( + (m.flows[i, o].investment.existing + self.invest[i, o]) + * m.flows[i, o].min[t] + ) return expr - self.min = Constraint(self.MIN_INVESTFLOWS, m.TIMESTEPS, - rule=_min_investflow_rule) + + self.min = Constraint( + self.MIN_INVESTFLOWS, m.TIMESTEPS, rule=_min_investflow_rule + ) def _summed_max_investflow_rule(block, i, o): """Rule definition for build action of max. sum flow constraint in investment case. """ - expr = (sum(m.flow[i, o, t] * m.timeincrement[t] - for t in m.TIMESTEPS) <= - m.flows[i, o].summed_max * ( - self.invest[i, o] + - m.flows[i, o].investment.existing)) + expr = sum( + m.flow[i, o, t] * m.timeincrement[t] for t in m.TIMESTEPS + ) <= m.flows[i, o].summed_max * ( + self.invest[i, o] + m.flows[i, o].investment.existing + ) return expr - self.summed_max = Constraint(self.SUMMED_MAX_INVESTFLOWS, - rule=_summed_max_investflow_rule) + + self.summed_max = Constraint( + self.SUMMED_MAX_INVESTFLOWS, rule=_summed_max_investflow_rule + ) def _summed_min_investflow_rule(block, i, o): """Rule definition for build action of min. sum flow constraint in investment case. """ - expr = (sum(m.flow[i, o, t] * m.timeincrement[t] - for t in m.TIMESTEPS) >= - ((m.flows[i, o].investment.existing + - self.invest[i, o]) * - m.flows[i, o].summed_min)) + expr = sum( + m.flow[i, o, t] * m.timeincrement[t] for t in m.TIMESTEPS + ) >= ( + (m.flows[i, o].investment.existing + self.invest[i, o]) + * m.flows[i, o].summed_min + ) return expr - self.summed_min = Constraint(self.SUMMED_MIN_INVESTFLOWS, - rule=_summed_min_investflow_rule) + + self.summed_min = Constraint( + self.SUMMED_MIN_INVESTFLOWS, rule=_summed_min_investflow_rule + ) def _objective_expression(self): - r""" Objective expression for flows with investment attribute of type + r"""Objective expression for flows with investment attribute of type class:`.Investment`. The returned costs are fixed, variable and investment costs. """ - if not hasattr(self, 'INVESTFLOWS'): + if not hasattr(self, "INVESTFLOWS"): return 0 m = self.parent_block() @@ -597,13 +680,13 @@ def _objective_expression(self): for i, o in self.CONVEX_INVESTFLOWS: investment_costs += ( - self.invest[i, o] * m.flows[i, o].investment.ep_costs) + self.invest[i, o] * m.flows[i, o].investment.ep_costs + ) for i, o in self.NON_CONVEX_INVESTFLOWS: investment_costs += ( - self.invest[i, o] * - m.flows[i, o].investment.ep_costs + - self.invest_status[i, o] * - m.flows[i, o].investment.offset) + self.invest[i, o] * m.flows[i, o].investment.ep_costs + + self.invest_status[i, o] * m.flows[i, o].investment.offset + ) self.investment_costs = Expression(expr=investment_costs) return investment_costs @@ -621,6 +704,7 @@ class Bus(SimpleBlock): \forall n \in \textrm{BUSES}, \forall t \in \textrm{TIMESTEPS}. """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -649,10 +733,11 @@ def _busbalance_rule(block): for g in group: lhs = sum(m.flow[i, g, t] for i in ins[g]) rhs = sum(m.flow[g, o, t] for o in outs[g]) - expr = (lhs == rhs) + expr = lhs == rhs # no inflows no outflows yield: 0 == 0 which is True if expr is not True: block.balance.add((g, t), expr) + self.balance = Constraint(group, m.TIMESTEPS, noruleinit=True) self.balance_build = BuildAction(rule=_busbalance_rule) @@ -692,11 +777,12 @@ class Transformer(SimpleBlock): ====================== ==================================== ============= """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _create(self, group=None): - """ Creates the linear constraint for the class:`Transformer` + """Creates the linear constraint for the class:`Transformer` block. Parameters ---------- @@ -718,11 +804,15 @@ def _create(self, group=None): out_flows = {n: [o for o in n.outputs.keys()] for n in group} self.relation = Constraint( - [(n, i, o, t) - for t in m.TIMESTEPS - for n in group - for o in out_flows[n] - for i in in_flows[n]], noruleinit=True) + [ + (n, i, o, t) + for t in m.TIMESTEPS + for n in group + for o in out_flows[n] + for i in in_flows[n] + ], + noruleinit=True, + ) def _input_output_relation(block): for t in m.TIMESTEPS: @@ -730,16 +820,23 @@ def _input_output_relation(block): for o in out_flows[n]: for i in in_flows[n]: try: - lhs = (m.flow[i, n, t] * - n.conversion_factors[o][t]) - rhs = (m.flow[n, o, t] * - n.conversion_factors[i][t]) + lhs = ( + m.flow[i, n, t] + * n.conversion_factors[o][t] + ) + rhs = ( + m.flow[n, o, t] + * n.conversion_factors[i][t] + ) except ValueError: raise ValueError( "Error in constraint creation", "source: {0}, target: {1}".format( - n.label, o.label)) + n.label, o.label + ), + ) block.relation.add((n, i, o, t), (lhs == rhs)) + self.relation_build = BuildAction(rule=_input_output_relation) @@ -884,11 +981,12 @@ class NonConvexFlow(SimpleBlock): \cdot activity\_costs(i, o) """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _create(self, group=None): - """ Creates set, variables, constraints for all flow object with + """Creates set, variables, constraints for all flow object with an attribute flow of type class:`.NonConvexFlow`. Parameters @@ -904,35 +1002,62 @@ def _create(self, group=None): # ########################## SETS ##################################### self.NONCONVEX_FLOWS = Set(initialize=[(g[0], g[1]) for g in group]) - self.MIN_FLOWS = Set(initialize=[(g[0], g[1]) for g in group - if g[2].min[0] is not None]) - self.STARTUPFLOWS = Set(initialize=[(g[0], g[1]) for g in group - if g[2].nonconvex.startup_costs[0] - is not None - or g[2].nonconvex.maximum_startups - is not None]) - self.MAXSTARTUPFLOWS = Set(initialize=[(g[0], g[1]) for g in group - if g[2].nonconvex.maximum_startups - is not None]) - self.SHUTDOWNFLOWS = Set(initialize=[(g[0], g[1]) for g in group - if g[2].nonconvex.shutdown_costs[0] - is not None - or g[2].nonconvex.maximum_shutdowns - is not None]) - self.MAXSHUTDOWNFLOWS = Set(initialize=[(g[0], g[1]) for g in group - if g[2].nonconvex.maximum_shutdowns - is not None]) - self.MINUPTIMEFLOWS = Set(initialize=[(g[0], g[1]) for g in group - if g[2].nonconvex.minimum_uptime - is not None]) - - self.MINDOWNTIMEFLOWS = Set(initialize=[(g[0], g[1]) for g in group - if g[2].nonconvex.minimum_downtime - is not None]) + self.MIN_FLOWS = Set( + initialize=[(g[0], g[1]) for g in group if g[2].min[0] is not None] + ) + self.STARTUPFLOWS = Set( + initialize=[ + (g[0], g[1]) + for g in group + if g[2].nonconvex.startup_costs[0] is not None + or g[2].nonconvex.maximum_startups is not None + ] + ) + self.MAXSTARTUPFLOWS = Set( + initialize=[ + (g[0], g[1]) + for g in group + if g[2].nonconvex.maximum_startups is not None + ] + ) + self.SHUTDOWNFLOWS = Set( + initialize=[ + (g[0], g[1]) + for g in group + if g[2].nonconvex.shutdown_costs[0] is not None + or g[2].nonconvex.maximum_shutdowns is not None + ] + ) + self.MAXSHUTDOWNFLOWS = Set( + initialize=[ + (g[0], g[1]) + for g in group + if g[2].nonconvex.maximum_shutdowns is not None + ] + ) + self.MINUPTIMEFLOWS = Set( + initialize=[ + (g[0], g[1]) + for g in group + if g[2].nonconvex.minimum_uptime is not None + ] + ) + + self.MINDOWNTIMEFLOWS = Set( + initialize=[ + (g[0], g[1]) + for g in group + if g[2].nonconvex.minimum_downtime is not None + ] + ) self.ACTIVITYCOSTFLOWS = Set( - initialize=[(g[0], g[1]) for g in group - if g[2].nonconvex.activity_costs[0] is not None]) + initialize=[ + (g[0], g[1]) + for g in group + if g[2].nonconvex.activity_costs[0] is not None + ] + ) # ################### VARIABLES AND CONSTRAINTS ####################### self.status = Var(self.NONCONVEX_FLOWS, m.TIMESTEPS, within=Binary) @@ -944,113 +1069,151 @@ def _create(self, group=None): self.shutdown = Var(self.SHUTDOWNFLOWS, m.TIMESTEPS, within=Binary) def _minimum_flow_rule(block, i, o, t): - """Rule definition for MILP minimum flow constraints. - """ - expr = (self.status[i, o, t] * - m.flows[i, o].min[t] * m.flows[i, o].nominal_value <= - m.flow[i, o, t]) + """Rule definition for MILP minimum flow constraints.""" + expr = ( + self.status[i, o, t] + * m.flows[i, o].min[t] + * m.flows[i, o].nominal_value + <= m.flow[i, o, t] + ) return expr - self.min = Constraint(self.MIN_FLOWS, m.TIMESTEPS, - rule=_minimum_flow_rule) + + self.min = Constraint( + self.MIN_FLOWS, m.TIMESTEPS, rule=_minimum_flow_rule + ) def _maximum_flow_rule(block, i, o, t): - """Rule definition for MILP maximum flow constraints. - """ - expr = (self.status[i, o, t] * - m.flows[i, o].max[t] * m.flows[i, o].nominal_value >= - m.flow[i, o, t]) + """Rule definition for MILP maximum flow constraints.""" + expr = ( + self.status[i, o, t] + * m.flows[i, o].max[t] + * m.flows[i, o].nominal_value + >= m.flow[i, o, t] + ) return expr - self.max = Constraint(self.MIN_FLOWS, m.TIMESTEPS, - rule=_maximum_flow_rule) + + self.max = Constraint( + self.MIN_FLOWS, m.TIMESTEPS, rule=_maximum_flow_rule + ) def _startup_rule(block, i, o, t): - """Rule definition for startup constraint of nonconvex flows. - """ + """Rule definition for startup constraint of nonconvex flows.""" if t > m.TIMESTEPS[1]: - expr = (self.startup[i, o, t] >= self.status[i, o, t] - - self.status[i, o, t-1]) + expr = ( + self.startup[i, o, t] + >= self.status[i, o, t] - self.status[i, o, t - 1] + ) else: - expr = (self.startup[i, o, t] >= self.status[i, o, t] - - m.flows[i, o].nonconvex.initial_status) + expr = ( + self.startup[i, o, t] + >= self.status[i, o, t] + - m.flows[i, o].nonconvex.initial_status + ) return expr - self.startup_constr = Constraint(self.STARTUPFLOWS, m.TIMESTEPS, - rule=_startup_rule) + + self.startup_constr = Constraint( + self.STARTUPFLOWS, m.TIMESTEPS, rule=_startup_rule + ) def _max_startup_rule(block, i, o): - """Rule definition for maximum number of start-ups. - """ + """Rule definition for maximum number of start-ups.""" lhs = sum(self.startup[i, o, t] for t in m.TIMESTEPS) return lhs <= m.flows[i, o].nonconvex.maximum_startups - self.max_startup_constr = Constraint(self.MAXSTARTUPFLOWS, - rule=_max_startup_rule) + + self.max_startup_constr = Constraint( + self.MAXSTARTUPFLOWS, rule=_max_startup_rule + ) def _shutdown_rule(block, i, o, t): - """Rule definition for shutdown constraints of nonconvex flows. - """ + """Rule definition for shutdown constraints of nonconvex flows.""" if t > m.TIMESTEPS[1]: - expr = (self.shutdown[i, o, t] >= self.status[i, o, t-1] - - self.status[i, o, t]) + expr = ( + self.shutdown[i, o, t] + >= self.status[i, o, t - 1] - self.status[i, o, t] + ) else: - expr = (self.shutdown[i, o, t] >= - m.flows[i, o].nonconvex.initial_status - - self.status[i, o, t]) + expr = ( + self.shutdown[i, o, t] + >= m.flows[i, o].nonconvex.initial_status + - self.status[i, o, t] + ) return expr - self.shutdown_constr = Constraint(self.SHUTDOWNFLOWS, m.TIMESTEPS, - rule=_shutdown_rule) + + self.shutdown_constr = Constraint( + self.SHUTDOWNFLOWS, m.TIMESTEPS, rule=_shutdown_rule + ) def _max_shutdown_rule(block, i, o): - """Rule definition for maximum number of start-ups. - """ + """Rule definition for maximum number of start-ups.""" lhs = sum(self.shutdown[i, o, t] for t in m.TIMESTEPS) return lhs <= m.flows[i, o].nonconvex.maximum_shutdowns - self.max_shutdown_constr = Constraint(self.MAXSHUTDOWNFLOWS, - rule=_max_shutdown_rule) + + self.max_shutdown_constr = Constraint( + self.MAXSHUTDOWNFLOWS, rule=_max_shutdown_rule + ) def _min_uptime_rule(block, i, o, t): - """Rule definition for min-uptime constraints of nonconvex flows. """ - if m.flows[i, o].nonconvex.max_up_down <= t\ - <= m.TIMESTEPS[-1]-m.flows[i, o].nonconvex.max_up_down: + Rule definition for min-uptime constraints of nonconvex flows. + """ + if ( + m.flows[i, o].nonconvex.max_up_down + <= t + <= m.TIMESTEPS[-1] - m.flows[i, o].nonconvex.max_up_down + ): expr = 0 - expr += ((self.status[i, o, t]-self.status[i, o, t-1]) * - m.flows[i, o].nonconvex.minimum_uptime) - expr += -sum(self.status[i, o, t+u] for u in range(0, - m.flows[i, o].nonconvex.minimum_uptime)) + expr += ( + self.status[i, o, t] - self.status[i, o, t - 1] + ) * m.flows[i, o].nonconvex.minimum_uptime + expr += -sum( + self.status[i, o, t + u] + for u in range(0, m.flows[i, o].nonconvex.minimum_uptime) + ) return expr <= 0 else: expr = 0 expr += self.status[i, o, t] expr += -m.flows[i, o].nonconvex.initial_status return expr == 0 + self.min_uptime_constr = Constraint( - self.MINUPTIMEFLOWS, m.TIMESTEPS, rule=_min_uptime_rule) + self.MINUPTIMEFLOWS, m.TIMESTEPS, rule=_min_uptime_rule + ) def _min_downtime_rule(block, i, o, t): - """Rule definition for min-downtime constraints of nonconvex flows. """ - if m.flows[i, o].nonconvex.max_up_down <= t\ - <= m.TIMESTEPS[-1]-m.flows[i, o].nonconvex.max_up_down: + Rule definition for min-downtime constraints of nonconvex flows. + """ + if ( + m.flows[i, o].nonconvex.max_up_down + <= t + <= m.TIMESTEPS[-1] - m.flows[i, o].nonconvex.max_up_down + ): expr = 0 - expr += ((self.status[i, o, t-1]-self.status[i, o, t]) * - m.flows[i, o].nonconvex.minimum_downtime) - expr += - m.flows[i, o].nonconvex.minimum_downtime - expr += sum(self.status[i, o, t+d] for d in range(0, - m.flows[i, o].nonconvex.minimum_downtime)) + expr += ( + self.status[i, o, t - 1] - self.status[i, o, t] + ) * m.flows[i, o].nonconvex.minimum_downtime + expr += -m.flows[i, o].nonconvex.minimum_downtime + expr += sum( + self.status[i, o, t + d] + for d in range(0, m.flows[i, o].nonconvex.minimum_downtime) + ) return expr <= 0 else: expr = 0 expr += self.status[i, o, t] expr += -m.flows[i, o].nonconvex.initial_status return expr == 0 + self.min_downtime_constr = Constraint( - self.MINDOWNTIMEFLOWS, m.TIMESTEPS, rule=_min_downtime_rule) + self.MINDOWNTIMEFLOWS, m.TIMESTEPS, rule=_min_downtime_rule + ) # TODO: Add gradient constraints for nonconvex block / flows def _objective_expression(self): - r"""Objective expression for nonconvex flows. - """ - if not hasattr(self, 'NONCONVEX_FLOWS'): + r"""Objective expression for nonconvex flows.""" + if not hasattr(self, "NONCONVEX_FLOWS"): return 0 m = self.parent_block() @@ -1063,27 +1226,30 @@ def _objective_expression(self): for i, o in self.STARTUPFLOWS: if m.flows[i, o].nonconvex.startup_costs[0] is not None: startup_costs += sum( - self.startup[i, o, t] * - m.flows[i, o].nonconvex.startup_costs[t] - for t in m.TIMESTEPS) + self.startup[i, o, t] + * m.flows[i, o].nonconvex.startup_costs[t] + for t in m.TIMESTEPS + ) self.startup_costs = Expression(expr=startup_costs) if self.SHUTDOWNFLOWS: for i, o in self.SHUTDOWNFLOWS: if m.flows[i, o].nonconvex.shutdown_costs[0] is not None: shutdown_costs += sum( - self.shutdown[i, o, t] * - m.flows[i, o].nonconvex.shutdown_costs[t] - for t in m.TIMESTEPS) + self.shutdown[i, o, t] + * m.flows[i, o].nonconvex.shutdown_costs[t] + for t in m.TIMESTEPS + ) self.shutdown_costs = Expression(expr=shutdown_costs) if self.ACTIVITYCOSTFLOWS: for i, o in self.ACTIVITYCOSTFLOWS: if m.flows[i, o].nonconvex.activity_costs[0] is not None: activity_costs += sum( - self.status[i, o, t] * - m.flows[i, o].nonconvex.activity_costs[t] - for t in m.TIMESTEPS) + self.status[i, o, t] + * m.flows[i, o].nonconvex.activity_costs[t] + for t in m.TIMESTEPS + ) self.activity_costs = Expression(expr=activity_costs) diff --git a/src/oemof/solph/components.py b/src/oemof/solph/components.py index 2f5ea4b1b..6358a2f2f 100644 --- a/src/oemof/solph/components.py +++ b/src/oemof/solph/components.py @@ -768,8 +768,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _create(self, group=None): - """ - """ + """""" m = self.parent_block() if group is None: return None @@ -980,13 +979,11 @@ def _storage_capacity_inflow_invest_rule(block, n): by nominal_storage_capacity__inflow_ratio """ expr = ( - ( - m.InvestmentFlow.invest[i[n], n] - + m.flows[i[n], n].investment.existing - ) - == (n.investment.existing + self.invest[n]) - * n.invest_relation_input_capacity - ) + m.InvestmentFlow.invest[i[n], n] + + m.flows[i[n], n].investment.existing + ) == ( + n.investment.existing + self.invest[n] + ) * n.invest_relation_input_capacity return expr self.storage_capacity_inflow = Constraint( @@ -1000,13 +997,11 @@ def _storage_capacity_outflow_invest_rule(block, n): by nominal_storage_capacity__outflow_ratio """ expr = ( - ( - m.InvestmentFlow.invest[n, o[n]] - + m.flows[n, o[n]].investment.existing - ) - == (n.investment.existing + self.invest[n]) - * n.invest_relation_output_capacity - ) + m.InvestmentFlow.invest[n, o[n]] + + m.flows[n, o[n]].investment.existing + ) == ( + n.investment.existing + self.invest[n] + ) * n.invest_relation_output_capacity return expr self.storage_capacity_outflow = Constraint( @@ -1683,7 +1678,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _create(self, group=None): - """ Creates the linear constraint for the + """Creates the linear constraint for the :class:`oemof.solph.Transformer` block. Parameters @@ -1726,8 +1721,7 @@ def _create(self, group=None): ] def _input_output_relation_rule(block): - """Connection between input, main output and tapped output. - """ + """Connection between input, main output and tapped output.""" for t in m.TIMESTEPS: for g in group: lhs = m.flow[g.inflow, g, t] @@ -1746,8 +1740,7 @@ def _input_output_relation_rule(block): ) def _out_flow_relation_rule(block): - """Relation between main and tapped output in full chp mode. - """ + """Relation between main and tapped output in full chp mode.""" for t in m.TIMESTEPS: for g in group: lhs = m.flow[g, g.main_output, t] @@ -1866,7 +1859,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _create(self, group=None): - """ Creates the relation for the class:`OffsetTransformer`. + """Creates the relation for the class:`OffsetTransformer`. Parameters ---------- diff --git a/src/oemof/solph/console_scripts.py b/src/oemof/solph/console_scripts.py index 8a6347a6b..a874463f1 100644 --- a/src/oemof/solph/console_scripts.py +++ b/src/oemof/solph/console_scripts.py @@ -21,25 +21,28 @@ def check_oemof_installation(silent=False): logging.disable(logging.CRITICAL) - date_time_index = pd.date_range('1/1/2012', periods=5, freq='H') + date_time_index = pd.date_range("1/1/2012", periods=5, freq="H") energysystem = solph.EnergySystem(timeindex=date_time_index) bgas = solph.Bus(label="natural_gas") bel = solph.Bus(label="electricity") - solph.Sink(label='excess_bel', inputs={bel: solph.Flow()}) - solph.Source(label='rgas', outputs={bgas: solph.Flow()}) - solph.Sink(label='demand', inputs={bel: solph.Flow( - fix=[10, 20, 30, 40, 50], nominal_value=1)}) + solph.Sink(label="excess_bel", inputs={bel: solph.Flow()}) + solph.Source(label="rgas", outputs={bgas: solph.Flow()}) + solph.Sink( + label="demand", + inputs={bel: solph.Flow(fix=[10, 20, 30, 40, 50], nominal_value=1)}, + ) solph.Transformer( label="pp_gas", inputs={bgas: solph.Flow()}, outputs={bel: solph.Flow(nominal_value=10e10, variable_costs=50)}, - conversion_factors={bel: 0.58}) + conversion_factors={bel: 0.58}, + ) om = solph.Model(energysystem) # check solvers solver = dict() - for s in ['cbc', 'glpk', 'gurobi', 'cplex']: + for s in ["cbc", "glpk", "gurobi", "cplex"]: try: om.solve(solver=s) solver[s] = "working" @@ -49,7 +52,7 @@ def check_oemof_installation(silent=False): if not silent: print() print("*****************************") - print('Solver installed with oemof:') + print("Solver installed with oemof:") print() for s, t in solver.items(): print("{0}: {1}".format(s, t)) diff --git a/src/oemof/solph/constraints.py b/src/oemof/solph/constraints.py index b4d0bb2a7..5b40e8cc4 100644 --- a/src/oemof/solph/constraints.py +++ b/src/oemof/solph/constraints.py @@ -17,7 +17,7 @@ def investment_limit(model, limit=None): - r""" Set an absolute limit for the total investment costs of an investment + r"""Set an absolute limit for the total investment costs of an investment optimization problem: .. math:: \sum_{investment\_costs} \leq limit @@ -122,14 +122,23 @@ def additional_investment_flow_limit(model, keyword, limit=None): limit_name = "invest_limit_" + keyword - setattr(model, limit_name, po.Expression( - expr=sum(model.InvestmentFlow.invest[inflow, outflow] * - getattr(invest_flows[inflow, outflow], keyword) - for (inflow, outflow) in invest_flows - ))) - - setattr(model, limit_name + "_constraint", po.Constraint( - expr=(getattr(model, limit_name) <= limit))) + setattr( + model, + limit_name, + po.Expression( + expr=sum( + model.InvestmentFlow.invest[inflow, outflow] + * getattr(invest_flows[inflow, outflow], keyword) + for (inflow, outflow) in invest_flows + ) + ), + ) + + setattr( + model, + limit_name + "_constraint", + po.Constraint(expr=(getattr(model, limit_name) <= limit)), + ) return model @@ -143,10 +152,9 @@ def emission_limit(om, flows=None, limit=None): Flow objects required an attribute "emission_factor"! """ - generic_integral_limit(om, - keyword='emission_factor', - flows=flows, - limit=limit) + generic_integral_limit( + om, keyword="emission_factor", flows=flows, limit=limit + ) def generic_integral_limit(om, keyword, flows=None, limit=None): @@ -223,27 +231,40 @@ def generic_integral_limit(om, keyword, flows=None, limit=None): for (i, o) in flows: if not hasattr(flows[i, o], keyword): raise AttributeError( - ('Flow with source: {0} and target: {1} ' - 'has no attribute {2}.').format( - i.label, o.label, keyword)) - - limit_name = "integral_limit_"+keyword - - setattr(om, limit_name, po.Expression( - expr=sum(om.flow[inflow, outflow, t] - * om.timeincrement[t] - * sequence(getattr(flows[inflow, outflow], keyword))[t] - for (inflow, outflow) in flows - for t in om.TIMESTEPS))) - - setattr(om, limit_name+"_constraint", po.Constraint( - expr=(getattr(om, limit_name) <= limit))) + ( + "Flow with source: {0} and target: {1} " + "has no attribute {2}." + ).format(i.label, o.label, keyword) + ) + + limit_name = "integral_limit_" + keyword + + setattr( + om, + limit_name, + po.Expression( + expr=sum( + om.flow[inflow, outflow, t] + * om.timeincrement[t] + * sequence(getattr(flows[inflow, outflow], keyword))[t] + for (inflow, outflow) in flows + for t in om.TIMESTEPS + ) + ), + ) + + setattr( + om, + limit_name + "_constraint", + po.Constraint(expr=(getattr(om, limit_name) <= limit)), + ) return om -def limit_active_flow_count(model, constraint_name, flows, - lower_limit=0, upper_limit=None): +def limit_active_flow_count( + model, constraint_name, flows, lower_limit=0, upper_limit=None +): r""" Set limits (lower and/or upper) for the number of concurrently active NonConvex flows. The flows are given as a list. @@ -304,23 +325,29 @@ def limit_active_flow_count(model, constraint_name, flows, def _flow_count_rule(m): for ts in m.TIMESTEPS: - lhs = sum(m.NonConvexFlow.status[fi, fo, ts] - for fi, fo in flows) + lhs = sum(m.NonConvexFlow.status[fi, fo, ts] for fi, fo in flows) rhs = getattr(model, constraint_name)[ts] - expr = (lhs == rhs) + expr = lhs == rhs if expr is not True: getattr(m, attrname_constraint).add(ts, expr) - setattr(model, attrname_constraint, - po.Constraint(model.TIMESTEPS, noruleinit=True)) - setattr(model, attrname_constraint+"_build", - po.BuildAction(rule=_flow_count_rule)) + setattr( + model, + attrname_constraint, + po.Constraint(model.TIMESTEPS, noruleinit=True), + ) + setattr( + model, + attrname_constraint + "_build", + po.BuildAction(rule=_flow_count_rule), + ) return model -def limit_active_flow_count_by_keyword(model, keyword, - lower_limit=0, upper_limit=None): +def limit_active_flow_count_by_keyword( + model, keyword, lower_limit=0, upper_limit=None +): r""" This wrapper for limit_active_flow_count allows to set limits to the count of concurrently active flows by using a keyword @@ -351,10 +378,13 @@ def limit_active_flow_count_by_keyword(model, keyword, if hasattr(model.flows[i, o], keyword): flows.append((i, o)) - return limit_active_flow_count(model, keyword, - flows=flows, - lower_limit=lower_limit, - upper_limit=upper_limit) + return limit_active_flow_count( + model, + keyword, + flows=flows, + lower_limit=lower_limit, + upper_limit=upper_limit, + ) def equate_variables(model, var1, var2, factor1=1, name=None): @@ -415,15 +445,23 @@ def equate_variables(model, var1, var2, factor1=1, name=None): ... om.InvestmentFlow.invest[line21, bel1]) """ if name is None: - name = '_'.join(["equate", str(var1), str(var2)]) + name = "_".join(["equate", str(var1), str(var2)]) def equate_variables_rule(m): return var1 * factor1 == var2 + setattr(model, name, po.Constraint(rule=equate_variables_rule)) -def shared_limit(model, quantity, limit_name, - components, weights, lower_limit=0, upper_limit=None): +def shared_limit( + model, + quantity, + limit_name, + components, + weights, + lower_limit=0, + upper_limit=None, +): r""" Adds a constraint to the given model that restricts the weighted sum of variables to a corridor. @@ -496,13 +534,18 @@ def shared_limit(model, quantity, limit_name, def _weighted_sum_rule(m): for ts in m.TIMESTEPS: - lhs = sum(quantity[c, ts] * w - for c, w in zip(components, weights)) + lhs = sum(quantity[c, ts] * w for c, w in zip(components, weights)) rhs = getattr(model, limit_name)[ts] - expr = (lhs == rhs) + expr = lhs == rhs getattr(m, weighted_sum_constraint).add(ts, expr) - setattr(model, weighted_sum_constraint, - po.Constraint(model.TIMESTEPS, noruleinit=True)) - setattr(model, weighted_sum_constraint+"_build", - po.BuildAction(rule=_weighted_sum_rule)) + setattr( + model, + weighted_sum_constraint, + po.Constraint(model.TIMESTEPS, noruleinit=True), + ) + setattr( + model, + weighted_sum_constraint + "_build", + po.BuildAction(rule=_weighted_sum_rule), + ) diff --git a/src/oemof/solph/custom.py b/src/oemof/solph/custom.py index 6a37903b4..ad4047448 100644 --- a/src/oemof/solph/custom.py +++ b/src/oemof/solph/custom.py @@ -65,9 +65,9 @@ class ElectricalBus(Bus): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.slack = kwargs.get('slack', False) - self.v_max = kwargs.get('v_max', 1000) - self.v_min = kwargs.get('v_min', -1000) + self.slack = kwargs.get("slack", False) + self.v_max = kwargs.get("v_max", 1000) + self.v_min = kwargs.get("v_min", -1000) class ElectricalLine(Flow): @@ -97,16 +97,19 @@ class ElectricalLine(Flow): * :py:class:`~oemof.solph.custom.ElectricalLineBlock` """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.reactance = sequence(kwargs.get('reactance', 0.00001)) + self.reactance = sequence(kwargs.get("reactance", 0.00001)) # set input / output flow values to -1 by default if not set by user if self.nonconvex is not None: raise ValueError( - ("Attribute `nonconvex` must be None for " + - "component `ElectricalLine` from {} to {}!").format( - self.input, self.output)) + ( + "Attribute `nonconvex` must be None for " + + "component `ElectricalLine` from {} to {}!" + ).format(self.input, self.output) + ) if self.min is None: self.min = -1 # to be used in grouping for all bidi flows @@ -148,7 +151,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _create(self, group=None): - """ Creates the linear constraint for the class:`ElectricalLine` + """Creates the linear constraint for the class:`ElectricalLine` block. Parameters @@ -166,13 +169,16 @@ def _create(self, group=None): m = self.parent_block() # create voltage angle variables - self.ELECTRICAL_BUSES = Set(initialize=[n for n in m.es.nodes - if isinstance(n, ElectricalBus)]) + self.ELECTRICAL_BUSES = Set( + initialize=[n for n in m.es.nodes if isinstance(n, ElectricalBus)] + ) def _voltage_angle_bounds(block, b, t): return b.v_min, b.v_max - self.voltage_angle = Var(self.ELECTRICAL_BUSES, m.TIMESTEPS, - bounds=_voltage_angle_bounds) + + self.voltage_angle = Var( + self.ELECTRICAL_BUSES, m.TIMESTEPS, bounds=_voltage_angle_bounds + ) if True not in [b.slack for b in self.ELECTRICAL_BUSES]: # TODO: Make this robust to select the same slack bus for @@ -180,7 +186,9 @@ def _voltage_angle_bounds(block, b, t): bus = [b for b in self.ELECTRICAL_BUSES][0] logging.info( "No slack bus set,setting bus {0} as slack bus".format( - bus.label)) + bus.label + ) + ) bus.slack = True def _voltage_angle_relation(block): @@ -191,18 +199,24 @@ def _voltage_angle_relation(block): self.voltage_angle[n.output, t].fix() try: lhs = m.flow[n.input, n.output, t] - rhs = 1 / n.reactance[t] * ( - self.voltage_angle[n.input, t] - - self.voltage_angle[n.output, t]) + rhs = ( + 1 + / n.reactance[t] + * ( + self.voltage_angle[n.input, t] + - self.voltage_angle[n.output, t] + ) + ) except ValueError: - raise ValueError("Error in constraint creation", - "of node {}".format(n.label)) + raise ValueError( + "Error in constraint creation", + "of node {}".format(n.label), + ) block.electrical_flow.add((n, t), (lhs == rhs)) self.electrical_flow = Constraint(group, m.TIMESTEPS, noruleinit=True) - self.electrical_flow_build = BuildAction( - rule=_voltage_angle_relation) + self.electrical_flow_build = BuildAction(rule=_voltage_angle_relation) class Link(on.Transformer): @@ -247,16 +261,20 @@ class Link(on.Transformer): >>> link.conversion_factors[(bel0, bel1)][3] 0.92 """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.conversion_factors = { k: sequence(v) - for k, v in kwargs.get('conversion_factors', {}).items()} - - wrong_args_message = "Component `Link` must have exactly" \ - + "2 inputs, 2 outputs, and 2" \ - + "conversion factors connecting these." + for k, v in kwargs.get("conversion_factors", {}).items() + } + + wrong_args_message = ( + "Component `Link` must have exactly" + + "2 inputs, 2 outputs, and 2" + + "conversion factors connecting these." + ) assert len(self.inputs) == 2, wrong_args_message assert len(self.outputs) == 2, wrong_args_message assert len(self.conversion_factors) == 2, wrong_args_message @@ -283,7 +301,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _create(self, group=None): - """ Creates the relation for the class:`Link`. + """Creates the relation for the class:`Link`. Parameters ---------- @@ -302,46 +320,61 @@ def _create(self, group=None): all_conversions = {} for n in group: all_conversions[n] = { - k: v for k, v in n.conversion_factors.items()} + k: v for k, v in n.conversion_factors.items() + } def _input_output_relation(block): for t in m.TIMESTEPS: for n, conversion in all_conversions.items(): for cidx, c in conversion.items(): try: - expr = (m.flow[n, cidx[1], t] == - c[t] * m.flow[cidx[0], n, t]) + expr = ( + m.flow[n, cidx[1], t] + == c[t] * m.flow[cidx[0], n, t] + ) except ValueError: raise ValueError( "Error in constraint creation", "from: {0}, to: {1}, via: {2}".format( - cidx[0], cidx[1], n)) + cidx[0], cidx[1], n + ), + ) block.relation.add((n, cidx[0], cidx[1], t), (expr)) self.relation = Constraint( - [(n, cidx[0], cidx[1], t) - for t in m.TIMESTEPS - for n, conversion in all_conversions.items() - for cidx, c in conversion.items()], noruleinit=True) + [ + (n, cidx[0], cidx[1], t) + for t in m.TIMESTEPS + for n, conversion in all_conversions.items() + for cidx, c in conversion.items() + ], + noruleinit=True, + ) self.relation_build = BuildAction(rule=_input_output_relation) def _exclusive_direction_relation(block): for t in m.TIMESTEPS: for n, cf in all_conversions.items(): cf_keys = list(cf.keys()) - expr = (m.flow[cf_keys[0][0], n, t] * cf[cf_keys[0]][t] - + m.flow[cf_keys[1][0], n, t] * cf[cf_keys[1]][t] - == - m.flow[n, cf_keys[0][1], t] - + m.flow[n, cf_keys[1][1], t]) + expr = ( + m.flow[cf_keys[0][0], n, t] * cf[cf_keys[0]][t] + + m.flow[cf_keys[1][0], n, t] * cf[cf_keys[1]][t] + == m.flow[n, cf_keys[0][1], t] + + m.flow[n, cf_keys[1][1], t] + ) block.relation_exclusive_direction.add((n, t), expr) self.relation_exclusive_direction = Constraint( - [(n, t) - for t in m.TIMESTEPS - for n, conversion in all_conversions.items()], noruleinit=True) + [ + (n, t) + for t in m.TIMESTEPS + for n, conversion in all_conversions.items() + ], + noruleinit=True, + ) self.relation_exclusive_direction_build = BuildAction( - rule=_exclusive_direction_relation) + rule=_exclusive_direction_relation + ) class GenericCAES(on.Transformer): @@ -421,15 +454,15 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.electrical_input = kwargs.get('electrical_input') - self.fuel_input = kwargs.get('fuel_input') - self.electrical_output = kwargs.get('electrical_output') - self.params = kwargs.get('params') + self.electrical_input = kwargs.get("electrical_input") + self.fuel_input = kwargs.get("fuel_input") + self.electrical_output = kwargs.get("electrical_output") + self.params = kwargs.get("params") # map specific flows to standard API - self.inputs.update(kwargs.get('electrical_input')) - self.inputs.update(kwargs.get('fuel_input')) - self.outputs.update(kwargs.get('electrical_output')) + self.inputs.update(kwargs.get("electrical_input")) + self.inputs.update(kwargs.get("fuel_input")) + self.outputs.update(kwargs.get("electrical_output")) def constraint_group(self): return GenericCAESBlock @@ -662,75 +695,92 @@ def _create(self, group=None): self.cmp_st = Var(self.GENERICCAES, m.TIMESTEPS, within=Binary) # Compression: Realized capacity - self.cmp_p = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.cmp_p = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Compression: Max. Capacity - self.cmp_p_max = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.cmp_p_max = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Compression: Heat flow - self.cmp_q_out_sum = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.cmp_q_out_sum = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Compression: Waste heat - self.cmp_q_waste = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.cmp_q_waste = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Expansion: Binary variable for operation status self.exp_st = Var(self.GENERICCAES, m.TIMESTEPS, within=Binary) # Expansion: Realized capacity - self.exp_p = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.exp_p = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Expansion: Max. Capacity - self.exp_p_max = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.exp_p_max = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Expansion: Heat flow of natural gas co-firing - self.exp_q_in_sum = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.exp_q_in_sum = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Expansion: Heat flow of natural gas co-firing - self.exp_q_fuel_in = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.exp_q_fuel_in = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Expansion: Heat flow of additional firing - self.exp_q_add_in = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.exp_q_add_in = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Cavern: Filling levelh - self.cav_level = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.cav_level = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Cavern: Energy inflow - self.cav_e_in = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.cav_e_in = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Cavern: Energy outflow - self.cav_e_out = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.cav_e_out = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # TES: Filling levelh - self.tes_level = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.tes_level = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # TES: Energy inflow - self.tes_e_in = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.tes_e_in = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # TES: Energy outflow - self.tes_e_out = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.tes_e_out = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Spot market: Positive capacity - self.exp_p_spot = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.exp_p_spot = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Spot market: Negative capacity - self.cmp_p_spot = Var(self.GENERICCAES, m.TIMESTEPS, - within=NonNegativeReals) + self.cmp_p_spot = Var( + self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals + ) # Compression: Capacity on markets def cmp_p_constr_rule(block, n, t): @@ -738,60 +788,89 @@ def cmp_p_constr_rule(block, n, t): expr += -self.cmp_p[n, t] expr += m.flow[list(n.electrical_input.keys())[0], n, t] return expr == 0 + self.cmp_p_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_constr_rule + ) # Compression: Max. capacity depending on cavern filling level def cmp_p_max_constr_rule(block, n, t): if t != 0: - return (self.cmp_p_max[n, t] == - n.params['cmp_p_max_m'] * self.cav_level[n, t-1] + - n.params['cmp_p_max_b']) + return ( + self.cmp_p_max[n, t] + == n.params["cmp_p_max_m"] * self.cav_level[n, t - 1] + + n.params["cmp_p_max_b"] + ) else: - return self.cmp_p_max[n, t] == n.params['cmp_p_max_b'] + return self.cmp_p_max[n, t] == n.params["cmp_p_max_b"] + self.cmp_p_max_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_max_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_max_constr_rule + ) def cmp_p_max_area_constr_rule(block, n, t): return self.cmp_p[n, t] <= self.cmp_p_max[n, t] + self.cmp_p_max_area_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_max_area_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_max_area_constr_rule + ) # Compression: Status of operation (on/off) def cmp_st_p_min_constr_rule(block, n, t): return ( - self.cmp_p[n, t] >= n.params['cmp_p_min'] * self.cmp_st[n, t]) + self.cmp_p[n, t] >= n.params["cmp_p_min"] * self.cmp_st[n, t] + ) + self.cmp_st_p_min_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cmp_st_p_min_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cmp_st_p_min_constr_rule + ) def cmp_st_p_max_constr_rule(block, n, t): - return (self.cmp_p[n, t] <= - (n.params['cmp_p_max_m'] * n.params['cav_level_max'] + - n.params['cmp_p_max_b']) * self.cmp_st[n, t]) + return ( + self.cmp_p[n, t] + <= ( + n.params["cmp_p_max_m"] * n.params["cav_level_max"] + + n.params["cmp_p_max_b"] + ) + * self.cmp_st[n, t] + ) + self.cmp_st_p_max_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cmp_st_p_max_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cmp_st_p_max_constr_rule + ) # (7) Compression: Heat flow out def cmp_q_out_constr_rule(block, n, t): - return (self.cmp_q_out_sum[n, t] == - n.params['cmp_q_out_m'] * self.cmp_p[n, t] + - n.params['cmp_q_out_b'] * self.cmp_st[n, t]) + return ( + self.cmp_q_out_sum[n, t] + == n.params["cmp_q_out_m"] * self.cmp_p[n, t] + + n.params["cmp_q_out_b"] * self.cmp_st[n, t] + ) + self.cmp_q_out_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_constr_rule + ) # (8) Compression: Definition of single heat flows def cmp_q_out_sum_constr_rule(block, n, t): - return (self.cmp_q_out_sum[n, t] == self.cmp_q_waste[n, t] + - self.tes_e_in[n, t]) + return ( + self.cmp_q_out_sum[n, t] + == self.cmp_q_waste[n, t] + self.tes_e_in[n, t] + ) + self.cmp_q_out_sum_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_sum_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_sum_constr_rule + ) # (9) Compression: Heat flow out ratio def cmp_q_out_shr_constr_rule(block, n, t): - return (self.cmp_q_waste[n, t] * n.params['cmp_q_tes_share'] == - self.tes_e_in[n, t] * (1 - n.params['cmp_q_tes_share'])) + return self.cmp_q_waste[n, t] * n.params[ + "cmp_q_tes_share" + ] == self.tes_e_in[n, t] * (1 - n.params["cmp_q_tes_share"]) + self.cmp_q_out_shr_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_shr_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_shr_constr_rule + ) # (10) Expansion: Capacity on markets def exp_p_constr_rule(block, n, t): @@ -799,48 +878,70 @@ def exp_p_constr_rule(block, n, t): expr += -self.exp_p[n, t] expr += m.flow[n, list(n.electrical_output.keys())[0], t] return expr == 0 + self.exp_p_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=exp_p_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=exp_p_constr_rule + ) # (11-12) Expansion: Max. capacity depending on cavern filling level def exp_p_max_constr_rule(block, n, t): if t != 0: - return (self.exp_p_max[n, t] == - n.params['exp_p_max_m'] * self.cav_level[n, t-1] + - n.params['exp_p_max_b']) + return ( + self.exp_p_max[n, t] + == n.params["exp_p_max_m"] * self.cav_level[n, t - 1] + + n.params["exp_p_max_b"] + ) else: - return self.exp_p_max[n, t] == n.params['exp_p_max_b'] + return self.exp_p_max[n, t] == n.params["exp_p_max_b"] + self.exp_p_max_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=exp_p_max_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=exp_p_max_constr_rule + ) # (13) def exp_p_max_area_constr_rule(block, n, t): return self.exp_p[n, t] <= self.exp_p_max[n, t] + self.exp_p_max_area_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=exp_p_max_area_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=exp_p_max_area_constr_rule + ) # (14) Expansion: Status of operation (on/off) def exp_st_p_min_constr_rule(block, n, t): return ( - self.exp_p[n, t] >= n.params['exp_p_min'] * self.exp_st[n, t]) + self.exp_p[n, t] >= n.params["exp_p_min"] * self.exp_st[n, t] + ) + self.exp_st_p_min_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=exp_st_p_min_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=exp_st_p_min_constr_rule + ) # (15) def exp_st_p_max_constr_rule(block, n, t): - return (self.exp_p[n, t] <= - (n.params['exp_p_max_m'] * n.params['cav_level_max'] + - n.params['exp_p_max_b']) * self.exp_st[n, t]) + return ( + self.exp_p[n, t] + <= ( + n.params["exp_p_max_m"] * n.params["cav_level_max"] + + n.params["exp_p_max_b"] + ) + * self.exp_st[n, t] + ) + self.exp_st_p_max_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=exp_st_p_max_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=exp_st_p_max_constr_rule + ) # (16) Expansion: Heat flow in def exp_q_in_constr_rule(block, n, t): - return (self.exp_q_in_sum[n, t] == - n.params['exp_q_in_m'] * self.exp_p[n, t] + - n.params['exp_q_in_b'] * self.exp_st[n, t]) + return ( + self.exp_q_in_sum[n, t] + == n.params["exp_q_in_m"] * self.exp_p[n, t] + + n.params["exp_q_in_b"] * self.exp_st[n, t] + ) + self.exp_q_in_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_constr_rule + ) # (17) Expansion: Fuel allocation def exp_q_fuel_constr_rule(block, n, t): @@ -848,77 +949,109 @@ def exp_q_fuel_constr_rule(block, n, t): expr += -self.exp_q_fuel_in[n, t] expr += m.flow[list(n.fuel_input.keys())[0], n, t] return expr == 0 + self.exp_q_fuel_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=exp_q_fuel_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=exp_q_fuel_constr_rule + ) # (18) Expansion: Definition of single heat flows def exp_q_in_sum_constr_rule(block, n, t): - return (self.exp_q_in_sum[n, t] == self.exp_q_fuel_in[n, t] + - self.tes_e_out[n, t] + self.exp_q_add_in[n, t]) + return ( + self.exp_q_in_sum[n, t] + == self.exp_q_fuel_in[n, t] + + self.tes_e_out[n, t] + + self.exp_q_add_in[n, t] + ) + self.exp_q_in_sum_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_sum_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_sum_constr_rule + ) # (19) Expansion: Heat flow in ratio def exp_q_in_shr_constr_rule(block, n, t): - return (n.params['exp_q_tes_share'] * self.exp_q_fuel_in[n, t] == - (1 - n.params['exp_q_tes_share']) * - (self.exp_q_add_in[n, t] + self.tes_e_out[n, t])) + return n.params["exp_q_tes_share"] * self.exp_q_fuel_in[n, t] == ( + 1 - n.params["exp_q_tes_share"] + ) * (self.exp_q_add_in[n, t] + self.tes_e_out[n, t]) + self.exp_q_in_shr_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_shr_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_shr_constr_rule + ) # (20) Cavern: Energy inflow def cav_e_in_constr_rule(block, n, t): - return (self.cav_e_in[n, t] == - n.params['cav_e_in_m'] * self.cmp_p[n, t] + - n.params['cav_e_in_b']) + return ( + self.cav_e_in[n, t] + == n.params["cav_e_in_m"] * self.cmp_p[n, t] + + n.params["cav_e_in_b"] + ) + self.cav_e_in_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cav_e_in_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cav_e_in_constr_rule + ) # (21) Cavern: Energy outflow def cav_e_out_constr_rule(block, n, t): - return (self.cav_e_out[n, t] == - n.params['cav_e_out_m'] * self.exp_p[n, t] + - n.params['cav_e_out_b']) + return ( + self.cav_e_out[n, t] + == n.params["cav_e_out_m"] * self.exp_p[n, t] + + n.params["cav_e_out_b"] + ) + self.cav_e_out_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cav_e_out_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cav_e_out_constr_rule + ) # (22-23) Cavern: Storage balance def cav_eta_constr_rule(block, n, t): if t != 0: - return (n.params['cav_eta_temp'] * self.cav_level[n, t] == - self.cav_level[n, t-1] + m.timeincrement[t] * - (self.cav_e_in[n, t] - self.cav_e_out[n, t])) + return n.params["cav_eta_temp"] * self.cav_level[ + n, t + ] == self.cav_level[n, t - 1] + m.timeincrement[t] * ( + self.cav_e_in[n, t] - self.cav_e_out[n, t] + ) else: - return (n.params['cav_eta_temp'] * self.cav_level[n, t] == - m.timeincrement[t] * - (self.cav_e_in[n, t] - self.cav_e_out[n, t])) + return n.params["cav_eta_temp"] * self.cav_level[ + n, t + ] == m.timeincrement[t] * ( + self.cav_e_in[n, t] - self.cav_e_out[n, t] + ) + self.cav_eta_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cav_eta_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cav_eta_constr_rule + ) # (24) Cavern: Upper bound def cav_ub_constr_rule(block, n, t): - return self.cav_level[n, t] <= n.params['cav_level_max'] + return self.cav_level[n, t] <= n.params["cav_level_max"] + self.cav_ub_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=cav_ub_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=cav_ub_constr_rule + ) # (25-26) TES: Storage balance def tes_eta_constr_rule(block, n, t): if t != 0: - return (self.tes_level[n, t] == - self.tes_level[n, t-1] + m.timeincrement[t] * - (self.tes_e_in[n, t] - self.tes_e_out[n, t])) + return self.tes_level[n, t] == self.tes_level[ + n, t - 1 + ] + m.timeincrement[t] * ( + self.tes_e_in[n, t] - self.tes_e_out[n, t] + ) else: - return (self.tes_level[n, t] == - m.timeincrement[t] * - (self.tes_e_in[n, t] - self.tes_e_out[n, t])) + return self.tes_level[n, t] == m.timeincrement[t] * ( + self.tes_e_in[n, t] - self.tes_e_out[n, t] + ) + self.tes_eta_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=tes_eta_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=tes_eta_constr_rule + ) # (27) TES: Upper bound def tes_ub_constr_rule(block, n, t): - return self.tes_level[n, t] <= n.params['tes_level_max'] + return self.tes_level[n, t] <= n.params["tes_level_max"] + self.tes_ub_constr = Constraint( - self.GENERICCAES, m.TIMESTEPS, rule=tes_ub_constr_rule) + self.GENERICCAES, m.TIMESTEPS, rule=tes_ub_constr_rule + ) class SinkDSM(Sink): @@ -996,9 +1129,18 @@ class SinkDSM(Sink): """ - def __init__(self, demand, capacity_up, capacity_down, method, - shift_interval=None, delay_time=None, cost_dsm_up=0, - cost_dsm_down=0, **kwargs): + def __init__( + self, + demand, + capacity_up, + capacity_down, + method, + shift_interval=None, + delay_time=None, + cost_dsm_up=0, + cost_dsm_down=0, + **kwargs, + ): super().__init__(**kwargs) self.capacity_up = sequence(capacity_up) @@ -1011,22 +1153,26 @@ def __init__(self, demand, capacity_up, capacity_down, method, self.cost_dsm_down = cost_dsm_down def constraint_group(self): - possible_methods = ['delay', 'interval'] + possible_methods = ["delay", "interval"] if self.method == possible_methods[0]: if self.delay_time is None: - raise ValueError('Please define: **delay_time' - 'is a mandatory parameter') + raise ValueError( + "Please define: **delay_time" "is a mandatory parameter" + ) return SinkDSMDelayBlock elif self.method == possible_methods[1]: if self.shift_interval is None: - raise ValueError('Please define: **shift_interval' - ' is a mandatory parameter') + raise ValueError( + "Please define: **shift_interval" + " is a mandatory parameter" + ) return SinkDSMIntervalBlock else: raise ValueError( 'The "method" must be one of the following set: ' - '"{}"'.format('" or "'.join(possible_methods))) + '"{}"'.format('" or "'.join(possible_methods)) + ) class SinkDSMIntervalBlock(SimpleBlock): @@ -1099,12 +1245,14 @@ def _create(self, group=None): # ************* VARIABLES ***************************** # Variable load shift down - self.dsm_do = Var(self.dsm, m.TIMESTEPS, initialize=0, - within=NonNegativeReals) + self.dsm_do = Var( + self.dsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals + ) # Variable load shift up - self.dsm_up = Var(self.dsm, m.TIMESTEPS, initialize=0, - within=NonNegativeReals) + self.dsm_up = Var( + self.dsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals + ) # ************* CONSTRAINTS ***************************** @@ -1126,10 +1274,12 @@ def _input_output_relation_rule(block): # add constraint block.input_output_relation.add((g, t), (lhs == rhs)) - self.input_output_relation = Constraint(group, m.TIMESTEPS, - noruleinit=True) + self.input_output_relation = Constraint( + group, m.TIMESTEPS, noruleinit=True + ) self.input_output_relation_build = BuildAction( - rule=_input_output_relation_rule) + rule=_input_output_relation_rule + ) # Upper bounds relation def dsm_up_constraint_rule(block): @@ -1148,8 +1298,9 @@ def dsm_up_constraint_rule(block): # add constraint block.dsm_up_constraint.add((g, t), (lhs <= rhs)) - self.dsm_up_constraint = Constraint(group, m.TIMESTEPS, - noruleinit=True) + self.dsm_up_constraint = Constraint( + group, m.TIMESTEPS, noruleinit=True + ) self.dsm_up_constraint_build = BuildAction(rule=dsm_up_constraint_rule) # Upper bounds relation @@ -1169,10 +1320,12 @@ def dsm_down_constraint_rule(block): # add constraint block.dsm_down_constraint.add((g, t), (lhs <= rhs)) - self.dsm_down_constraint = Constraint(group, m.TIMESTEPS, - noruleinit=True) + self.dsm_down_constraint = Constraint( + group, m.TIMESTEPS, noruleinit=True + ) self.dsm_down_constraint_build = BuildAction( - rule=dsm_down_constraint_rule) + rule=dsm_down_constraint_rule + ) def dsm_sum_constraint_rule(block): """ @@ -1183,33 +1336,38 @@ def dsm_sum_constraint_rule(block): """ for g in group: - intervals = range(m.TIMESTEPS.value_list[0], - m.TIMESTEPS.value_list[-1], - g.shift_interval) + intervals = range( + m.TIMESTEPS.value_list[0], + m.TIMESTEPS.value_list[-1], + g.shift_interval, + ) for interval in intervals: - if (interval + g.shift_interval - 1) \ - > m.TIMESTEPS.value_list[-1]: - timesteps = range(interval, - m.TIMESTEPS.value_list[-1] + 1) + if ( + interval + g.shift_interval - 1 + ) > m.TIMESTEPS.value_list[-1]: + timesteps = range( + interval, m.TIMESTEPS.value_list[-1] + 1 + ) else: - timesteps = range(interval, interval + - g.shift_interval) + timesteps = range( + interval, interval + g.shift_interval + ) # DSM up/down - lhs = sum(self.dsm_up[g, tt] - for tt in timesteps) + lhs = sum(self.dsm_up[g, tt] for tt in timesteps) # value - rhs = sum(self.dsm_do[g, tt] - for tt in timesteps) + rhs = sum(self.dsm_do[g, tt] for tt in timesteps) # add constraint block.dsm_sum_constraint.add((g, interval), (lhs == rhs)) - self.dsm_sum_constraint = Constraint(group, m.TIMESTEPS, - noruleinit=True) + self.dsm_sum_constraint = Constraint( + group, m.TIMESTEPS, noruleinit=True + ) self.dsm_sum_constraint_build = BuildAction( - rule=dsm_sum_constraint_rule) + rule=dsm_sum_constraint_rule + ) def _objective_expression(self): """Adding cost terms for DSM activity to obj. function""" @@ -1307,12 +1465,18 @@ def _create(self, group=None): # ************* VARIABLES ***************************** # Variable load shift down - self.dsm_do = Var(self.dsm, m.TIMESTEPS, m.TIMESTEPS, initialize=0, - within=NonNegativeReals) + self.dsm_do = Var( + self.dsm, + m.TIMESTEPS, + m.TIMESTEPS, + initialize=0, + within=NonNegativeReals, + ) # Variable load shift up - self.dsm_up = Var(self.dsm, m.TIMESTEPS, initialize=0, - within=NonNegativeReals) + self.dsm_up = Var( + self.dsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals + ) # ************* CONSTRAINTS ***************************** @@ -1331,24 +1495,34 @@ def _input_output_relation_rule(block): # Generator loads from bus lhs = m.flow[g.inflow, g, t] # Demand +- DSM - rhs = g.demand[t] + self.dsm_up[g, t] - sum( - self.dsm_do[g, tt, t] - for tt in range(t + g.delay_time + 1)) + rhs = ( + g.demand[t] + + self.dsm_up[g, t] + - sum( + self.dsm_do[g, tt, t] + for tt in range(t + g.delay_time + 1) + ) + ) # add constraint block.input_output_relation.add((g, t), (lhs == rhs)) # main use case - elif (g.delay_time < t <= - m.TIMESTEPS[-1] - g.delay_time): + elif g.delay_time < t <= m.TIMESTEPS[-1] - g.delay_time: # Generator loads from bus lhs = m.flow[g.inflow, g, t] # Demand +- DSM - rhs = g.demand[t] + self.dsm_up[g, t] - sum( - self.dsm_do[g, tt, t] - for tt in range(t - g.delay_time, - t + g.delay_time + 1)) + rhs = ( + g.demand[t] + + self.dsm_up[g, t] + - sum( + self.dsm_do[g, tt, t] + for tt in range( + t - g.delay_time, t + g.delay_time + 1 + ) + ) + ) # add constraint block.input_output_relation.add((g, t), (lhs == rhs)) @@ -1358,18 +1532,26 @@ def _input_output_relation_rule(block): # Generator loads from bus lhs = m.flow[g.inflow, g, t] # Demand +- DSM - rhs = g.demand[t] + self.dsm_up[g, t] - sum( - self.dsm_do[g, tt, t] - for tt in range(t - g.delay_time, - m.TIMESTEPS[-1] + 1)) + rhs = ( + g.demand[t] + + self.dsm_up[g, t] + - sum( + self.dsm_do[g, tt, t] + for tt in range( + t - g.delay_time, m.TIMESTEPS[-1] + 1 + ) + ) + ) # add constraint block.input_output_relation.add((g, t), (lhs == rhs)) - self.input_output_relation = Constraint(group, m.TIMESTEPS, - noruleinit=True) + self.input_output_relation = Constraint( + group, m.TIMESTEPS, noruleinit=True + ) self.input_output_relation_build = BuildAction( - rule=_input_output_relation_rule) + rule=_input_output_relation_rule + ) # Equation 7 def dsm_up_down_constraint_rule(block): @@ -1389,22 +1571,26 @@ def dsm_up_down_constraint_rule(block): # DSM up lhs = self.dsm_up[g, t] # DSM down - rhs = sum(self.dsm_do[g, t, tt] - for tt in range(t + g.delay_time + 1)) + rhs = sum( + self.dsm_do[g, t, tt] + for tt in range(t + g.delay_time + 1) + ) # add constraint block.dsm_updo_constraint.add((g, t), (lhs == rhs)) # main use case - elif g.delay_time < t <= ( - m.TIMESTEPS[-1] - g.delay_time): + elif g.delay_time < t <= (m.TIMESTEPS[-1] - g.delay_time): # DSM up lhs = self.dsm_up[g, t] # DSM down - rhs = sum(self.dsm_do[g, t, tt] - for tt in range(t - g.delay_time, - t + g.delay_time + 1)) + rhs = sum( + self.dsm_do[g, t, tt] + for tt in range( + t - g.delay_time, t + g.delay_time + 1 + ) + ) # add constraint block.dsm_updo_constraint.add((g, t), (lhs == rhs)) @@ -1415,17 +1601,22 @@ def dsm_up_down_constraint_rule(block): # DSM up lhs = self.dsm_up[g, t] # DSM down - rhs = sum(self.dsm_do[g, t, tt] - for tt in range(t - g.delay_time, - m.TIMESTEPS[-1] + 1)) + rhs = sum( + self.dsm_do[g, t, tt] + for tt in range( + t - g.delay_time, m.TIMESTEPS[-1] + 1 + ) + ) # add constraint block.dsm_updo_constraint.add((g, t), (lhs == rhs)) - self.dsm_updo_constraint = Constraint(group, m.TIMESTEPS, - noruleinit=True) + self.dsm_updo_constraint = Constraint( + group, m.TIMESTEPS, noruleinit=True + ) self.dsm_updo_constraint_build = BuildAction( - rule=dsm_up_down_constraint_rule) + rule=dsm_up_down_constraint_rule + ) # Equation 8 def dsm_up_constraint_rule(block): @@ -1445,8 +1636,9 @@ def dsm_up_constraint_rule(block): # add constraint block.dsm_up_constraint.add((g, t), (lhs <= rhs)) - self.dsm_up_constraint = Constraint(group, m.TIMESTEPS, - noruleinit=True) + self.dsm_up_constraint = Constraint( + group, m.TIMESTEPS, noruleinit=True + ) self.dsm_up_constraint_build = BuildAction(rule=dsm_up_constraint_rule) # Equation 9 @@ -1464,8 +1656,10 @@ def dsm_do_constraint_rule(block): if tt <= g.delay_time: # DSM down - lhs = sum(self.dsm_do[g, t, tt] - for t in range(tt + g.delay_time + 1)) + lhs = sum( + self.dsm_do[g, t, tt] + for t in range(tt + g.delay_time + 1) + ) # Capacity DSM down rhs = g.capacity_down[tt] @@ -1473,13 +1667,15 @@ def dsm_do_constraint_rule(block): block.dsm_do_constraint.add((g, tt), (lhs <= rhs)) # main use case - elif g.delay_time < tt <= ( - m.TIMESTEPS[-1] - g.delay_time): + elif g.delay_time < tt <= (m.TIMESTEPS[-1] - g.delay_time): # DSM down - lhs = sum(self.dsm_do[g, t, tt] - for t in range(tt - g.delay_time, - tt + g.delay_time + 1)) + lhs = sum( + self.dsm_do[g, t, tt] + for t in range( + tt - g.delay_time, tt + g.delay_time + 1 + ) + ) # Capacity DSM down rhs = g.capacity_down[tt] @@ -1490,19 +1686,22 @@ def dsm_do_constraint_rule(block): else: # DSM down - lhs = sum(self.dsm_do[g, t, tt] - for t in range(tt - g.delay_time, - m.TIMESTEPS[-1] + 1)) + lhs = sum( + self.dsm_do[g, t, tt] + for t in range( + tt - g.delay_time, m.TIMESTEPS[-1] + 1 + ) + ) # Capacity DSM down rhs = g.capacity_down[tt] # add constraint block.dsm_do_constraint.add((g, tt), (lhs <= rhs)) - self.dsm_do_constraint = Constraint(group, m.TIMESTEPS, - noruleinit=True) - self.dsm_do_constraint_build = BuildAction( - rule=dsm_do_constraint_rule) + self.dsm_do_constraint = Constraint( + group, m.TIMESTEPS, noruleinit=True + ) + self.dsm_do_constraint_build = BuildAction(rule=dsm_do_constraint_rule) # Equation 10 def c2_constraint_rule(block): @@ -1522,21 +1721,23 @@ def c2_constraint_rule(block): # DSM up/down lhs = self.dsm_up[g, tt] + sum( self.dsm_do[g, t, tt] - for t in range(tt + g.delay_time + 1)) + for t in range(tt + g.delay_time + 1) + ) # max capacity at tt rhs = max(g.capacity_up[tt], g.capacity_down[tt]) # add constraint block.C2_constraint.add((g, tt), (lhs <= rhs)) - elif g.delay_time < tt <= ( - m.TIMESTEPS[-1] - g.delay_time): + elif g.delay_time < tt <= (m.TIMESTEPS[-1] - g.delay_time): # DSM up/down lhs = self.dsm_up[g, tt] + sum( self.dsm_do[g, t, tt] - for t in range(tt - g.delay_time, - tt + g.delay_time + 1)) + for t in range( + tt - g.delay_time, tt + g.delay_time + 1 + ) + ) # max capacity at tt rhs = max(g.capacity_up[tt], g.capacity_down[tt]) @@ -1548,8 +1749,10 @@ def c2_constraint_rule(block): # DSM up/down lhs = self.dsm_up[g, tt] + sum( self.dsm_do[g, t, tt] - for t in range(tt - g.delay_time, - m.TIMESTEPS[-1] + 1)) + for t in range( + tt - g.delay_time, m.TIMESTEPS[-1] + 1 + ) + ) # max capacity at tt rhs = max(g.capacity_up[tt], g.capacity_down[tt]) @@ -1569,8 +1772,10 @@ def _objective_expression(self): for t in m.TIMESTEPS: for g in self.dsm: dsm_cost += self.dsm_up[g, t] * g.cost_dsm_up - dsm_cost += sum(self.dsm_do[g, t, tt] for tt in m.TIMESTEPS - ) * g.cost_dsm_down + dsm_cost += ( + sum(self.dsm_do[g, t, tt] for tt in m.TIMESTEPS) + * g.cost_dsm_down + ) self.cost = Expression(expr=dsm_cost) @@ -1615,22 +1820,26 @@ class PiecewiseLinearTransformer(on.Transformer): >>> type(pwltf) """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.in_breakpoints = list(kwargs.get('in_breakpoints')) - self.conversion_function = kwargs.get('conversion_function') - self.pw_repn = kwargs.get('pw_repn') + self.in_breakpoints = list(kwargs.get("in_breakpoints")) + self.conversion_function = kwargs.get("conversion_function") + self.pw_repn = kwargs.get("pw_repn") if len(self.inputs) > 1 or len(self.outputs) > 1: raise ValueError( - 'Component `PiecewiseLinearTransformer` cannot have ' + - 'more than 1 input and 1 output!') + "Component `PiecewiseLinearTransformer` cannot have " + + "more than 1 input and 1 output!" + ) nominal_value = [a.nominal_value for a in self.inputs.values()][0] if max(self.in_breakpoints) < nominal_value: - raise ValueError('Largest in_breakpoint must be larger or equal ' + - 'nominal value') + raise ValueError( + "Largest in_breakpoint must be larger or equal " + + "nominal value" + ) def constraint_group(self): return PiecewiseLinearTransformerBlock @@ -1649,7 +1858,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _create(self, group=None): - """ Creates the relation for the class:`PiecewiseLinearTransformer`. + """Creates the relation for the class:`PiecewiseLinearTransformer`. Parameters ---------- @@ -1670,16 +1879,20 @@ def _create(self, group=None): if all(x == pw_repns[0] for x in pw_repns): self.pw_repn = pw_repns[0] else: - print('Cannot different piecewise representations ', - [n.pw_repn for n in group]) + print( + "Cannot different piecewise representations ", + [n.pw_repn for n in group], + ) self.breakpoints = {} def build_breakpoints(block, n): for t in m.TIMESTEPS: self.breakpoints[(n, t)] = n.in_breakpoints - self.breakpoint_build = BuildAction(self.PWLINEARTRANSFORMERS, - rule=build_breakpoints) + + self.breakpoint_build = BuildAction( + self.PWLINEARTRANSFORMERS, rule=build_breakpoints + ) def _conversion_function(block, n, t, x): expr = n.conversion_function(x) @@ -1688,10 +1901,14 @@ def _conversion_function(block, n, t, x): # bounds are min/max of breakpoints lower_bound_in = {n: min(n.in_breakpoints) for n in group} upper_bound_in = {n: max(n.in_breakpoints) for n in group} - lower_bound_out = {n: n.conversion_function(bound) for - (n, bound) in lower_bound_in.items()} - upper_bound_out = {n: n.conversion_function(bound) for - (n, bound) in upper_bound_in.items()} + lower_bound_out = { + n: n.conversion_function(bound) + for (n, bound) in lower_bound_in.items() + } + upper_bound_out = { + n: n.conversion_function(bound) + for (n, bound) in upper_bound_in.items() + } def get_inflow_bounds(model, n, t): return lower_bound_in[n], upper_bound_in[n] @@ -1699,36 +1916,42 @@ def get_inflow_bounds(model, n, t): def get_outflow_bounds(model, n, t): return lower_bound_out[n], upper_bound_out[n] - self.inflow = Var(self.PWLINEARTRANSFORMERS, m.TIMESTEPS, - bounds=get_inflow_bounds) - self.outflow = Var(self.PWLINEARTRANSFORMERS, m.TIMESTEPS, - bounds=get_outflow_bounds) + self.inflow = Var( + self.PWLINEARTRANSFORMERS, m.TIMESTEPS, bounds=get_inflow_bounds + ) + self.outflow = Var( + self.PWLINEARTRANSFORMERS, m.TIMESTEPS, bounds=get_outflow_bounds + ) def _in_equation(block, n, t): """Link binary input and output flow to component outflow.""" expr = 0 - expr += - m.flow[list(n.inputs.keys())[0], n, t] + expr += -m.flow[list(n.inputs.keys())[0], n, t] expr += self.inflow[n, t] return expr == 0 - self.equate_in = Constraint(self.PWLINEARTRANSFORMERS, m.TIMESTEPS, - rule=_in_equation) + self.equate_in = Constraint( + self.PWLINEARTRANSFORMERS, m.TIMESTEPS, rule=_in_equation + ) def _out_equation(block, n, t): """Link binary input and output flow to component outflow.""" expr = 0 - expr += - m.flow[n, list(n.outputs.keys())[0], t] + expr += -m.flow[n, list(n.outputs.keys())[0], t] expr += self.outflow[n, t] return expr == 0 - self.equate_out = Constraint(self.PWLINEARTRANSFORMERS, m.TIMESTEPS, - rule=_out_equation) - - self.piecewise = Piecewise(self.PWLINEARTRANSFORMERS, - m.TIMESTEPS, - self.outflow, - self.inflow, - pw_repn=self.pw_repn, - pw_constr_type='EQ', - pw_pts=self.breakpoints, - f_rule=_conversion_function) + self.equate_out = Constraint( + self.PWLINEARTRANSFORMERS, m.TIMESTEPS, rule=_out_equation + ) + + self.piecewise = Piecewise( + self.PWLINEARTRANSFORMERS, + m.TIMESTEPS, + self.outflow, + self.inflow, + pw_repn=self.pw_repn, + pw_constr_type="EQ", + pw_pts=self.breakpoints, + f_rule=_conversion_function, + ) diff --git a/src/oemof/solph/groupings.py b/src/oemof/solph/groupings.py index 3c1e4b6b3..bfd71138d 100644 --- a/src/oemof/solph/groupings.py +++ b/src/oemof/solph/groupings.py @@ -56,12 +56,11 @@ def constraint_grouping(node, fallback=lambda *xs, **ks: None): return cg() -standard_flow_grouping = groupings.FlowsWithNodes( - constant_key=blocks.Flow) +standard_flow_grouping = groupings.FlowsWithNodes(constant_key=blocks.Flow) def _investment_grouping(stf): - if hasattr(stf[2], 'investment'): + if hasattr(stf[2], "investment"): if stf[2].investment is not None: return True else: @@ -71,11 +70,12 @@ def _investment_grouping(stf): investment_flow_grouping = groupings.FlowsWithNodes( constant_key=blocks.InvestmentFlow, # stf: a tuple consisting of (source, target, flow), so stf[2] is the flow. - filter=_investment_grouping) + filter=_investment_grouping, +) def _nonconvex_grouping(stf): - if hasattr(stf[2], 'nonconvex'): + if hasattr(stf[2], "nonconvex"): if stf[2].nonconvex is not None: return True else: @@ -83,9 +83,13 @@ def _nonconvex_grouping(stf): nonconvex_flow_grouping = groupings.FlowsWithNodes( - constant_key=blocks.NonConvexFlow, - filter=_nonconvex_grouping) + constant_key=blocks.NonConvexFlow, filter=_nonconvex_grouping +) -GROUPINGS = [constraint_grouping, investment_flow_grouping, - standard_flow_grouping, nonconvex_flow_grouping] +GROUPINGS = [ + constraint_grouping, + investment_flow_grouping, + standard_flow_grouping, + nonconvex_flow_grouping, +] diff --git a/src/oemof/solph/helpers.py b/src/oemof/solph/helpers.py index f63b8fd12..744879898 100644 --- a/src/oemof/solph/helpers.py +++ b/src/oemof/solph/helpers.py @@ -29,7 +29,7 @@ def get_basic_path(): """Returns the basic oemof path and creates it if necessary. The basic path is the '.oemof' folder in the $HOME directory. """ - basicpath = os.path.join(os.path.expanduser('~'), '.oemof') + basicpath = os.path.join(os.path.expanduser("~"), ".oemof") if not os.path.isdir(basicpath): os.mkdir(basicpath) return basicpath @@ -37,7 +37,7 @@ def get_basic_path(): def extend_basic_path(subfolder): """Returns a path based on the basic oemof path and creates it if - necessary. The subfolder is the name of the path extension. + necessary. The subfolder is the name of the path extension. """ extended_path = os.path.join(get_basic_path(), subfolder) if not os.path.isdir(extended_path): @@ -45,7 +45,7 @@ def extend_basic_path(subfolder): return extended_path -def flatten(d, parent_key='', sep='_'): +def flatten(d, parent_key="", sep="_"): """ Flatten dictionary by compressing keys. @@ -80,9 +80,11 @@ def calculate_timeincrement(timeindex, fill_value=None): fill_value: numerical timeincrement for first timestep in hours """ - if isinstance(timeindex, pd.DatetimeIndex) and \ - (fill_value and isinstance(fill_value, pd.Timedelta) or - fill_value is None): + if isinstance(timeindex, pd.DatetimeIndex) and ( + fill_value + and isinstance(fill_value, pd.Timedelta) + or fill_value is None + ): if len(set(timeindex)) != len(timeindex): raise IndexError("No equal DatetimeIndex allowed!") timeindex = timeindex.to_series() @@ -90,13 +92,13 @@ def calculate_timeincrement(timeindex, fill_value=None): if fill_value: timeincrement = timeindex_sorted.diff().fillna(value=fill_value) else: - timeincrement = timeindex_sorted.diff().fillna(method='bfill') + timeincrement = timeindex_sorted.diff().fillna(method="bfill") timeincrement_sec = timeincrement.map(dt.timedelta.total_seconds) - timeincrement_hourly = list(timeincrement_sec.map( - lambda x: x/3600)) + timeincrement_hourly = list(timeincrement_sec.map(lambda x: x / 3600)) timeincrement = sequence(timeincrement_hourly) return timeincrement else: raise AttributeError( - "'timeindex' must be of type 'DatetimeIndex' and " + - "'fill_value' of type 'Timedelta'.") + "'timeindex' must be of type 'DatetimeIndex' and " + + "'fill_value' of type 'Timedelta'." + ) diff --git a/src/oemof/solph/models.py b/src/oemof/solph/models.py index a4c58b687..4cb035969 100644 --- a/src/oemof/solph/models.py +++ b/src/oemof/solph/models.py @@ -24,7 +24,7 @@ class BaseModel(po.ConcreteModel): - """ The BaseModel for other solph-models (Model, MultiPeriodModel, etc.) + """The BaseModel for other solph-models (Model, MultiPeriodModel, etc.) Parameters ---------- @@ -61,6 +61,7 @@ class BaseModel(po.ConcreteModel): rc : ... or None """ + CONSTRAINT_GROUPS = [] def __init__(self, energysystem, **kwargs): @@ -68,30 +69,39 @@ def __init__(self, energysystem, **kwargs): # ######################## Arguments ################################# - self.name = kwargs.get('name', type(self).__name__) + self.name = kwargs.get("name", type(self).__name__) self.es = energysystem - self.timeincrement = sequence(kwargs.get('timeincrement', - self.es.timeincrement)) + self.timeincrement = sequence( + kwargs.get("timeincrement", self.es.timeincrement) + ) if self.timeincrement[0] is None: try: self.timeincrement = sequence( - self.es.timeindex.freq.nanos / 3.6e12) + self.es.timeindex.freq.nanos / 3.6e12 + ) except AttributeError: - msg = ("No valid time increment found. Please pass a valid " - "timeincremet parameter or pass an EnergySystem with " - "a valid time index. Please note that a valid time" - "index need to have a 'freq' attribute.") + msg = ( + "No valid time increment found. Please pass a valid " + "timeincremet parameter or pass an EnergySystem with " + "a valid time index. Please note that a valid time" + "index need to have a 'freq' attribute." + ) raise AttributeError(msg) - self.objective_weighting = kwargs.get('objective_weighting', - self.timeincrement) + self.objective_weighting = kwargs.get( + "objective_weighting", self.timeincrement + ) - self._constraint_groups = (type(self).CONSTRAINT_GROUPS + - kwargs.get('constraint_groups', [])) + self._constraint_groups = type(self).CONSTRAINT_GROUPS + kwargs.get( + "constraint_groups", [] + ) - self._constraint_groups += [i for i in self.es.groups - if hasattr(i, 'CONSTRAINT_GROUP') and - i not in self._constraint_groups] + self._constraint_groups += [ + i + for i in self.es.groups + if hasattr(i, "CONSTRAINT_GROUP") + and i not in self._constraint_groups + ] self.flows = self.es.flows() @@ -103,28 +113,27 @@ def __init__(self, energysystem, **kwargs): self._construct() def _construct(self): - """ - """ + """""" self._add_parent_block_sets() self._add_parent_block_variables() self._add_child_blocks() self._add_objective() def _add_parent_block_sets(self): - """" Method to create all sets located at the parent block, i.e. the + """ " Method to create all sets located at the parent block, i.e. the model itself as they are to be shared across all model components. """ pass def _add_parent_block_variables(self): - """" Method to create all variables located at the parent block, + """ " Method to create all variables located at the parent block, i.e. the model itself as these variables are to be shared across all model components. """ pass def _add_child_blocks(self): - """ Method to add the defined child blocks for components that have + """Method to add the defined child blocks for components that have been grouped in the defined constraint groups. """ @@ -138,24 +147,24 @@ def _add_child_blocks(self): block._create(group=self.es.groups.get(group)) def _add_objective(self, sense=po.minimize, update=False): - """ Method to sum up all objective expressions from the child blocks + """Method to sum up all objective expressions from the child blocks that have been created. This method looks for `_objective_expression` attribute in the block definition and will call this method to add their return value to the objective function. """ if update: - self.del_component('objective') + self.del_component("objective") expr = 0 for block in self.component_data_objects(): - if hasattr(block, '_objective_expression'): + if hasattr(block, "_objective_expression"): expr += block._objective_expression() self.objective = po.Objective(sense=sense, expr=expr) def receive_duals(self): - """ Method sets solver suffix to extract information about dual + """Method sets solver suffix to extract information about dual variables from solver. Shadow prices (duals) and reduced costs (rc) are set as attributes of the model. @@ -166,12 +175,11 @@ def receive_duals(self): self.rc = po.Suffix(direction=po.Suffix.IMPORT) def results(self): - """ Returns a nested dictionary of the results of this optimization - """ + """Returns a nested dictionary of the results of this optimization""" return processing.results(self) - def solve(self, solver='cbc', solver_io='lp', **kwargs): - r""" Takes care of communication with solver to solve the model. + def solve(self, solver="cbc", solver_io="lp", **kwargs): + r"""Takes care of communication with solver to solve the model. Parameters ---------- @@ -195,7 +203,7 @@ def solve(self, solver='cbc', solver_io='lp', **kwargs): {"method": 2} """ - solve_kwargs = kwargs.get('solve_kwargs', {}) + solve_kwargs = kwargs.get("solve_kwargs", {}) solver_cmdline_options = kwargs.get("cmdline_options", {}) opt = SolverFactory(solver, solver_io=solver_io) @@ -207,16 +215,20 @@ def solve(self, solver='cbc', solver_io='lp', **kwargs): solver_results = opt.solve(self, **solve_kwargs) status = solver_results["Solver"][0]["Status"] - termination_condition = ( - solver_results["Solver"][0]["Termination condition"]) + termination_condition = solver_results["Solver"][0][ + "Termination condition" + ] if status == "ok" and termination_condition == "optimal": logging.info("Optimization successful...") else: - msg = ("Optimization ended with status {0} and termination " - "condition {1}") - warnings.warn(msg.format(status, termination_condition), - UserWarning) + msg = ( + "Optimization ended with status {0} and termination " + "condition {1}" + ) + warnings.warn( + msg.format(status, termination_condition), UserWarning + ) self.es.results = solver_results self.solver_results = solver_results @@ -231,7 +243,7 @@ def relax_problem(self): class Model(BaseModel): - """ An energy system model for operational and investment + """An energy system model for operational and investment optimization. Parameters @@ -262,22 +274,27 @@ class Model(BaseModel): the corresponding flow object. """ - CONSTRAINT_GROUPS = [blocks.Bus, blocks.Transformer, - blocks.InvestmentFlow, blocks.Flow, - blocks.NonConvexFlow] + + CONSTRAINT_GROUPS = [ + blocks.Bus, + blocks.Transformer, + blocks.InvestmentFlow, + blocks.Flow, + blocks.NonConvexFlow, + ] def __init__(self, energysystem, **kwargs): super().__init__(energysystem, **kwargs) def _add_parent_block_sets(self): - """ - """ + """""" # set with all nodes self.NODES = po.Set(initialize=[n for n in self.es.nodes]) # pyomo set for timesteps of optimization problem - self.TIMESTEPS = po.Set(initialize=range(len(self.es.timeindex)), - ordered=True) + self.TIMESTEPS = po.Set( + initialize=range(len(self.es.timeindex)), ordered=True + ) # previous timesteps previous_timesteps = [x - 1 for x in self.TIMESTEPS] @@ -286,44 +303,58 @@ def _add_parent_block_sets(self): self.previous_timesteps = dict(zip(self.TIMESTEPS, previous_timesteps)) # pyomo set for all flows in the energy system graph - self.FLOWS = po.Set(initialize=self.flows.keys(), - ordered=True, dimen=2) - - self.BIDIRECTIONAL_FLOWS = po.Set(initialize=[ - k for (k, v) in self.flows.items() if hasattr(v, 'bidirectional')], - ordered=True, dimen=2, - within=self.FLOWS) + self.FLOWS = po.Set( + initialize=self.flows.keys(), ordered=True, dimen=2 + ) + + self.BIDIRECTIONAL_FLOWS = po.Set( + initialize=[ + k + for (k, v) in self.flows.items() + if hasattr(v, "bidirectional") + ], + ordered=True, + dimen=2, + within=self.FLOWS, + ) self.UNIDIRECTIONAL_FLOWS = po.Set( - initialize=[k for (k, v) in self.flows.items() if not - hasattr(v, 'bidirectional')], - ordered=True, dimen=2, within=self.FLOWS) + initialize=[ + k + for (k, v) in self.flows.items() + if not hasattr(v, "bidirectional") + ], + ordered=True, + dimen=2, + within=self.FLOWS, + ) def _add_parent_block_variables(self): - """ - """ - self.flow = po.Var(self.FLOWS, self.TIMESTEPS, - within=po.Reals) + """""" + self.flow = po.Var(self.FLOWS, self.TIMESTEPS, within=po.Reals) for (o, i) in self.FLOWS: if self.flows[o, i].nominal_value is not None: if self.flows[o, i].fix[self.TIMESTEPS[1]] is not None: for t in self.TIMESTEPS: self.flow[o, i, t].value = ( - self.flows[o, i].fix[t] * - self.flows[o, i].nominal_value) + self.flows[o, i].fix[t] + * self.flows[o, i].nominal_value + ) self.flow[o, i, t].fix() else: for t in self.TIMESTEPS: self.flow[o, i, t].setub( - self.flows[o, i].max[t] * - self.flows[o, i].nominal_value) + self.flows[o, i].max[t] + * self.flows[o, i].nominal_value + ) if not self.flows[o, i].nonconvex: for t in self.TIMESTEPS: self.flow[o, i, t].setlb( - self.flows[o, i].min[t] * - self.flows[o, i].nominal_value) + self.flows[o, i].min[t] + * self.flows[o, i].nominal_value + ) elif (o, i) in self.UNIDIRECTIONAL_FLOWS: for t in self.TIMESTEPS: self.flow[o, i, t].setlb(0) diff --git a/src/oemof/solph/network.py b/src/oemof/solph/network.py index 59b4cda23..1d69b374c 100644 --- a/src/oemof/solph/network.py +++ b/src/oemof/solph/network.py @@ -28,7 +28,7 @@ class EnergySystem(es.EnergySystem): - """ A variant of :class:`EnergySystem + """A variant of :class:`EnergySystem ` specially tailored to solph. In order to work in tandem with solph, instances of this class always use @@ -48,13 +48,13 @@ def __init__(self, **kwargs): # ` for more information. from oemof.solph.groupings import GROUPINGS - kwargs['groupings'] = (GROUPINGS + kwargs.get('groupings', [])) + kwargs["groupings"] = GROUPINGS + kwargs.get("groupings", []) super().__init__(**kwargs) class Flow(on.Edge): - r""" Defines a flow between two nodes. + r"""Defines a flow between two nodes. Keyword arguments are used to set the attributes of this flow. Parameters which are handled specially are noted below. @@ -161,41 +161,54 @@ def __init__(self, **kwargs): super().__init__() - scalars = ['nominal_value', 'summed_max', 'summed_min', - 'investment', 'nonconvex', 'integer'] - sequences = ['fix', 'variable_costs', 'min', 'max'] - dictionaries = ['positive_gradient', 'negative_gradient'] - defaults = {'variable_costs': 0, - 'positive_gradient': {'ub': None, 'costs': 0}, - 'negative_gradient': {'ub': None, 'costs': 0}} - keys = [k for k in kwargs if k != 'label'] - - if 'fixed_costs' in keys: + scalars = [ + "nominal_value", + "summed_max", + "summed_min", + "investment", + "nonconvex", + "integer", + ] + sequences = ["fix", "variable_costs", "min", "max"] + dictionaries = ["positive_gradient", "negative_gradient"] + defaults = { + "variable_costs": 0, + "positive_gradient": {"ub": None, "costs": 0}, + "negative_gradient": {"ub": None, "costs": 0}, + } + keys = [k for k in kwargs if k != "label"] + + if "fixed_costs" in keys: raise AttributeError( - "The `fixed_costs` attribute has been removed" - " with v0.2!") + "The `fixed_costs` attribute has been removed" " with v0.2!" + ) - if 'actual_value' in keys: + if "actual_value" in keys: raise AttributeError( "The `actual_value` attribute has been renamed" " to `fix` with v0.4. The attribute `fixed` is" - " set to True automatically when passing `fix`.") + " set to True automatically when passing `fix`." + ) if "fixed" in keys: - msg = ("The `fixed` attribute is deprecated.\nIf you have defined " - "the `fix` attribute the flow variable will be fixed.\n" - "The `fixed` attribute does not change anything.") + msg = ( + "The `fixed` attribute is deprecated.\nIf you have defined " + "the `fix` attribute the flow variable will be fixed.\n" + "The `fixed` attribute does not change anything." + ) warn(msg, debugging.SuspiciousUsageWarning) # It is not allowed to define min or max if fix is defined. - if kwargs.get("fix") is not None and (kwargs.get("min") is not None or - kwargs.get("max") is not None): + if kwargs.get("fix") is not None and ( + kwargs.get("min") is not None or kwargs.get("max") is not None + ): raise AttributeError( - "It is not allowed to define min/max if fix is defined.") + "It is not allowed to define min/max if fix is defined." + ) # Set default value for min and max if kwargs.get("min") is None: - if 'bidirectional' in keys: + if "bidirectional" in keys: defaults["min"] = -1 else: defaults["min"] = 0 @@ -205,20 +218,30 @@ def __init__(self, **kwargs): for attribute in set(scalars + sequences + dictionaries + keys): value = kwargs.get(attribute, defaults.get(attribute)) if attribute in dictionaries: - setattr(self, attribute, {'ub': sequence(value['ub']), - 'costs': value['costs']}) + setattr( + self, + attribute, + {"ub": sequence(value["ub"]), "costs": value["costs"]}, + ) else: - setattr(self, attribute, - sequence(value) if attribute in sequences else value) + setattr( + self, + attribute, + sequence(value) if attribute in sequences else value, + ) # Checking for impossible attribute combinations if self.investment and self.nominal_value is not None: - raise ValueError("Using the investment object the nominal_value" - " has to be set to None.") + raise ValueError( + "Using the investment object the nominal_value" + " has to be set to None." + ) if self.investment and self.nonconvex: - raise ValueError("Investment flows cannot be combined with " + - "nonconvex flows!") + raise ValueError( + "Investment flows cannot be combined with " + + "nonconvex flows!" + ) class Bus(on.Bus): @@ -230,9 +253,10 @@ class Bus(on.Bus): * :py:class:`~oemof.solph.blocks.Bus` """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.balanced = kwargs.get('balanced', True) + self.balanced = kwargs.get("balanced", True) def constraint_group(self): if self.balanced: @@ -242,8 +266,8 @@ def constraint_group(self): class Sink(on.Sink): - """An object with one input flow. - """ + """An object with one input flow.""" + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) check_node_object_for_missing_attribute(self, "inputs") @@ -253,8 +277,8 @@ def constraint_group(self): class Source(on.Source): - """An object with one output flow. - """ + """An object with one output flow.""" + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) check_node_object_for_missing_attribute(self, "outputs") @@ -312,6 +336,7 @@ class Transformer(on.Transformer): The following sets, variables, constraints and objective parts are created * :py:class:`~oemof.solph.blocks.Transformer` """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -320,11 +345,12 @@ def __init__(self, *args, **kwargs): self.conversion_factors = { k: sequence(v) - for k, v in kwargs.get('conversion_factors', {}).items()} + for k, v in kwargs.get("conversion_factors", {}).items() + } missing_conversion_factor_keys = ( - (set(self.outputs) | set(self.inputs)) - - set(self.conversion_factors)) + set(self.outputs) | set(self.inputs) + ) - set(self.conversion_factors) for cf in missing_conversion_factor_keys: self.conversion_factors[cf] = sequence(1) @@ -335,8 +361,12 @@ def constraint_group(self): def check_node_object_for_missing_attribute(obj, attribute): if not getattr(obj, attribute): - msg = ("Attribute <{0}> is missing in Node <{1}> of {2}.\n" - "If this is intended and you know what you are doing you can" - "disable the SuspiciousUsageWarning globally.") - warn(msg.format(attribute, obj.label, type(obj)), - debugging.SuspiciousUsageWarning) + msg = ( + "Attribute <{0}> is missing in Node <{1}> of {2}.\n" + "If this is intended and you know what you are doing you can" + "disable the SuspiciousUsageWarning globally." + ) + warn( + msg.format(attribute, obj.label, type(obj)), + debugging.SuspiciousUsageWarning, + ) diff --git a/src/oemof/solph/options.py b/src/oemof/solph/options.py index fc60f9ead..6e4fc78ed 100644 --- a/src/oemof/solph/options.py +++ b/src/oemof/solph/options.py @@ -45,8 +45,17 @@ class Investment: :class:`oemof.solph.components.GenericInvestmentStorageBlock`. """ - def __init__(self, maximum=float('+inf'), minimum=0, ep_costs=0, - existing=0, nonconvex=False, offset=0, **kwargs): + + def __init__( + self, + maximum=float("+inf"), + minimum=0, + ep_costs=0, + existing=0, + nonconvex=False, + offset=0, + **kwargs, + ): self.maximum = maximum self.minimum = minimum @@ -65,25 +74,31 @@ def __init__(self, maximum=float('+inf'), minimum=0, ep_costs=0, def _check_invest_attributes(self): if (self.existing != 0) and (self.nonconvex is True): - e1 = ("Values for 'offset' and 'existing' are given in" - " investement attributes. \n These two options cannot be " - "considered at the same time.") + e1 = ( + "Values for 'offset' and 'existing' are given in" + " investement attributes. \n These two options cannot be " + "considered at the same time." + ) raise AttributeError(e1) def _check_invest_attributes_maximum(self): - if (self.maximum == float('+inf')) and (self.nonconvex is True): - e2 = ("Please provide an maximum investment value in case of" - " nonconvex investemnt (nonconvex=True), which is in the" - " expected magnitude." - " \nVery high maximum values (> 10e8) as maximum investment" - " limit might lead to numeric issues, so that no investment" - " is done, although it is the optimal solution!") + if (self.maximum == float("+inf")) and (self.nonconvex is True): + e2 = ( + "Please provide an maximum investment value in case of" + " nonconvex investemnt (nonconvex=True), which is in the" + " expected magnitude." + " \nVery high maximum values (> 10e8) as maximum investment" + " limit might lead to numeric issues, so that no investment" + " is done, although it is the optimal solution!" + ) raise AttributeError(e2) def _check_invest_attributes_offset(self): if (self.offset != 0) and (self.nonconvex is False): - e3 = ("If `nonconvex` is `False`, the `offset` parameter will be" - " ignored.") + e3 = ( + "If `nonconvex` is `False`, the `offset` parameter will be" + " ignored." + ) raise AttributeError(e3) @@ -120,16 +135,25 @@ class NonConvex: the maximum of both e.g. for six timesteps if a minimum downtime of six timesteps is defined in addition to a four timestep minimum uptime. """ + def __init__(self, **kwargs): - scalars = ['minimum_uptime', 'minimum_downtime', 'initial_status', - 'maximum_startups', 'maximum_shutdowns'] - sequences = ['startup_costs', 'shutdown_costs', 'activity_costs'] - defaults = {'initial_status': 0} + scalars = [ + "minimum_uptime", + "minimum_downtime", + "initial_status", + "maximum_startups", + "maximum_shutdowns", + ] + sequences = ["startup_costs", "shutdown_costs", "activity_costs"] + defaults = {"initial_status": 0} for attribute in set(scalars + sequences + list(kwargs)): value = kwargs.get(attribute, defaults.get(attribute)) - setattr(self, attribute, - sequence(value) if attribute in sequences else value) + setattr( + self, + attribute, + sequence(value) if attribute in sequences else value, + ) self._max_up_down = None diff --git a/src/oemof/solph/plumbing.py b/src/oemof/solph/plumbing.py index 59a27f8e2..dcfb31432 100644 --- a/src/oemof/solph/plumbing.py +++ b/src/oemof/solph/plumbing.py @@ -17,7 +17,7 @@ def sequence(iterable_or_scalar): - """ Tests if an object is iterable (except string) or scalar and returns + """Tests if an object is iterable (except string) or scalar and returns a the original sequence if object is an iterable and a 'emulated' sequence object of class _Sequence if object is a scalar or string. @@ -40,15 +40,16 @@ def sequence(iterable_or_scalar): [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] """ - if (isinstance(iterable_or_scalar, abc.Iterable) and not - isinstance(iterable_or_scalar, str)): + if isinstance(iterable_or_scalar, abc.Iterable) and not isinstance( + iterable_or_scalar, str + ): return iterable_or_scalar else: return _Sequence(default=iterable_or_scalar) class _Sequence(UserList): - """ Emulates a list whose length is not known in advance. + """Emulates a list whose length is not known in advance. Parameters ---------- @@ -74,6 +75,7 @@ class _Sequence(UserList): """ + def __init__(self, *args, **kwargs): self.default = kwargs["default"] self.default_changed = False diff --git a/src/oemof/solph/processing.py b/src/oemof/solph/processing.py index b838ca6cc..679a9ae7d 100644 --- a/src/oemof/solph/processing.py +++ b/src/oemof/solph/processing.py @@ -36,7 +36,7 @@ def get_tuple(x): if isinstance(i, tuple): return i elif issubclass(type(i), Node): - return i, + return (i,) # for standalone variables, x is used as identifying tuple if isinstance(x, tuple): @@ -78,38 +78,36 @@ def create_dataframe(om): components or the timesteps. """ # get all pyomo variables including their block - block_vars = list(set([ - bv.parent_component() for bv in om.component_data_objects(Var)])) + block_vars = list( + set([bv.parent_component() for bv in om.component_data_objects(Var)]) + ) var_dict = {} for bv in block_vars: # Drop the auxiliary variables introduced by pyomo's Piecewise parent_component = bv.parent_block().parent_component() if not isinstance(parent_component, IndexedPiecewise): - for i in getattr(bv, '_index'): - key = ( - str(bv).split('.')[0], - str(bv).split('.')[-1], - i) + for i in getattr(bv, "_index"): + key = (str(bv).split(".")[0], str(bv).split(".")[-1], i) value = bv[i].value var_dict[key] = value # use this to create a pandas dataframe - df = pd.DataFrame(list(var_dict.items()), columns=['pyomo_tuple', 'value']) - df['variable_name'] = df['pyomo_tuple'].str[1] + df = pd.DataFrame(list(var_dict.items()), columns=["pyomo_tuple", "value"]) + df["variable_name"] = df["pyomo_tuple"].str[1] # adapt the dataframe by separating tuple data into columns depending # on which dimension the variable/parameter has (scalar/sequence). # columns for the oemof tuple and timestep are created - df['oemof_tuple'] = df['pyomo_tuple'].map(get_tuple) - df = df[df['oemof_tuple'].map(lambda x: x is not None)] - df['timestep'] = df['oemof_tuple'].map(get_timestep) - df['oemof_tuple'] = df['oemof_tuple'].map(remove_timestep) + df["oemof_tuple"] = df["pyomo_tuple"].map(get_tuple) + df = df[df["oemof_tuple"].map(lambda x: x is not None)] + df["timestep"] = df["oemof_tuple"].map(get_timestep) + df["oemof_tuple"] = df["oemof_tuple"].map(remove_timestep) # order the data by oemof tuple and timestep - df = df.sort_values(['oemof_tuple', 'timestep'], ascending=[True, True]) + df = df.sort_values(["oemof_tuple", "timestep"], ascending=[True, True]) # drop empty decision variables - df = df.dropna(subset=['value']) + df = df.dropna(subset=["value"]) return df @@ -127,32 +125,40 @@ def results(om): df = create_dataframe(om) # create a dict of dataframes keyed by oemof tuples - df_dict = {k if len(k) > 1 else (k[0], None): - v[['timestep', 'variable_name', 'value']] - for k, v in df.groupby('oemof_tuple')} + df_dict = { + k + if len(k) > 1 + else (k[0], None): v[["timestep", "variable_name", "value"]] + for k, v in df.groupby("oemof_tuple") + } # create final result dictionary by splitting up the dataframes in the # dataframe dict into a series for scalar data and dataframe for sequences result = {} for k in df_dict: - df_dict[k].set_index('timestep', inplace=True) - df_dict[k] = df_dict[k].pivot(columns='variable_name', values='value') + df_dict[k].set_index("timestep", inplace=True) + df_dict[k] = df_dict[k].pivot(columns="variable_name", values="value") try: df_dict[k].index = om.es.timeindex except ValueError as e: - msg = ("\nFlow: {0}-{1}. This could be caused by NaN-values in" - " your input data.") - raise type(e)(str(e) + msg.format(k[0].label, k[1].label) - ).with_traceback(sys.exc_info()[2]) + msg = ( + "\nFlow: {0}-{1}. This could be caused by NaN-values in" + " your input data." + ) + raise type(e)( + str(e) + msg.format(k[0].label, k[1].label) + ).with_traceback(sys.exc_info()[2]) try: condition = df_dict[k].isnull().any() scalars = df_dict[k].loc[:, condition].dropna().iloc[0] sequences = df_dict[k].loc[:, ~condition] - result[k] = {'scalars': scalars, 'sequences': sequences} + result[k] = {"scalars": scalars, "sequences": sequences} except IndexError: - error_message = ('Cannot access index on result data. ' + - 'Did the optimization terminate' + - ' without errors?') + error_message = ( + "Cannot access index on result data. " + + "Did the optimization terminate" + + " without errors?" + ) raise IndexError(error_message) # add dual variables for bus constraints @@ -160,12 +166,14 @@ def results(om): grouped = groupby(sorted(om.Bus.balance.iterkeys()), lambda p: p[0]) for bus, timesteps in grouped: duals = [om.dual[om.Bus.balance[bus, t]] for _, t in timesteps] - df = pd.DataFrame({'duals': duals}, index=om.es.timeindex) + df = pd.DataFrame({"duals": duals}, index=om.es.timeindex) if (bus, None) not in result.keys(): result[(bus, None)] = { - 'sequences': df, 'scalars': pd.Series(dtype=float)} + "sequences": df, + "scalars": pd.Series(dtype=float), + } else: - result[(bus, None)]['sequences']['duals'] = duals + result[(bus, None)]["sequences"]["duals"] = duals return result @@ -182,14 +190,14 @@ def convert_keys_to_strings(result, keep_none_type=False): converted = { tuple([str(e) if e is not None else None for e in k]) if isinstance(k, tuple) - else str(k) if k is not None else None: v + else str(k) + if k is not None + else None: v for k, v in result.items() } else: converted = { - tuple(map(str, k)) - if isinstance(k, tuple) - else str(k): v + tuple(map(str, k)) if isinstance(k, tuple) else str(k): v for k, v in result.items() } return converted @@ -212,24 +220,24 @@ def meta_results(om, undefined=False): ------- dict """ - meta_res = {'objective': om.objective()} + meta_res = {"objective": om.objective()} - for k1 in ['Problem', 'Solver']: + for k1 in ["Problem", "Solver"]: k1 = k1.lower() meta_res[k1] = {} for k2, v2 in om.es.results[k1][0].items(): try: - if str(om.es.results[k1][0][k2]) == '': + if str(om.es.results[k1][0][k2]) == "": if undefined: - meta_res[k1][k2] = str( - om.es.results[k1][0][k2]) + meta_res[k1][k2] = str(om.es.results[k1][0][k2]) else: meta_res[k1][k2] = om.es.results[k1][0][k2] except TypeError: if undefined: msg = "Cannot fetch meta results of type {0}" meta_res[k1][k2] = msg.format( - type(om.es.results[k1][0][k2])) + type(om.es.results[k1][0][k2]) + ) return meta_res @@ -249,44 +257,57 @@ def __separate_attrs(system, get_flows=False, exclude_none=True): ------- dict """ - def detect_scalars_and_sequences(com): - com_data = {'scalars': {}, 'sequences': {}} - exclusions = ('__', '_', 'registry', 'inputs', 'outputs', - 'register', - 'Label', 'from_object', 'input', 'output', - 'constraint_group') - attrs = [i for i in dir(com) - if not (callable(i) or i.startswith(exclusions))] + def detect_scalars_and_sequences(com): + com_data = {"scalars": {}, "sequences": {}} + + exclusions = ( + "__", + "_", + "registry", + "inputs", + "outputs", + "register", + "Label", + "from_object", + "input", + "output", + "constraint_group", + ) + attrs = [ + i + for i in dir(com) + if not (callable(i) or i.startswith(exclusions)) + ] for a in attrs: attr_value = getattr(com, a) # Iterate trough investment and add scalars and sequences with # "investment" prefix to component data: - if attr_value.__class__.__name__ == 'Investment': + if attr_value.__class__.__name__ == "Investment": invest_data = detect_scalars_and_sequences(attr_value) - com_data['scalars'].update( + com_data["scalars"].update( { - 'investment_' + str(k): v - for k, v in invest_data['scalars'].items() - } + "investment_" + str(k): v + for k, v in invest_data["scalars"].items() + } ) - com_data['sequences'].update( + com_data["sequences"].update( { - 'investment_' + str(k): v - for k, v in invest_data['sequences'].items() + "investment_" + str(k): v + for k, v in invest_data["sequences"].items() } ) continue if isinstance(attr_value, str): - com_data['scalars'][a] = attr_value + com_data["scalars"][a] = attr_value continue # If the label is a tuple it is iterable, therefore it should be # converted to a string. Otherwise it will be a sequence. - if a == 'label': + if a == "label": attr_value = str(attr_value) # check if attribute is iterable @@ -294,53 +315,50 @@ def detect_scalars_and_sequences(com): # in-python-how-do-i-determine-if-an-object-is-iterable try: _ = (e for e in attr_value) - com_data['sequences'][a] = attr_value + com_data["sequences"][a] = attr_value except TypeError: - com_data['scalars'][a] = attr_value + com_data["scalars"][a] = attr_value - com_data['sequences'] = flatten(com_data['sequences']) + com_data["sequences"] = flatten(com_data["sequences"]) move_undetected_scalars(com_data) if exclude_none: remove_nones(com_data) com_data = { - 'scalars': pd.Series(com_data['scalars']), - 'sequences': pd.DataFrame(com_data['sequences']) + "scalars": pd.Series(com_data["scalars"]), + "sequences": pd.DataFrame(com_data["sequences"]), } return com_data def move_undetected_scalars(com): - for ckey, value in list(com['sequences'].items()): + for ckey, value in list(com["sequences"].items()): if isinstance(value, str): - com['scalars'][ckey] = value - del com['sequences'][ckey] + com["scalars"][ckey] = value + del com["sequences"][ckey] continue try: _ = (e for e in value) except TypeError: - com['scalars'][ckey] = value - del com['sequences'][ckey] + com["scalars"][ckey] = value + del com["sequences"][ckey] else: try: if not value.default_changed: - com['scalars'][ckey] = value.default - del com['sequences'][ckey] + com["scalars"][ckey] = value.default + del com["sequences"][ckey] except AttributeError: pass def remove_nones(com): - for ckey, value in list(com['scalars'].items()): + for ckey, value in list(com["scalars"].items()): if value is None: - del com['scalars'][ckey] - for ckey, value in list(com['sequences'].items()): - if ( - len(value) == 0 or - value[0] is None - ): - del com['sequences'][ckey] + del com["scalars"][ckey] + for ckey, value in list(com["sequences"].items()): + if len(value) == 0 or value[0] is None: + del com["sequences"][ckey] # Check if system is es or om: - if system.__class__.__name__ == 'EnergySystem': + if system.__class__.__name__ == "EnergySystem": components = system.flows() if get_flows else system.nodes else: components = system.flows if get_flows else system.es.nodes diff --git a/src/oemof/solph/views.py b/src/oemof/solph/views.py index e4acb17a8..afeabc09c 100644 --- a/src/oemof/solph/views.py +++ b/src/oemof/solph/views.py @@ -21,7 +21,7 @@ from oemof.solph.processing import convert_keys_to_strings -NONE_REPLACEMENT_STR = '_NONE_' +NONE_REPLACEMENT_STR = "_NONE_" def node(results, node, multiindex=False, keep_none_type=False): @@ -32,18 +32,20 @@ def node(results, node, multiindex=False, keep_none_type=False): Results are written into a dictionary which is keyed by 'scalars' and 'sequences' holding respective data in a pandas Series and DataFrame. """ + def replace_none(col_list, reverse=False): replacement = ( - (None, NONE_REPLACEMENT_STR) if reverse else - (NONE_REPLACEMENT_STR, None) + (None, NONE_REPLACEMENT_STR) + if reverse + else (NONE_REPLACEMENT_STR, None) ) changed_col_list = [ ( ( replacement[0] if n1 is replacement[1] else n1, - replacement[0] if n2 is replacement[1] else n2 + replacement[0] if n2 is replacement[1] else n2, ), - f + f, ) for (n1, n2), f in col_list ] @@ -56,73 +58,92 @@ def replace_none(col_list, reverse=False): filtered = {} # create a series with tuples as index labels for scalars - scalars = {k: v['scalars'] for k, v in results.items() - if node in k and not v['scalars'].empty} + scalars = { + k: v["scalars"] + for k, v in results.items() + if node in k and not v["scalars"].empty + } if scalars: # aggregate data - filtered['scalars'] = pd.concat(scalars.values(), axis=0) + filtered["scalars"] = pd.concat(scalars.values(), axis=0) # assign index values - idx = {k: [c for c in v['scalars'].index] - for k, v in results.items() - if node in k and not v['scalars'].empty} + idx = { + k: [c for c in v["scalars"].index] + for k, v in results.items() + if node in k and not v["scalars"].empty + } idx = [tuple((k, m) for m in v) for k, v in idx.items()] idx = [i for sublist in idx for i in sublist] - filtered['scalars'].index = idx + filtered["scalars"].index = idx # Sort index # (if Nones are present, they have to be replaced while sorting) if keep_none_type: - filtered['scalars'].index = replace_none( - filtered['scalars'].index.tolist()) - filtered['scalars'].sort_index(axis=0, inplace=True) + filtered["scalars"].index = replace_none( + filtered["scalars"].index.tolist() + ) + filtered["scalars"].sort_index(axis=0, inplace=True) if keep_none_type: - filtered['scalars'].index = replace_none( - filtered['scalars'].index.tolist(), True) + filtered["scalars"].index = replace_none( + filtered["scalars"].index.tolist(), True + ) if multiindex: idx = pd.MultiIndex.from_tuples( - [tuple([row[0][0], row[0][1], row[1]]) - for row in filtered['scalars'].index]) - idx.set_names(['from', 'to', 'type'], inplace=True) - filtered['scalars'].index = idx + [ + tuple([row[0][0], row[0][1], row[1]]) + for row in filtered["scalars"].index + ] + ) + idx.set_names(["from", "to", "type"], inplace=True) + filtered["scalars"].index = idx # create a dataframe with tuples as column labels for sequences - sequences = {k: v['sequences'] for k, v in results.items() - if node in k and not v['sequences'].empty} + sequences = { + k: v["sequences"] + for k, v in results.items() + if node in k and not v["sequences"].empty + } if sequences: # aggregate data - filtered['sequences'] = pd.concat(sequences.values(), axis=1) + filtered["sequences"] = pd.concat(sequences.values(), axis=1) # assign column names - cols = {k: [c for c in v['sequences'].columns] - for k, v in results.items() - if node in k and not v['sequences'].empty} + cols = { + k: [c for c in v["sequences"].columns] + for k, v in results.items() + if node in k and not v["sequences"].empty + } cols = [tuple((k, m) for m in v) for k, v in cols.items()] cols = [c for sublist in cols for c in sublist] - filtered['sequences'].columns = replace_none(cols) - filtered['sequences'].sort_index(axis=1, inplace=True) - filtered['sequences'].columns = replace_none( - filtered['sequences'].columns, True) + filtered["sequences"].columns = replace_none(cols) + filtered["sequences"].sort_index(axis=1, inplace=True) + filtered["sequences"].columns = replace_none( + filtered["sequences"].columns, True + ) if multiindex: idx = pd.MultiIndex.from_tuples( - [tuple([col[0][0], col[0][1], col[1]]) - for col in filtered['sequences'].columns]) - idx.set_names(['from', 'to', 'type'], inplace=True) - filtered['sequences'].columns = idx + [ + tuple([col[0][0], col[0][1], col[1]]) + for col in filtered["sequences"].columns + ] + ) + idx.set_names(["from", "to", "type"], inplace=True) + filtered["sequences"].columns = idx return filtered class NodeOption(str, Enum): - All = 'all' - HasOutputs = 'has_outputs' - HasInputs = 'has_inputs' - HasOnlyOutputs = 'has_only_outputs' - HasOnlyInputs = 'has_only_inputs' + All = "all" + HasOutputs = "has_outputs" + HasInputs = "has_inputs" + HasOnlyOutputs = "has_only_outputs" + HasOnlyInputs = "has_only_inputs" def filter_nodes(results, option=NodeOption.All, exclude_busses=False): - """ Get set of nodes from results-dict for given node option. + """Get set of nodes from results-dict for given node option. This function filters nodes from results for special needs. At the moment, the following options are available: @@ -167,7 +188,7 @@ def filter_nodes(results, option=NodeOption.All, exclude_busses=False): raise ValueError('Invalid node option "' + str(option) + '"') if exclude_busses: - return {n for n in nodes if not n.__class__.__name__ == 'Bus'} + return {n for n in nodes if not n.__class__.__name__ == "Bus"} else: return nodes @@ -213,21 +234,25 @@ def node_weight_by_type(results, node_type): views.node_weight_by_type(m.results(), node_type=solph.GenericStorage) """ - group = {k: v['sequences'] for k, v in results.items() - if isinstance(k[0], node_type) and k[1] is None} + group = { + k: v["sequences"] + for k, v in results.items() + if isinstance(k[0], node_type) and k[1] is None + } if not group: - logging.error('No node weights for nodes of type `{}`'.format( - node_type)) + logging.error( + "No node weights for nodes of type `{}`".format(node_type) + ) return None else: - df = convert_to_multiindex(group, - index_names=['node', 'to', 'weight_type'], - droplevel=[1]) + df = convert_to_multiindex( + group, index_names=["node", "to", "weight_type"], droplevel=[1] + ) return df def node_input_by_type(results, node_type, droplevel=None): - """ Gets all inputs for all nodes of the type `node_type` and returns + """Gets all inputs for all nodes of the type `node_type` and returns a dataframe. Parameters @@ -250,11 +275,14 @@ def node_input_by_type(results, node_type, droplevel=None): if droplevel is None: droplevel = [] - group = {k: v['sequences'] for k, v in results.items() - if isinstance(k[1], node_type) and k[0] is not None} + group = { + k: v["sequences"] + for k, v in results.items() + if isinstance(k[1], node_type) and k[0] is not None + } if not group: - logging.info('No nodes of type `{}`'.format(node_type)) + logging.info("No nodes of type `{}`".format(node_type)) return None else: df = convert_to_multiindex(group, droplevel=droplevel) @@ -262,7 +290,7 @@ def node_input_by_type(results, node_type, droplevel=None): def node_output_by_type(results, node_type, droplevel=None): - """ Gets all outputs for all nodes of the type `node_type` and returns + """Gets all outputs for all nodes of the type `node_type` and returns a dataframe. Parameters @@ -284,11 +312,14 @@ def node_output_by_type(results, node_type, droplevel=None): """ if droplevel is None: droplevel = [] - group = {k: v['sequences'] for k, v in results.items() - if isinstance(k[0], node_type) and k[1] is not None} + group = { + k: v["sequences"] + for k, v in results.items() + if isinstance(k[0], node_type) and k[1] is not None + } if not group: - logging.info('No nodes of type `{}`'.format(node_type)) + logging.info("No nodes of type `{}`".format(node_type)) return None else: df = convert_to_multiindex(group, droplevel=droplevel) @@ -296,7 +327,7 @@ def node_output_by_type(results, node_type, droplevel=None): def net_storage_flow(results, node_type): - """ Calculates the net storage flow for storage models that have one + """Calculates the net storage flow for storage models that have one input edge and one output edge both with flows within the domain of non-negative reals. @@ -322,49 +353,55 @@ def net_storage_flow(results, node_type): views.net_storage_flow(m.results(), node_type=solph.GenericStorage) """ - group = {k: v['sequences'] for k, v in results.items() - if isinstance(k[0], node_type) or isinstance(k[1], node_type)} + group = { + k: v["sequences"] + for k, v in results.items() + if isinstance(k[0], node_type) or isinstance(k[1], node_type) + } if not group: - logging.info( - 'No nodes of type `{}`'.format(node_type)) + logging.info("No nodes of type `{}`".format(node_type)) return None df = convert_to_multiindex(group) - if 'storage_content' not in df.columns.get_level_values(2).unique(): + if "storage_content" not in df.columns.get_level_values(2).unique(): return None - x = df.xs('storage_content', axis=1, level=2).columns.values + x = df.xs("storage_content", axis=1, level=2).columns.values labels = [s for s, t in x] dataframes = [] for lb in labels: subset = df.groupby( - lambda x1: (lambda fr, to, ty: - 'output' if (fr == lb and ty == 'flow') else - 'input' if (to == lb and ty == 'flow') else - 'level' if (fr == lb and ty != 'flow') else - None)(*x1), - axis=1 + lambda x1: ( + lambda fr, to, ty: "output" + if (fr == lb and ty == "flow") + else "input" + if (to == lb and ty == "flow") + else "level" + if (fr == lb and ty != "flow") + else None + )(*x1), + axis=1, ).sum() - subset['net_flow'] = subset['output'] - subset['input'] + subset["net_flow"] = subset["output"] - subset["input"] subset.columns = pd.MultiIndex.from_product( - [[lb], - [o for o in lb.outputs], - subset.columns]) + [[lb], [o for o in lb.outputs], subset.columns] + ) dataframes.append( - subset.loc[:, (slice(None), slice(None), 'net_flow')]) + subset.loc[:, (slice(None), slice(None), "net_flow")] + ) return pd.concat(dataframes, axis=1) def convert_to_multiindex(group, index_names=None, droplevel=None): - """ Convert dict to pandas DataFrame with multiindex + """Convert dict to pandas DataFrame with multiindex Parameters ---------- @@ -376,21 +413,19 @@ def convert_to_multiindex(group, index_names=None, droplevel=None): List containing levels to be dropped from the dataframe """ if index_names is None: - index_names = ['from', 'to', 'type'] + index_names = ["from", "to", "type"] if droplevel is None: droplevel = [] - sorted_group = OrderedDict( - (k, group[k]) for k in sorted(group)) + sorted_group = OrderedDict((k, group[k]) for k in sorted(group)) df = pd.concat(sorted_group.values(), axis=1) - cols = OrderedDict((k, v.columns) - for k, v in sorted_group.items()) + cols = OrderedDict((k, v.columns) for k, v in sorted_group.items()) cols = [tuple((k, m) for m in v) for k, v in cols.items()] cols = [c for sublist in cols for c in sublist] idx = pd.MultiIndex.from_tuples( - [tuple([col[0][0], col[0][1], col[1]]) - for col in cols]) + [tuple([col[0][0], col[0][1], col[1]]) for col in cols] + ) idx.set_names(index_names, inplace=True) df.columns = idx df.columns = df.columns.droplevel(droplevel) diff --git a/tests/constraint_tests.py b/tests/constraint_tests.py index d518715d3..f23ddb625 100644 --- a/tests/constraint_tests.py +++ b/tests/constraint_tests.py @@ -27,22 +27,25 @@ class TestsConstraint: @classmethod def setup_class(cls): - cls.objective_pattern = re.compile(r'^objective.*(?=s\.t\.)', - re.DOTALL | re.MULTILINE) + cls.objective_pattern = re.compile( + r"^objective.*(?=s\.t\.)", re.DOTALL | re.MULTILINE + ) - cls.date_time_index = pd.date_range('1/1/2012', periods=3, freq='H') + cls.date_time_index = pd.date_range("1/1/2012", periods=3, freq="H") - cls.tmppath = solph.helpers.extend_basic_path('tmp') + cls.tmppath = solph.helpers.extend_basic_path("tmp") logging.info(cls.tmppath) def setup(self): - self.energysystem = solph.EnergySystem(groupings=solph.GROUPINGS, - timeindex=self.date_time_index) + self.energysystem = solph.EnergySystem( + groupings=solph.GROUPINGS, timeindex=self.date_time_index + ) Node.registry = self.energysystem def get_om(self): - return solph.Model(self.energysystem, - timeindex=self.energysystem.timeindex) + return solph.Model( + self.energysystem, timeindex=self.energysystem.timeindex + ) def compare_lp_files(self, filename, ignored=None, my_om=None): r"""Compare lp-files to check constraints generated within solph. @@ -57,240 +60,294 @@ def compare_lp_files(self, filename, ignored=None, my_om=None): om = self.get_om() else: om = my_om - tmp_filename = filename.replace('.lp', '') + '_tmp.lp' + tmp_filename = filename.replace(".lp", "") + "_tmp.lp" new_filename = ospath.join(self.tmppath, tmp_filename) - om.write(new_filename, io_options={'symbolic_solver_labels': True}) + om.write(new_filename, io_options={"symbolic_solver_labels": True}) logging.info("Comparing with file: {0}".format(filename)) with open(ospath.join(self.tmppath, tmp_filename)) as generated_file: - with open(ospath.join(ospath.dirname(ospath.realpath(__file__)), - "lp_files", - filename)) as expected_file: + with open( + ospath.join( + ospath.dirname(ospath.realpath(__file__)), + "lp_files", + filename, + ) + ) as expected_file: def chop_trailing_whitespace(lines): - return [re.sub(r'\s*$', '', ln) for ln in lines] + return [re.sub(r"\s*$", "", ln) for ln in lines] def remove(pattern, lines): if not pattern: return lines - return re.subn(pattern, "", - "\n".join(lines))[0].split("\n") - - expected = remove(ignored, - chop_trailing_whitespace( - expected_file.readlines())) - generated = remove(ignored, - chop_trailing_whitespace( - generated_file.readlines())) + return re.subn(pattern, "", "\n".join(lines))[0].split( + "\n" + ) + + expected = remove( + ignored, + chop_trailing_whitespace(expected_file.readlines()), + ) + generated = remove( + ignored, + chop_trailing_whitespace(generated_file.readlines()), + ) def normalize_to_positive_results(lines): negative_result_indices = [ - n for n, line in enumerate(lines) - if re.match("^= -", line)] + n + for n, line in enumerate(lines) + if re.match("^= -", line) + ] equation_start_indices = [ - [n for n in reversed(range(0, nri)) - if re.match('.*:$', lines[n])][0]+1 - for nri in negative_result_indices] + [ + n + for n in reversed(range(0, nri)) + if re.match(".*:$", lines[n]) + ][0] + + 1 + for nri in negative_result_indices + ] for (start, end) in zip( - equation_start_indices, - negative_result_indices): + equation_start_indices, negative_result_indices + ): for n in range(start, end): lines[n] = ( - '-' - if lines[n] and lines[n][0] == '+' - else '+' + "-" + if lines[n] and lines[n][0] == "+" + else "+" if lines[n] - else lines[n]) + lines[n][1:] - lines[end] = '= ' + lines[end][3:] + else lines[n] + ) + lines[n][1:] + lines[end] = "= " + lines[end][3:] return lines expected = normalize_to_positive_results(expected) generated = normalize_to_positive_results(generated) - eq_(generated, expected, - "Failed matching expected with generated lp file:\n" + - "\n".join(unified_diff(expected, generated, - fromfile=ospath.relpath( - expected_file.name), - tofile=ospath.basename( - generated_file.name), - lineterm=""))) + eq_( + generated, + expected, + "Failed matching expected with generated lp file:\n" + + "\n".join( + unified_diff( + expected, + generated, + fromfile=ospath.relpath(expected_file.name), + tofile=ospath.basename(generated_file.name), + lineterm="", + ) + ), + ) def test_linear_transformer(self): - """Constraint test of a Transformer without Investment. - """ - bgas = solph.Bus(label='gas') + """Constraint test of a Transformer without Investment.""" + bgas = solph.Bus(label="gas") - bel = solph.Bus(label='electricity') + bel = solph.Bus(label="electricity") solph.Transformer( - label='powerplantGas', + label="powerplantGas", inputs={bgas: solph.Flow()}, outputs={bel: solph.Flow(nominal_value=10e10, variable_costs=50)}, - conversion_factors={bel: 0.58}) + conversion_factors={bel: 0.58}, + ) - self.compare_lp_files('linear_transformer.lp') + self.compare_lp_files("linear_transformer.lp") def test_linear_transformer_invest(self): - """Constraint test of a Transformer with Investment. - """ + """Constraint test of a Transformer with Investment.""" - bgas = solph.Bus(label='gas') + bgas = solph.Bus(label="gas") - bel = solph.Bus(label='electricity') + bel = solph.Bus(label="electricity") solph.Transformer( - label='powerplant_gas', + label="powerplant_gas", inputs={bgas: solph.Flow()}, - outputs={bel: solph.Flow(variable_costs=50, - investment=solph.Investment(maximum=1000, - ep_costs=20)) - }, - conversion_factors={bel: 0.58}) + outputs={ + bel: solph.Flow( + variable_costs=50, + investment=solph.Investment(maximum=1000, ep_costs=20), + ) + }, + conversion_factors={bel: 0.58}, + ) - self.compare_lp_files('linear_transformer_invest.lp') + self.compare_lp_files("linear_transformer_invest.lp") def test_max_source_min_sink(self): - """ - """ - bel = solph.Bus(label='electricityBus') + """""" + bel = solph.Bus(label="electricityBus") - solph.Source(label='wind', outputs={ - bel: solph.Flow(nominal_value=54, max=(.85, .95, .61))}) + solph.Source( + label="wind", + outputs={ + bel: solph.Flow(nominal_value=54, max=(0.85, 0.95, 0.61)) + }, + ) - solph.Sink(label='minDemand', inputs={bel: solph.Flow( - nominal_value=54, min=(.84, .94, .59), variable_costs=14)}) + solph.Sink( + label="minDemand", + inputs={ + bel: solph.Flow( + nominal_value=54, min=(0.84, 0.94, 0.59), variable_costs=14 + ) + }, + ) - self.compare_lp_files('max_source_min_sink.lp') + self.compare_lp_files("max_source_min_sink.lp") def test_fixed_source_variable_sink(self): - """Constraint test with a fixed source and a variable sink. - """ + """Constraint test with a fixed source and a variable sink.""" - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") - solph.Source(label='wind', outputs={bel: solph.Flow( - fix=[.43, .72, .29], nominal_value=10e5)}) + solph.Source( + label="wind", + outputs={ + bel: solph.Flow(fix=[0.43, 0.72, 0.29], nominal_value=10e5) + }, + ) - solph.Sink(label='excess', inputs={bel: solph.Flow(variable_costs=40)}) + solph.Sink(label="excess", inputs={bel: solph.Flow(variable_costs=40)}) - self.compare_lp_files('fixed_source_variable_sink.lp') + self.compare_lp_files("fixed_source_variable_sink.lp") def test_nominal_value_to_zero(self): - """If the nominal value is set to zero nothing should happen. - """ - bel = solph.Bus(label='electricityBus') + """If the nominal value is set to zero nothing should happen.""" + bel = solph.Bus(label="electricityBus") - solph.Source(label='s1', outputs={bel: solph.Flow(nominal_value=0)}) - self.compare_lp_files('nominal_value_to_zero.lp') + solph.Source(label="s1", outputs={bel: solph.Flow(nominal_value=0)}) + self.compare_lp_files("nominal_value_to_zero.lp") def test_fixed_source_invest_sink(self): - """ Wrong constraints for fixed source + invest sink w. `summed_max`. - """ + """Wrong constraints for fixed source + invest sink w. `summed_max`.""" - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") - solph.Source(label='wind', outputs={bel: solph.Flow( - fix=[12, 16, 14], nominal_value=1000000)}) + solph.Source( + label="wind", + outputs={bel: solph.Flow(fix=[12, 16, 14], nominal_value=1000000)}, + ) - solph.Sink(label='excess', inputs={bel: solph.Flow( - summed_max=2.3, variable_costs=25, max=0.8, - investment=solph.Investment(ep_costs=500, maximum=10e5, - existing=50))}) + solph.Sink( + label="excess", + inputs={ + bel: solph.Flow( + summed_max=2.3, + variable_costs=25, + max=0.8, + investment=solph.Investment( + ep_costs=500, maximum=10e5, existing=50 + ), + ) + }, + ) - self.compare_lp_files('fixed_source_invest_sink.lp') + self.compare_lp_files("fixed_source_invest_sink.lp") def test_invest_source_fixed_sink(self): - """Constraint test with a fixed sink and a dispatch invest source. - """ + """Constraint test with a fixed sink and a dispatch invest source.""" - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") - solph.Source(label='pv', outputs={bel: solph.Flow( - max=[45, 83, 65], variable_costs=13, - investment=solph.Investment(ep_costs=123))}) + solph.Source( + label="pv", + outputs={ + bel: solph.Flow( + max=[45, 83, 65], + variable_costs=13, + investment=solph.Investment(ep_costs=123), + ) + }, + ) - solph.Sink(label='excess', inputs={bel: solph.Flow( - fix=[.5, .8, .3], nominal_value=10e4)}) + solph.Sink( + label="excess", + inputs={bel: solph.Flow(fix=[0.5, 0.8, 0.3], nominal_value=10e4)}, + ) - self.compare_lp_files('invest_source_fixed_sink.lp') + self.compare_lp_files("invest_source_fixed_sink.lp") def test_storage(self): - """ - """ - bel = solph.Bus(label='electricityBus') + """""" + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage_no_invest', + label="storage_no_invest", inputs={bel: solph.Flow(nominal_value=16667, variable_costs=56)}, outputs={bel: solph.Flow(nominal_value=16667, variable_costs=24)}, nominal_storage_capacity=10e4, loss_rate=0.13, inflow_conversion_factor=0.97, outflow_conversion_factor=0.86, - initial_storage_level=0.4) + initial_storage_level=0.4, + ) - self.compare_lp_files('storage.lp') + self.compare_lp_files("storage.lp") def test_storage_invest_1(self): """All invest variables are coupled. The invest variables of the Flows will be created during the initialisation of the storage e.g. battery """ - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage1', + label="storage1", inputs={bel: solph.Flow(variable_costs=56)}, outputs={bel: solph.Flow(variable_costs=24)}, nominal_storage_capacity=None, loss_rate=0.13, max_storage_level=0.9, min_storage_level=0.1, - invest_relation_input_capacity=1/6, - invest_relation_output_capacity=1/6, + invest_relation_input_capacity=1 / 6, + invest_relation_output_capacity=1 / 6, inflow_conversion_factor=0.97, outflow_conversion_factor=0.86, - investment=solph.Investment(ep_costs=145, maximum=234)) + investment=solph.Investment(ep_costs=145, maximum=234), + ) - self.compare_lp_files('storage_invest_1.lp') + self.compare_lp_files("storage_invest_1.lp") def test_storage_invest_2(self): - """All can be free extended to their own cost. - """ - bel = solph.Bus(label='electricityBus') + """All can be free extended to their own cost.""" + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage2', + label="storage2", inputs={bel: solph.Flow(investment=solph.Investment(ep_costs=99))}, outputs={bel: solph.Flow(investment=solph.Investment(ep_costs=9))}, investment=solph.Investment(ep_costs=145), - initial_storage_level=0.5) - self.compare_lp_files('storage_invest_2.lp') + initial_storage_level=0.5, + ) + self.compare_lp_files("storage_invest_2.lp") def test_storage_invest_3(self): """The storage capacity is fixed, but the Flows can be extended. e.g. PHES with a fixed basin but the pump and the turbine can be adapted """ - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage3', + label="storage3", inputs={bel: solph.Flow(investment=solph.Investment(ep_costs=99))}, outputs={bel: solph.Flow(investment=solph.Investment(ep_costs=9))}, - nominal_storage_capacity=5000) - self.compare_lp_files('storage_invest_3.lp') + nominal_storage_capacity=5000, + ) + self.compare_lp_files("storage_invest_3.lp") def test_storage_invest_4(self): - """Only the storage capacity can be extended. - """ - bel = solph.Bus(label='electricityBus') + """Only the storage capacity can be extended.""" + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage4', + label="storage4", inputs={bel: solph.Flow(nominal_value=80)}, outputs={bel: solph.Flow(nominal_value=100)}, - investment=solph.Investment(ep_costs=145, maximum=500)) - self.compare_lp_files('storage_invest_4.lp') + investment=solph.Investment(ep_costs=145, maximum=500), + ) + self.compare_lp_files("storage_invest_4.lp") def test_storage_invest_5(self): """The storage capacity is fixed, but the Flows can be extended. @@ -298,70 +355,83 @@ def test_storage_invest_5(self): adapted. The installed capacity of the pump is 10 % bigger than the the capacity of the turbine due to 'invest_relation_input_output=1.1'. """ - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage5', - inputs={bel: solph.Flow(investment=solph.Investment( - ep_costs=99, existing=110))}, - outputs={bel: solph.Flow(investment=solph.Investment( - existing=100))}, + label="storage5", + inputs={ + bel: solph.Flow( + investment=solph.Investment(ep_costs=99, existing=110) + ) + }, + outputs={ + bel: solph.Flow(investment=solph.Investment(existing=100)) + }, invest_relation_input_output=1.1, - nominal_storage_capacity=10000) - self.compare_lp_files('storage_invest_5.lp') + nominal_storage_capacity=10000, + ) + self.compare_lp_files("storage_invest_5.lp") def test_storage_invest_6(self): """Like test_storage_invest_5 but there can also be an investment in the basin. """ - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage6', - inputs={bel: solph.Flow(investment=solph.Investment( - ep_costs=99, existing=110))}, - outputs={bel: solph.Flow(investment=solph.Investment( - existing=100))}, + label="storage6", + inputs={ + bel: solph.Flow( + investment=solph.Investment(ep_costs=99, existing=110) + ) + }, + outputs={ + bel: solph.Flow(investment=solph.Investment(existing=100)) + }, invest_relation_input_output=1.1, - investment=solph.Investment(ep_costs=145, existing=10000)) - self.compare_lp_files('storage_invest_6.lp') + investment=solph.Investment(ep_costs=145, existing=10000), + ) + self.compare_lp_files("storage_invest_6.lp") def test_storage_minimum_invest(self): """All invest variables are coupled. The invest variables of the Flows will be created during the initialisation of the storage e.g. battery """ - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage1', + label="storage1", inputs={bel: solph.Flow()}, outputs={bel: solph.Flow()}, investment=solph.Investment( - ep_costs=145, minimum=100, maximum=200)) + ep_costs=145, minimum=100, maximum=200 + ), + ) - self.compare_lp_files('storage_invest_minimum.lp') + self.compare_lp_files("storage_invest_minimum.lp") def test_storage_unbalanced(self): """Testing a unbalanced storage (e.g. battery).""" - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage1', + label="storage1", inputs={bel: solph.Flow()}, outputs={bel: solph.Flow()}, nominal_storage_capacity=1111, initial_storage_level=None, balanced=False, invest_relation_input_capacity=1, - invest_relation_output_capacity=1) - self.compare_lp_files('storage_unbalanced.lp') + invest_relation_output_capacity=1, + ) + self.compare_lp_files("storage_unbalanced.lp") def test_storage_invest_unbalanced(self): """Testing a unbalanced storage (e.g. battery).""" - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage1', + label="storage1", inputs={bel: solph.Flow()}, outputs={bel: solph.Flow()}, nominal_storage_capacity=None, @@ -369,16 +439,16 @@ def test_storage_invest_unbalanced(self): balanced=False, invest_relation_input_capacity=1, invest_relation_output_capacity=1, - investment=solph.Investment(ep_costs=145)) - self.compare_lp_files('storage_invest_unbalanced.lp') + investment=solph.Investment(ep_costs=145), + ) + self.compare_lp_files("storage_invest_unbalanced.lp") def test_storage_fixed_losses(self): - """ - """ - bel = solph.Bus(label='electricityBus') + """""" + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage_no_invest', + label="storage_no_invest", inputs={bel: solph.Flow(nominal_value=16667, variable_costs=56)}, outputs={bel: solph.Flow(nominal_value=16667, variable_costs=24)}, nominal_storage_capacity=1e5, @@ -387,18 +457,19 @@ def test_storage_fixed_losses(self): fixed_losses_absolute=3, inflow_conversion_factor=0.97, outflow_conversion_factor=0.86, - initial_storage_level=0.4) + initial_storage_level=0.4, + ) - self.compare_lp_files('storage_fixed_losses.lp') + self.compare_lp_files("storage_fixed_losses.lp") def test_storage_invest_1_fixed_losses(self): """All invest variables are coupled. The invest variables of the Flows will be created during the initialisation of the storage e.g. battery """ - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage1', + label="storage1", inputs={bel: solph.Flow(variable_costs=56)}, outputs={bel: solph.Flow(variable_costs=24)}, nominal_storage_capacity=None, @@ -407,221 +478,277 @@ def test_storage_invest_1_fixed_losses(self): fixed_losses_absolute=3, max_storage_level=0.9, min_storage_level=0.1, - invest_relation_input_capacity=1/6, - invest_relation_output_capacity=1/6, + invest_relation_input_capacity=1 / 6, + invest_relation_output_capacity=1 / 6, inflow_conversion_factor=0.97, outflow_conversion_factor=0.86, - investment=solph.Investment(ep_costs=145, maximum=234)) + investment=solph.Investment(ep_costs=145, maximum=234), + ) - self.compare_lp_files('storage_invest_1_fixed_losses.lp') + self.compare_lp_files("storage_invest_1_fixed_losses.lp") def test_transformer(self): - """Constraint test of a LinearN1Transformer without Investment. - """ - bgas = solph.Bus(label='gasBus') - bbms = solph.Bus(label='biomassBus') - bel = solph.Bus(label='electricityBus') - bth = solph.Bus(label='thermalBus') + """Constraint test of a LinearN1Transformer without Investment.""" + bgas = solph.Bus(label="gasBus") + bbms = solph.Bus(label="biomassBus") + bel = solph.Bus(label="electricityBus") + bth = solph.Bus(label="thermalBus") solph.Transformer( - label='powerplantGasCoal', + label="powerplantGasCoal", inputs={bbms: solph.Flow(), bgas: solph.Flow()}, - outputs={bel: solph.Flow(variable_costs=50), - bth: solph.Flow(nominal_value=5e10, variable_costs=20)}, - conversion_factors={bgas: 0.4, bbms: 0.1, - bel: 0.3, bth: 0.5}) + outputs={ + bel: solph.Flow(variable_costs=50), + bth: solph.Flow(nominal_value=5e10, variable_costs=20), + }, + conversion_factors={bgas: 0.4, bbms: 0.1, bel: 0.3, bth: 0.5}, + ) - self.compare_lp_files('transformer.lp') + self.compare_lp_files("transformer.lp") def test_transformer_invest(self): - """Constraint test of a LinearN1Transformer with Investment. - """ + """Constraint test of a LinearN1Transformer with Investment.""" - bgas = solph.Bus(label='gasBus') - bcoal = solph.Bus(label='coalBus') - bel = solph.Bus(label='electricityBus') - bth = solph.Bus(label='thermalBus') + bgas = solph.Bus(label="gasBus") + bcoal = solph.Bus(label="coalBus") + bel = solph.Bus(label="electricityBus") + bth = solph.Bus(label="thermalBus") solph.Transformer( - label='powerplant_gas_coal', + label="powerplant_gas_coal", inputs={bgas: solph.Flow(), bcoal: solph.Flow()}, - outputs={bel: solph.Flow(variable_costs=50, - investment=solph.Investment(maximum=1000, - ep_costs=20)), - bth: solph.Flow(variable_costs=20) - }, - conversion_factors={bgas: 0.58, bcoal: 0.2, - bel: 0.3, bth: 0.5}) + outputs={ + bel: solph.Flow( + variable_costs=50, + investment=solph.Investment(maximum=1000, ep_costs=20), + ), + bth: solph.Flow(variable_costs=20), + }, + conversion_factors={bgas: 0.58, bcoal: 0.2, bel: 0.3, bth: 0.5}, + ) - self.compare_lp_files('transformer_invest.lp') + self.compare_lp_files("transformer_invest.lp") def test_transformer_invest_with_existing(self): - """Constraint test of a LinearN1Transformer with Investment. - """ + """Constraint test of a LinearN1Transformer with Investment.""" - bgas = solph.Bus(label='gasBus') - bcoal = solph.Bus(label='coalBus') - bel = solph.Bus(label='electricityBus') - bth = solph.Bus(label='thermalBus') + bgas = solph.Bus(label="gasBus") + bcoal = solph.Bus(label="coalBus") + bel = solph.Bus(label="electricityBus") + bth = solph.Bus(label="thermalBus") solph.Transformer( - label='powerplant_gas_coal', + label="powerplant_gas_coal", inputs={bgas: solph.Flow(), bcoal: solph.Flow()}, - outputs={bel: solph.Flow(variable_costs=50, - investment=solph.Investment( - maximum=1000, ep_costs=20, - existing=200)), - bth: solph.Flow(variable_costs=20) - }, - conversion_factors={bgas: 0.58, bcoal: 0.2, - bel: 0.3, bth: 0.5}) + outputs={ + bel: solph.Flow( + variable_costs=50, + investment=solph.Investment( + maximum=1000, ep_costs=20, existing=200 + ), + ), + bth: solph.Flow(variable_costs=20), + }, + conversion_factors={bgas: 0.58, bcoal: 0.2, bel: 0.3, bth: 0.5}, + ) - self.compare_lp_files('transformer_invest_with_existing.lp') + self.compare_lp_files("transformer_invest_with_existing.lp") def test_linear_transformer_chp(self): - """Constraint test of a Transformer without Investment (two outputs). """ - bgas = solph.Bus(label='gasBus') - bheat = solph.Bus(label='heatBus') - bel = solph.Bus(label='electricityBus') + Constraint test of a Transformer without Investment (two outputs). + """ + bgas = solph.Bus(label="gasBus") + bheat = solph.Bus(label="heatBus") + bel = solph.Bus(label="electricityBus") solph.Transformer( - label='CHPpowerplantGas', + label="CHPpowerplantGas", inputs={bgas: solph.Flow(nominal_value=10e10, variable_costs=50)}, outputs={bel: solph.Flow(), bheat: solph.Flow()}, - conversion_factors={bel: 0.4, bheat: 0.5}) + conversion_factors={bel: 0.4, bheat: 0.5}, + ) - self.compare_lp_files('linear_transformer_chp.lp') + self.compare_lp_files("linear_transformer_chp.lp") def test_linear_transformer_chp_invest(self): - """Constraint test of a Transformer with Investment (two outputs). - """ + """Constraint test of a Transformer with Investment (two outputs).""" - bgas = solph.Bus(label='gasBus') - bheat = solph.Bus(label='heatBus') - bel = solph.Bus(label='electricityBus') + bgas = solph.Bus(label="gasBus") + bheat = solph.Bus(label="heatBus") + bel = solph.Bus(label="electricityBus") solph.Transformer( - label='chp_powerplant_gas', - inputs={bgas: solph.Flow(variable_costs=50, - investment=solph.Investment(maximum=1000, - ep_costs=20)) - }, + label="chp_powerplant_gas", + inputs={ + bgas: solph.Flow( + variable_costs=50, + investment=solph.Investment(maximum=1000, ep_costs=20), + ) + }, outputs={bel: solph.Flow(), bheat: solph.Flow()}, - conversion_factors={bel: 0.4, bheat: 0.5}) + conversion_factors={bel: 0.4, bheat: 0.5}, + ) - self.compare_lp_files('linear_transformer_chp_invest.lp') + self.compare_lp_files("linear_transformer_chp_invest.lp") def test_variable_chp(self): - """ - """ - bel = solph.Bus(label='electricityBus') - bth = solph.Bus(label='heatBus') - bgas = solph.Bus(label='commodityBus') + """""" + bel = solph.Bus(label="electricityBus") + bth = solph.Bus(label="heatBus") + bgas = solph.Bus(label="commodityBus") solph.components.ExtractionTurbineCHP( - label='variable_chp_gas1', + label="variable_chp_gas1", inputs={bgas: solph.Flow(nominal_value=100)}, outputs={bel: solph.Flow(), bth: solph.Flow()}, conversion_factors={bel: 0.3, bth: 0.5}, - conversion_factor_full_condensation={bel: 0.5}) + conversion_factor_full_condensation={bel: 0.5}, + ) solph.components.ExtractionTurbineCHP( - label='variable_chp_gas2', + label="variable_chp_gas2", inputs={bgas: solph.Flow(nominal_value=100)}, outputs={bel: solph.Flow(), bth: solph.Flow()}, conversion_factors={bel: 0.3, bth: 0.5}, - conversion_factor_full_condensation={bel: 0.5}) + conversion_factor_full_condensation={bel: 0.5}, + ) - self.compare_lp_files('variable_chp.lp') + self.compare_lp_files("variable_chp.lp") def test_generic_invest_limit(self): - """ - """ - bus = solph.Bus(label='bus_1') + """""" + bus = solph.Bus(label="bus_1") - solph.Source(label='source_0', outputs={bus: solph.Flow( - investment=solph.Investment(ep_costs=50, space=4))}) + solph.Source( + label="source_0", + outputs={ + bus: solph.Flow( + investment=solph.Investment(ep_costs=50, space=4) + ) + }, + ) - solph.Source(label='source_1', outputs={bus: solph.Flow( - investment=solph.Investment(ep_costs=100, space=1))}) + solph.Source( + label="source_1", + outputs={ + bus: solph.Flow( + investment=solph.Investment(ep_costs=100, space=1) + ) + }, + ) - solph.Source(label='source_2', outputs={bus: solph.Flow( - investment=solph.Investment(ep_costs=75))}) + solph.Source( + label="source_2", + outputs={ + bus: solph.Flow(investment=solph.Investment(ep_costs=75)) + }, + ) om = self.get_om() om = solph.constraints.additional_investment_flow_limit( - om, "space", limit=20) + om, "space", limit=20 + ) - self.compare_lp_files('generic_invest_limit.lp', my_om=om) + self.compare_lp_files("generic_invest_limit.lp", my_om=om) def test_emission_constraints(self): - """ - """ - bel = solph.Bus(label='electricityBus') + """""" + bel = solph.Bus(label="electricityBus") - solph.Source(label='source1', outputs={bel: solph.Flow( - nominal_value=100, emission_factor=[0.5, -1.0, 2.0])}) - solph.Source(label='source2', outputs={bel: solph.Flow( - nominal_value=100, emission_factor=3.5)}) + solph.Source( + label="source1", + outputs={ + bel: solph.Flow( + nominal_value=100, emission_factor=[0.5, -1.0, 2.0] + ) + }, + ) + solph.Source( + label="source2", + outputs={bel: solph.Flow(nominal_value=100, emission_factor=3.5)}, + ) # Should be ignored because the emission attribute is not defined. - solph.Source(label='source3', outputs={bel: solph.Flow( - nominal_value=100)}) + solph.Source( + label="source3", outputs={bel: solph.Flow(nominal_value=100)} + ) om = self.get_om() solph.constraints.emission_limit(om, limit=777) - self.compare_lp_files('emission_limit.lp', my_om=om) + self.compare_lp_files("emission_limit.lp", my_om=om) def test_flow_count_limit(self): - """ - """ - bel = solph.Bus(label='electricityBus') + """""" + bel = solph.Bus(label="electricityBus") - solph.Source(label='source1', outputs={bel: solph.Flow( - nonconvex=solph.NonConvex(), - nominal_value=100, emission_factor=[0.5, -1.0, 2.0])}) - solph.Source(label='source2', outputs={bel: solph.Flow( - nonconvex=solph.NonConvex(), - nominal_value=100, emission_factor=3.5)}) + solph.Source( + label="source1", + outputs={ + bel: solph.Flow( + nonconvex=solph.NonConvex(), + nominal_value=100, + emission_factor=[0.5, -1.0, 2.0], + ) + }, + ) + solph.Source( + label="source2", + outputs={ + bel: solph.Flow( + nonconvex=solph.NonConvex(), + nominal_value=100, + emission_factor=3.5, + ) + }, + ) # Should be ignored because emission_factor is not defined. - solph.Source(label='source3', outputs={bel: solph.Flow( - nonconvex=solph.NonConvex(), nominal_value=100)}) + solph.Source( + label="source3", + outputs={ + bel: solph.Flow(nonconvex=solph.NonConvex(), nominal_value=100) + }, + ) # Should be ignored because it is not NonConvex. - solph.Source(label='source4', outputs={bel: solph.Flow( - emission_factor=1.5, - min=0.3, nominal_value=100)}) + solph.Source( + label="source4", + outputs={ + bel: solph.Flow( + emission_factor=1.5, min=0.3, nominal_value=100 + ) + }, + ) om = self.get_om() # one of the two flows has to be active - solph.constraints.limit_active_flow_count_by_keyword(om, - "emission_factor", - lower_limit=1, - upper_limit=2) + solph.constraints.limit_active_flow_count_by_keyword( + om, "emission_factor", lower_limit=1, upper_limit=2 + ) - self.compare_lp_files('flow_count_limit.lp', my_om=om) + self.compare_lp_files("flow_count_limit.lp", my_om=om) def test_shared_limit(self): - """ - """ - b1 = solph.Bus(label='bus') + """""" + b1 = solph.Bus(label="bus") storage1 = solph.components.GenericStorage( label="storage1", nominal_storage_capacity=5, inputs={b1: solph.Flow()}, - outputs={b1: solph.Flow()}) + outputs={b1: solph.Flow()}, + ) storage2 = solph.components.GenericStorage( label="storage2", nominal_storage_capacity=5, inputs={b1: solph.Flow()}, - outputs={b1: solph.Flow()}) + outputs={b1: solph.Flow()}, + ) model = self.get_om() @@ -630,217 +757,286 @@ def test_shared_limit(self): solph.constraints.shared_limit( model, model.GenericStorageBlock.storage_content, - "limit_storage", components, - [0.5, 1.25], upper_limit=7) + "limit_storage", + components, + [0.5, 1.25], + upper_limit=7, + ) - self.compare_lp_files('shared_limit.lp', my_om=model) + self.compare_lp_files("shared_limit.lp", my_om=model) def test_flow_without_emission_for_emission_constraint(self): - """ - """ + """""" + def define_emission_limit(): - bel = solph.Bus(label='electricityBus') - solph.Source(label='source1', outputs={bel: solph.Flow( - nominal_value=100, emission_factor=0.8)}) - solph.Source(label='source2', outputs={bel: solph.Flow( - nominal_value=100)}) + bel = solph.Bus(label="electricityBus") + solph.Source( + label="source1", + outputs={ + bel: solph.Flow(nominal_value=100, emission_factor=0.8) + }, + ) + solph.Source( + label="source2", outputs={bel: solph.Flow(nominal_value=100)} + ) om = self.get_om() solph.constraints.emission_limit(om, om.flows, limit=777) + assert_raises(AttributeError, define_emission_limit) def test_flow_without_emission_for_emission_constraint_no_error(self): - """ - """ - bel = solph.Bus(label='electricityBus') - solph.Source(label='source1', outputs={bel: solph.Flow( - nominal_value=100, emission_factor=0.8)}) - solph.Source(label='source2', outputs={bel: solph.Flow( - nominal_value=100)}) + """""" + bel = solph.Bus(label="electricityBus") + solph.Source( + label="source1", + outputs={bel: solph.Flow(nominal_value=100, emission_factor=0.8)}, + ) + solph.Source( + label="source2", outputs={bel: solph.Flow(nominal_value=100)} + ) om = self.get_om() solph.constraints.emission_limit(om, limit=777) def test_equate_variables_constraint(self): """Testing the equate_variables function in the constraint module.""" - bus1 = solph.Bus(label='Bus1') + bus1 = solph.Bus(label="Bus1") storage = solph.components.GenericStorage( - label='storage_constraint', + label="storage_constraint", invest_relation_input_capacity=0.2, invest_relation_output_capacity=0.2, inputs={bus1: solph.Flow()}, outputs={bus1: solph.Flow()}, - investment=solph.Investment(ep_costs=145)) - sink = solph.Sink(label='Sink', inputs={bus1: solph.Flow( - investment=solph.Investment(ep_costs=500))}) - source = solph.Source(label='Source', outputs={bus1: solph.Flow( - investment=solph.Investment(ep_costs=123))}) + investment=solph.Investment(ep_costs=145), + ) + sink = solph.Sink( + label="Sink", + inputs={ + bus1: solph.Flow(investment=solph.Investment(ep_costs=500)) + }, + ) + source = solph.Source( + label="Source", + outputs={ + bus1: solph.Flow(investment=solph.Investment(ep_costs=123)) + }, + ) om = self.get_om() solph.constraints.equate_variables( - om, om.InvestmentFlow.invest[source, bus1], - om.InvestmentFlow.invest[bus1, sink], 2) + om, + om.InvestmentFlow.invest[source, bus1], + om.InvestmentFlow.invest[bus1, sink], + 2, + ) solph.constraints.equate_variables( - om, om.InvestmentFlow.invest[source, bus1], - om.GenericInvestmentStorageBlock.invest[storage]) + om, + om.InvestmentFlow.invest[source, bus1], + om.GenericInvestmentStorageBlock.invest[storage], + ) - self.compare_lp_files('connect_investment.lp', my_om=om) + self.compare_lp_files("connect_investment.lp", my_om=om) def test_gradient(self): """Testing min and max runtimes for nonconvex flows.""" - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") - solph.Source(label='powerplant', outputs={bel: solph.Flow( - nominal_value=999, variable_costs=23, - positive_gradient={'ub': 0.03, 'costs': 7}, - negative_gradient={'ub': 0.05, 'costs': 8})}) + solph.Source( + label="powerplant", + outputs={ + bel: solph.Flow( + nominal_value=999, + variable_costs=23, + positive_gradient={"ub": 0.03, "costs": 7}, + negative_gradient={"ub": 0.05, "costs": 8}, + ) + }, + ) - self.compare_lp_files('source_with_gradient.lp') + self.compare_lp_files("source_with_gradient.lp") def test_investment_limit(self): """Testing the investment_limit function in the constraint module.""" - bus1 = solph.Bus(label='Bus1') + bus1 = solph.Bus(label="Bus1") solph.components.GenericStorage( - label='storage_invest_limit', + label="storage_invest_limit", invest_relation_input_capacity=0.2, invest_relation_output_capacity=0.2, inputs={bus1: solph.Flow()}, outputs={bus1: solph.Flow()}, - investment=solph.Investment(ep_costs=145)) - solph.Source(label='Source', outputs={bus1: solph.Flow( - investment=solph.Investment(ep_costs=123))}) + investment=solph.Investment(ep_costs=145), + ) + solph.Source( + label="Source", + outputs={ + bus1: solph.Flow(investment=solph.Investment(ep_costs=123)) + }, + ) om = self.get_om() solph.constraints.investment_limit(om, limit=900) - self.compare_lp_files('investment_limit.lp', my_om=om) + self.compare_lp_files("investment_limit.lp", my_om=om) def test_min_max_runtime(self): """Testing min and max runtimes for nonconvex flows.""" - bus_t = solph.Bus(label='Bus_T') + bus_t = solph.Bus(label="Bus_T") solph.Source( - label='cheap_plant_min_down_constraints', - outputs={bus_t: solph.Flow( - nominal_value=10, min=0.5, max=1.0, variable_costs=10, - nonconvex=solph.NonConvex( - minimum_downtime=4, minimum_uptime=2, initial_status=2, - startup_costs=5, shutdown_costs=7))}) - self.compare_lp_files('min_max_runtime.lp') + label="cheap_plant_min_down_constraints", + outputs={ + bus_t: solph.Flow( + nominal_value=10, + min=0.5, + max=1.0, + variable_costs=10, + nonconvex=solph.NonConvex( + minimum_downtime=4, + minimum_uptime=2, + initial_status=2, + startup_costs=5, + shutdown_costs=7, + ), + ) + }, + ) + self.compare_lp_files("min_max_runtime.lp") def test_activity_costs(self): """Testing activity_costs attribute for nonconvex flows.""" - bus_t = solph.Bus(label='Bus_C') + bus_t = solph.Bus(label="Bus_C") solph.Source( - label='cheap_plant_activity_costs', - outputs={bus_t: solph.Flow( - nominal_value=10, min=0.5, max=1.0, variable_costs=10, - nonconvex=solph.NonConvex(activity_costs=2))}) - self.compare_lp_files('activity_costs.lp') + label="cheap_plant_activity_costs", + outputs={ + bus_t: solph.Flow( + nominal_value=10, + min=0.5, + max=1.0, + variable_costs=10, + nonconvex=solph.NonConvex(activity_costs=2), + ) + }, + ) + self.compare_lp_files("activity_costs.lp") def test_piecewise_linear_transformer_cc(self): """Testing PiecewiseLinearTransformer using CC formulation.""" - bgas = solph.Bus(label='gasBus') - bel = solph.Bus(label='electricityBus') + bgas = solph.Bus(label="gasBus") + bel = solph.Bus(label="electricityBus") solph.custom.PiecewiseLinearTransformer( - label='pwltf', - inputs={bgas: solph.Flow( - nominal_value=100, - variable_costs=1)}, + label="pwltf", + inputs={bgas: solph.Flow(nominal_value=100, variable_costs=1)}, outputs={bel: solph.Flow()}, in_breakpoints=[0, 25, 50, 75, 100], - conversion_function=lambda x: x**2, - pw_repn='CC') - self.compare_lp_files('piecewise_linear_transformer_cc.lp') + conversion_function=lambda x: x ** 2, + pw_repn="CC", + ) + self.compare_lp_files("piecewise_linear_transformer_cc.lp") def test_piecewise_linear_transformer_dcc(self): """Testing PiecewiseLinearTransformer using DCC formulation.""" - bgas = solph.Bus(label='gasBus') - bel = solph.Bus(label='electricityBus') + bgas = solph.Bus(label="gasBus") + bel = solph.Bus(label="electricityBus") solph.custom.PiecewiseLinearTransformer( - label='pwltf', - inputs={bgas: solph.Flow( - nominal_value=100, - variable_costs=1)}, + label="pwltf", + inputs={bgas: solph.Flow(nominal_value=100, variable_costs=1)}, outputs={bel: solph.Flow()}, in_breakpoints=[0, 25, 50, 75, 100], - conversion_function=lambda x: x**2, - pw_repn='DCC') - self.compare_lp_files('piecewise_linear_transformer_dcc.lp') + conversion_function=lambda x: x ** 2, + pw_repn="DCC", + ) + self.compare_lp_files("piecewise_linear_transformer_dcc.lp") def test_maximum_startups(self): """Testing maximum_startups attribute for nonconvex flows.""" - bus_t = solph.Bus(label='Bus_C') + bus_t = solph.Bus(label="Bus_C") solph.Source( - label='cheap_plant_maximum_startups', - outputs={bus_t: solph.Flow( - nominal_value=10, min=0.5, max=1.0, variable_costs=10, - nonconvex=solph.NonConvex(maximum_startups=2))}) - self.compare_lp_files('maximum_startups.lp') + label="cheap_plant_maximum_startups", + outputs={ + bus_t: solph.Flow( + nominal_value=10, + min=0.5, + max=1.0, + variable_costs=10, + nonconvex=solph.NonConvex(maximum_startups=2), + ) + }, + ) + self.compare_lp_files("maximum_startups.lp") def test_maximum_shutdowns(self): """Testing maximum_shutdowns attribute for nonconvex flows.""" - bus_t = solph.Bus(label='Bus_C') + bus_t = solph.Bus(label="Bus_C") solph.Source( - label='cheap_plant_maximum_shutdowns', - outputs={bus_t: solph.Flow( - nominal_value=10, min=0.5, max=1.0, variable_costs=10, - nonconvex=solph.NonConvex(maximum_shutdowns=2))}) - self.compare_lp_files('maximum_shutdowns.lp') + label="cheap_plant_maximum_shutdowns", + outputs={ + bus_t: solph.Flow( + nominal_value=10, + min=0.5, + max=1.0, + variable_costs=10, + nonconvex=solph.NonConvex(maximum_shutdowns=2), + ) + }, + ) + self.compare_lp_files("maximum_shutdowns.lp") def test_offsettransformer(self): - """Constraint test of a OffsetTransformer. - """ - bgas = solph.Bus(label='gasBus') - bth = solph.Bus(label='thermalBus') + """Constraint test of a OffsetTransformer.""" + bgas = solph.Bus(label="gasBus") + bth = solph.Bus(label="thermalBus") solph.components.OffsetTransformer( - label='gasboiler', - inputs={bgas: solph.Flow( - nonconvex=solph.NonConvex(), - nominal_value=100, - min=0.32, - )}, + label="gasboiler", + inputs={ + bgas: solph.Flow( + nonconvex=solph.NonConvex(), + nominal_value=100, + min=0.32, + ) + }, outputs={bth: solph.Flow()}, - coefficients=[-17, 0.9]) + coefficients=[-17, 0.9], + ) - self.compare_lp_files('offsettransformer.lp') + self.compare_lp_files("offsettransformer.lp") def test_dsm_module_delay(self): """Constraint test of Sink-DSM with method=delay""" - b_elec = solph.Bus(label='bus_elec') + b_elec = solph.Bus(label="bus_elec") solph.custom.SinkDSM( - label='demand_dsm', + label="demand_dsm", inputs={b_elec: solph.Flow()}, demand=[1] * 3, capacity_up=[0.5] * 3, capacity_down=[0.5] * 3, - method='delay', + method="delay", delay_time=1, cost_dsm_down=2, ) - self.compare_lp_files('dsm_module_delay.lp') + self.compare_lp_files("dsm_module_delay.lp") def test_dsm_module_interval(self): """Constraint test of Sink-DSM with method=interval""" - b_elec = solph.Bus(label='bus_elec') + b_elec = solph.Bus(label="bus_elec") solph.custom.SinkDSM( - label='demand_dsm', + label="demand_dsm", inputs={b_elec: solph.Flow()}, demand=[1] * 3, capacity_up=[0.5, 0.4, 0.5], capacity_down=[0.5, 0.4, 0.5], - method='interval', + method="interval", shift_interval=2, cost_dsm_down=2, ) - self.compare_lp_files('dsm_module_interval.lp') + self.compare_lp_files("dsm_module_interval.lp") def test_nonconvex_investment_storage_without_offset(self): """All invest variables are coupled. The invest variables of the Flows will be created during the initialisation of the storage e.g. battery """ - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storage_non_convex', + label="storage_non_convex", inputs={bel: solph.Flow(variable_costs=56)}, outputs={bel: solph.Flow(variable_costs=24)}, nominal_storage_capacity=None, @@ -851,19 +1047,21 @@ def test_nonconvex_investment_storage_without_offset(self): invest_relation_output_capacity=1 / 6, inflow_conversion_factor=0.97, outflow_conversion_factor=0.86, - investment=solph.Investment(ep_costs=141, maximum=244, minimum=12, - nonconvex=True)) + investment=solph.Investment( + ep_costs=141, maximum=244, minimum=12, nonconvex=True + ), + ) - self.compare_lp_files('storage_invest_without_offset.lp') + self.compare_lp_files("storage_invest_without_offset.lp") def test_nonconvex_investment_storage_with_offset(self): """All invest variables are coupled. The invest variables of the Flows will be created during the initialisation of the storage e.g. battery """ - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") solph.components.GenericStorage( - label='storagenon_convex', + label="storagenon_convex", inputs={bel: solph.Flow(variable_costs=56)}, outputs={bel: solph.Flow(variable_costs=24)}, nominal_storage_capacity=None, @@ -874,59 +1072,109 @@ def test_nonconvex_investment_storage_with_offset(self): invest_relation_output_capacity=1 / 6, inflow_conversion_factor=0.97, outflow_conversion_factor=0.86, - investment=solph.Investment(ep_costs=145, minimum=19, offset=5, - nonconvex=True, maximum=1454)) + investment=solph.Investment( + ep_costs=145, + minimum=19, + offset=5, + nonconvex=True, + maximum=1454, + ), + ) - self.compare_lp_files('storage_invest_with_offset.lp') + self.compare_lp_files("storage_invest_with_offset.lp") def test_nonconvex_invest_storage_all_nonconvex(self): """All invest variables are free and nonconvex.""" - b1 = solph.Bus(label='bus1') + b1 = solph.Bus(label="bus1") solph.components.GenericStorage( - label='storage_all_nonconvex', - inputs={b1: solph.Flow(investment=solph.Investment( - nonconvex=True, minimum=5, offset=10, maximum=30, - ep_costs=10))}, - outputs={b1: solph.Flow( - investment=solph.Investment( - nonconvex=True, minimum=8, offset=15, ep_costs=10, - maximum=20))}, + label="storage_all_nonconvex", + inputs={ + b1: solph.Flow( + investment=solph.Investment( + nonconvex=True, + minimum=5, + offset=10, + maximum=30, + ep_costs=10, + ) + ) + }, + outputs={ + b1: solph.Flow( + investment=solph.Investment( + nonconvex=True, + minimum=8, + offset=15, + ep_costs=10, + maximum=20, + ) + ) + }, investment=solph.Investment( - nonconvex=True, ep_costs=20, offset=30, minimum=20, - maximum=100)) + nonconvex=True, ep_costs=20, offset=30, minimum=20, maximum=100 + ), + ) - self.compare_lp_files('storage_invest_all_nonconvex.lp') + self.compare_lp_files("storage_invest_all_nonconvex.lp") def test_nonconvex_invest_sink_without_offset(self): - """ Non convex invest flow without offset, with minimum. - """ - bel = solph.Bus(label='electricityBus') - - solph.Sink(label='sink_nonconvex_invest', inputs={bel: solph.Flow( - summed_max=2.3, variable_costs=25, max=0.8, - investment=solph.Investment(ep_costs=500, minimum=15, - nonconvex=True, maximum=172))}) - self.compare_lp_files('flow_invest_without_offset.lp') + """Non convex invest flow without offset, with minimum.""" + bel = solph.Bus(label="electricityBus") + + solph.Sink( + label="sink_nonconvex_invest", + inputs={ + bel: solph.Flow( + summed_max=2.3, + variable_costs=25, + max=0.8, + investment=solph.Investment( + ep_costs=500, minimum=15, nonconvex=True, maximum=172 + ), + ) + }, + ) + self.compare_lp_files("flow_invest_without_offset.lp") def test_nonconvex_invest_source_with_offset(self): - """ Non convex invest flow with offset, with minimum. - """ - bel = solph.Bus(label='electricityBus') + """Non convex invest flow with offset, with minimum.""" + bel = solph.Bus(label="electricityBus") - solph.Source(label='source_nonconvex_invest', inputs={bel: solph.Flow( - summed_max=2.3, variable_costs=25, max=0.8, - investment=solph.Investment(ep_costs=500, minimum=15, maximum=20, - offset=34, nonconvex=True))}) - self.compare_lp_files('flow_invest_with_offset.lp') + solph.Source( + label="source_nonconvex_invest", + inputs={ + bel: solph.Flow( + summed_max=2.3, + variable_costs=25, + max=0.8, + investment=solph.Investment( + ep_costs=500, + minimum=15, + maximum=20, + offset=34, + nonconvex=True, + ), + ) + }, + ) + self.compare_lp_files("flow_invest_with_offset.lp") def test_nonconvex_invest_source_with_offset_no_minimum(self): - """ Non convex invest flow with offset, without minimum. - """ - bel = solph.Bus(label='electricityBus') + """Non convex invest flow with offset, without minimum.""" + bel = solph.Bus(label="electricityBus") - solph.Source(label='source_nonconvex_invest', inputs={bel: solph.Flow( - summed_max=2.3, variable_costs=25, max=0.8, - investment=solph.Investment(ep_costs=500, maximum=1234, - offset=34, nonconvex=True))}) - self.compare_lp_files('flow_invest_with_offset_no_minimum.lp') + solph.Source( + label="source_nonconvex_invest", + inputs={ + bel: solph.Flow( + summed_max=2.3, + variable_costs=25, + max=0.8, + investment=solph.Investment( + ep_costs=500, maximum=1234, offset=34, nonconvex=True + ), + ) + }, + ) + self.compare_lp_files("flow_invest_with_offset_no_minimum.lp") diff --git a/tests/run_nose.py b/tests/run_nose.py deleted file mode 100644 index 422885cf7..000000000 --- a/tests/run_nose.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -import sys - -try: - import nose -except ImportError: - raise ImportError("Please install nosetest to use this script.") - - -def nose_oemof(): - """You can just execute this function to run the oemof nosetests and - doctests. Nosetests has to be installed. - """ - testdir = os.path.join(os.path.dirname(__file__), os.path.pardir) - argv = sys.argv[:] - argv.insert(1, "--with-doctest") - argv.insert(1, "--logging-clear-handlers") - argv.insert(1, "-w{0}".format(testdir)) - nose.run(argv=argv) - - -if __name__ == "__main__": - nose_oemof() diff --git a/tests/solph_tests.py b/tests/solph_tests.py index 14898e1cb..8dc447e93 100644 --- a/tests/solph_tests.py +++ b/tests/solph_tests.py @@ -22,13 +22,12 @@ class TestsGrouping: - def setup(self): self.es = EnSys(groupings=solph.GROUPINGS) Node.registry = self.es def test_investment_flow_grouping(self): - """ Flows of investment sink should be grouped. + """Flows of investment sink should be grouped. The constraint tests uncovered a spurious error where the flows of an investment `Sink` where not put into the `InvestmentFlow` group, @@ -40,23 +39,36 @@ def test_investment_flow_grouping(self): `InvestmentFlow` group is not empty. """ - b = solph.Bus(label='Bus') - - solph.Source(label='Source', outputs={b: solph.Flow( - fix=[12, 16, 14], nominal_value=1000000)}) - - solph.Sink(label='Sink', inputs={b: solph.Flow( - summed_max=2.3, variable_costs=25, max=0.8, - investment=Investment(ep_costs=500, maximum=10e5))}) - - ok_(self.es.groups.get(InvFlow), - ("Expected InvestmentFlow group to be nonempty.\n" + - "Got: {}").format(self.es.groups.get(InvFlow))) + b = solph.Bus(label="Bus") + + solph.Source( + label="Source", + outputs={b: solph.Flow(fix=[12, 16, 14], nominal_value=1000000)}, + ) + + solph.Sink( + label="Sink", + inputs={ + b: solph.Flow( + summed_max=2.3, + variable_costs=25, + max=0.8, + investment=Investment(ep_costs=500, maximum=10e5), + ) + }, + ) + + ok_( + self.es.groups.get(InvFlow), + ( + "Expected InvestmentFlow group to be nonempty.\n" + "Got: {}" + ).format(self.es.groups.get(InvFlow)), + ) def test_helpers(): - ok_(os.path.isdir(os.path.join(os.path.expanduser('~'), '.oemof'))) - new_dir = extend_basic_path('test_xf67456_dir') + ok_(os.path.isdir(os.path.join(os.path.expanduser("~"), ".oemof"))) + new_dir = extend_basic_path("test_xf67456_dir") ok_(os.path.isdir(new_dir)) os.rmdir(new_dir) ok_(not os.path.isdir(new_dir)) diff --git a/tests/test_components.py b/tests/test_components.py index 318a34cec..2463601f3 100644 --- a/tests/test_components.py +++ b/tests/test_components.py @@ -28,15 +28,18 @@ def test_generic_storage_1(): bel = Bus() with pytest.raises(AttributeError, match="Overdetermined."): components.GenericStorage( - label='storage1', + label="storage1", inputs={bel: Flow(variable_costs=10e10)}, outputs={bel: Flow(variable_costs=10e10)}, - loss_rate=0.00, initial_storage_level=0, + loss_rate=0.00, + initial_storage_level=0, invest_relation_input_output=1, invest_relation_output_capacity=1, invest_relation_input_capacity=1, investment=Investment(), - inflow_conversion_factor=1, outflow_conversion_factor=0.8) + inflow_conversion_factor=1, + outflow_conversion_factor=0.8, + ) def test_generic_storage_2(): @@ -44,43 +47,49 @@ def test_generic_storage_2(): bel = Bus() with pytest.raises(AttributeError, match="If an investment object"): components.GenericStorage( - label='storage3', + label="storage3", nominal_storage_capacity=45, inputs={bel: Flow(variable_costs=10e10)}, outputs={bel: Flow(variable_costs=10e10)}, - loss_rate=0.00, initial_storage_level=0, - invest_relation_input_capacity=1/6, - invest_relation_output_capacity=1/6, - inflow_conversion_factor=1, outflow_conversion_factor=0.8, - investment=Investment(ep_costs=23)) + loss_rate=0.00, + initial_storage_level=0, + invest_relation_input_capacity=1 / 6, + invest_relation_output_capacity=1 / 6, + inflow_conversion_factor=1, + outflow_conversion_factor=0.8, + investment=Investment(ep_costs=23), + ) def test_generic_storage_3(): """Nominal value defined with investment model.""" bel = Bus() components.GenericStorage( - label='storage4', + label="storage4", nominal_storage_capacity=45, inputs={bel: Flow(nominal_value=23, variable_costs=10e10)}, outputs={bel: Flow(nominal_value=7.5, variable_costs=10e10)}, - loss_rate=0.00, initial_storage_level=0, - inflow_conversion_factor=1, outflow_conversion_factor=0.8) + loss_rate=0.00, + initial_storage_level=0, + inflow_conversion_factor=1, + outflow_conversion_factor=0.8, + ) def test_generic_storage_with_old_parameters(): deprecated = { - 'nominal_capacity': 45, - 'initial_capacity': 0, - 'capacity_loss': 0, - 'capacity_min': 0, - 'capacity_max': 0, + "nominal_capacity": 45, + "initial_capacity": 0, + "capacity_loss": 0, + "capacity_min": 0, + "capacity_max": 0, } # Make sure an `AttributeError` is raised if we supply all deprecated # parameters. with pytest.raises(AttributeError) as caught: components.GenericStorage( - label='`GenericStorage` with all deprecated parameters', - **deprecated + label="`GenericStorage` with all deprecated parameters", + **deprecated, ) for parameter in deprecated: # Make sure every parameter used is mentioned in the exception's @@ -94,51 +103,56 @@ def test_generic_storage_with_old_parameters(): **{ "label": "`GenericStorage` with `{}`".format(parameter), parameter: deprecated[parameter], - }) + }, + ) def test_generic_storage_with_non_convex_investment(): """Tests error if `offset` and `existing` attribute are given.""" with pytest.raises( - AttributeError, - match=r"Values for 'offset' and 'existing' are given"): + AttributeError, match=r"Values for 'offset' and 'existing' are given" + ): bel = Bus() components.GenericStorage( - label='storage4', + label="storage4", inputs={bel: Flow()}, outputs={bel: Flow()}, - invest_relation_input_capacity=1/6, - invest_relation_output_capacity=1/6, - investment=Investment(nonconvex=True, existing=5, maximum=25)) + invest_relation_input_capacity=1 / 6, + invest_relation_output_capacity=1 / 6, + investment=Investment(nonconvex=True, existing=5, maximum=25), + ) def test_generic_storage_with_non_convex_invest_maximum(): """No investment maximum at nonconvex investment.""" with pytest.raises( - AttributeError, - match=r"Please provide an maximum investment value"): + AttributeError, match=r"Please provide an maximum investment value" + ): bel = Bus() components.GenericStorage( - label='storage6', + label="storage6", inputs={bel: Flow()}, outputs={bel: Flow()}, - invest_relation_input_capacity=1/6, - invest_relation_output_capacity=1/6, - investment=Investment(nonconvex=True)) + invest_relation_input_capacity=1 / 6, + invest_relation_output_capacity=1 / 6, + investment=Investment(nonconvex=True), + ) def test_generic_storage_with_convex_invest_offset(): """Offset value is given and nonconvex is False.""" with pytest.raises( - AttributeError, match=r"If `nonconvex` is `False`, the `offset`"): + AttributeError, match=r"If `nonconvex` is `False`, the `offset`" + ): bel = Bus() components.GenericStorage( - label='storage6', + label="storage6", inputs={bel: Flow()}, outputs={bel: Flow()}, - invest_relation_input_capacity=1/6, - invest_relation_output_capacity=1/6, - investment=Investment(offset=10)) + invest_relation_input_capacity=1 / 6, + invest_relation_output_capacity=1 / 6, + investment=Investment(offset=10), + ) def test_generic_storage_with_invest_and_fixed_losses_absolute(): @@ -147,22 +161,23 @@ def test_generic_storage_with_invest_and_fixed_losses_absolute(): value is set an AttributeError is raised because this may result in storage with zero capacity but fixed losses. """ - msg = (r"With fixed_losses_absolute > 0, either investment.existing or" - " investment.minimum has to be non-zero.") + msg = ( + r"With fixed_losses_absolute > 0, either investment.existing or" + " investment.minimum has to be non-zero." + ) with pytest.raises(AttributeError, match=msg): bel = Bus() components.GenericStorage( - label='storage4', + label="storage4", inputs={bel: Flow()}, outputs={bel: Flow()}, investment=Investment(ep_costs=23, minimum=0, existing=0), fixed_losses_absolute=[0, 0, 4], - ) + ) def test_generic_storage_without_inputs(): - components.GenericStorage( - label='storage5') + components.GenericStorage(label="storage5") def test_generic_storage_too_many_inputs(): @@ -171,8 +186,8 @@ def test_generic_storage_too_many_inputs(): bel2 = Bus() with pytest.raises(AttributeError, match=msg): components.GenericStorage( - label='storage6', - inputs={bel1: Flow(), bel2: Flow()}) + label="storage6", inputs={bel1: Flow(), bel2: Flow()} + ) def test_generic_storage_too_many_outputs(): @@ -181,39 +196,38 @@ def test_generic_storage_too_many_outputs(): bel2 = Bus() with pytest.raises(AttributeError, match=msg): components.GenericStorage( - label='storage7', - outputs={bel1: Flow(), bel2: Flow()}) + label="storage7", outputs={bel1: Flow(), bel2: Flow()} + ) # ********* OffsetTransformer ********* + def test_offsettransformer_wrong_flow_type(): """No NonConvexFlow for Inflow defined.""" with pytest.raises( - TypeError, match=r'Input flows must be of type NonConvexFlow!'): - bgas = Bus(label='gasBus') + TypeError, match=r"Input flows must be of type NonConvexFlow!" + ): + bgas = Bus(label="gasBus") components.OffsetTransformer( - label='gasboiler', - inputs={bgas: Flow()}, - coefficients=(-17, 0.9)) + label="gasboiler", inputs={bgas: Flow()}, coefficients=(-17, 0.9) + ) def test_offsettransformer_not_enough_coefficients(): with pytest.raises( - ValueError, - match=r'Two coefficients or coefficient series have to be given.'): - components.OffsetTransformer( - label='of1', - coefficients=([1, 4, 7])) + ValueError, + match=r"Two coefficients or coefficient series have to be given.", + ): + components.OffsetTransformer(label="of1", coefficients=([1, 4, 7])) def test_offsettransformer_too_many_coefficients(): with pytest.raises( - ValueError, - match=r'Two coefficients or coefficient series have to be given.'): - components.OffsetTransformer( - label='of2', - coefficients=(1, 4, 7)) + ValueError, + match=r"Two coefficients or coefficient series have to be given.", + ): + components.OffsetTransformer(label="of2", coefficients=(1, 4, 7)) def test_offsettransformer_empty(): @@ -223,58 +237,64 @@ def test_offsettransformer_empty(): def test_offsettransformer__too_many_input_flows(): """Too many Input Flows defined.""" - with pytest.raises(ValueError, - match=r"OffsetTransformer` must not have more than 1"): - bgas = Bus(label='GasBus') - bcoal = Bus(label='CoalBus') + with pytest.raises( + ValueError, match=r"OffsetTransformer` must not have more than 1" + ): + bgas = Bus(label="GasBus") + bcoal = Bus(label="CoalBus") components.OffsetTransformer( - label='ostf_2_in', + label="ostf_2_in", inputs={ bgas: Flow( - nominal_value=60, min=0.5, max=1.0, - nonconvex=NonConvex()), + nominal_value=60, min=0.5, max=1.0, nonconvex=NonConvex() + ), bcoal: Flow( - nominal_value=30, min=0.3, max=1.0, - nonconvex=NonConvex()) + nominal_value=30, min=0.3, max=1.0, nonconvex=NonConvex() + ), }, - coefficients=(20, 0.5)) + coefficients=(20, 0.5), + ) def test_offsettransformer_too_many_output_flows(): """Too many Output Flows defined.""" with pytest.raises( - ValueError, match='OffsetTransformer` must not have more than 1'): - bm1 = Bus(label='my_offset_Bus1') - bm2 = Bus(label='my_offset_Bus2') + ValueError, match="OffsetTransformer` must not have more than 1" + ): + bm1 = Bus(label="my_offset_Bus1") + bm2 = Bus(label="my_offset_Bus2") components.OffsetTransformer( - label='ostf_2_out', + label="ostf_2_out", inputs={ bm1: Flow( - nominal_value=60, min=0.5, max=1.0, - nonconvex=NonConvex()) + nominal_value=60, min=0.5, max=1.0, nonconvex=NonConvex() + ) }, - outputs={bm1: Flow(), - bm2: Flow()}, - coefficients=(20, 0.5)) + outputs={bm1: Flow(), bm2: Flow()}, + coefficients=(20, 0.5), + ) # ********* GenericCHP ********* def test_generic_chp_without_warning(): warnings.filterwarnings("error", category=SuspiciousUsageWarning) - bel = Bus(label='electricityBus') - bth = Bus(label='heatBus') - bgas = Bus(label='commodityBus') + bel = Bus(label="electricityBus") + bth = Bus(label="heatBus") + bgas = Bus(label="commodityBus") components.GenericCHP( - label='combined_cycle_extraction_turbine', - fuel_input={bgas: Flow( - H_L_FG_share_max=[0.183])}, - electrical_output={bel: Flow( - P_max_woDH=[155.946], - P_min_woDH=[68.787], - Eta_el_max_woDH=[0.525], - Eta_el_min_woDH=[0.444])}, - heat_output={bth: Flow( - Q_CW_min=[10.552])}, - Beta=[0.122], back_pressure=False) + label="combined_cycle_extraction_turbine", + fuel_input={bgas: Flow(H_L_FG_share_max=[0.183])}, + electrical_output={ + bel: Flow( + P_max_woDH=[155.946], + P_min_woDH=[68.787], + Eta_el_max_woDH=[0.525], + Eta_el_min_woDH=[0.444], + ) + }, + heat_output={bth: Flow(Q_CW_min=[10.552])}, + Beta=[0.122], + back_pressure=False, + ) warnings.filterwarnings("always", category=SuspiciousUsageWarning) diff --git a/tests/test_constraints_module.py b/tests/test_constraints_module.py index a4966aaa9..88ffa7ba2 100644 --- a/tests/test_constraints_module.py +++ b/tests/test_constraints_module.py @@ -4,41 +4,53 @@ def test_special(): - date_time_index = pd.date_range('1/1/2012', periods=5, freq='H') + date_time_index = pd.date_range("1/1/2012", periods=5, freq="H") energysystem = solph.EnergySystem(timeindex=date_time_index) - bel = solph.Bus(label='electricityBus') + bel = solph.Bus(label="electricityBus") flow1 = solph.Flow(nominal_value=100, my_factor=0.8) flow2 = solph.Flow(nominal_value=50) - src1 = solph.Source(label='source1', outputs={bel: flow1}) - src2 = solph.Source(label='source2', outputs={bel: flow2}) + src1 = solph.Source(label="source1", outputs={bel: flow1}) + src2 = solph.Source(label="source2", outputs={bel: flow2}) energysystem.add(bel, src1, src2) model = solph.Model(energysystem) - flow_with_keyword = {(src1, bel): flow1, } + flow_with_keyword = { + (src1, bel): flow1, + } solph.constraints.generic_integral_limit( - model, "my_factor", flow_with_keyword, limit=777) + model, "my_factor", flow_with_keyword, limit=777 + ) def test_something_else(): - date_time_index = pd.date_range('1/1/2012', periods=5, freq='H') + date_time_index = pd.date_range("1/1/2012", periods=5, freq="H") energysystem = solph.EnergySystem(timeindex=date_time_index) - bel1 = solph.Bus(label='electricity1') - bel2 = solph.Bus(label='electricity2') + bel1 = solph.Bus(label="electricity1") + bel2 = solph.Bus(label="electricity2") energysystem.add(bel1, bel2) - energysystem.add(solph.Transformer( - label='powerline_1_2', - inputs={bel1: solph.Flow()}, - outputs={bel2: solph.Flow( - investment=solph.Investment(ep_costs=20))})) - energysystem.add(solph.Transformer( - label='powerline_2_1', - inputs={bel2: solph.Flow()}, - outputs={bel1: solph.Flow( - investment=solph.Investment(ep_costs=20))})) + energysystem.add( + solph.Transformer( + label="powerline_1_2", + inputs={bel1: solph.Flow()}, + outputs={ + bel2: solph.Flow(investment=solph.Investment(ep_costs=20)) + }, + ) + ) + energysystem.add( + solph.Transformer( + label="powerline_2_1", + inputs={bel2: solph.Flow()}, + outputs={ + bel1: solph.Flow(investment=solph.Investment(ep_costs=20)) + }, + ) + ) om = solph.Model(energysystem) - line12 = energysystem.groups['powerline_1_2'] - line21 = energysystem.groups['powerline_2_1'] + line12 = energysystem.groups["powerline_1_2"] + line21 = energysystem.groups["powerline_2_1"] solph.constraints.equate_variables( - om, - om.InvestmentFlow.invest[line12, bel2], - om.InvestmentFlow.invest[line21, bel1], - name="my_name") + om, + om.InvestmentFlow.invest[line12, bel2], + om.InvestmentFlow.invest[line21, bel1], + name="my_name", + ) diff --git a/tests/test_models.py b/tests/test_models.py index 28adebc4b..928cbd39f 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -19,7 +19,7 @@ def test_timeincrement_with_valid_timeindex(): - datetimeindex = pd.date_range('1/1/2012', periods=1, freq='H') + datetimeindex = pd.date_range("1/1/2012", periods=1, freq="H") es = solph.EnergySystem(timeindex=datetimeindex) m = solph.models.BaseModel(es) assert m.timeincrement[0] == 1 @@ -45,31 +45,34 @@ def test_timeincrement_list(): def test_nonequ_timeincrement(): - timeindex_hourly = pd.date_range('1/1/2019', periods=2, freq='H') + timeindex_hourly = pd.date_range("1/1/2019", periods=2, freq="H") timeindex_30mins = pd.date_range( - '1/1/2019 03:00:00', periods=2, freq='30min') - timeindex_2h = pd.date_range('1/1/2019 04:00:00', periods=2, freq='2H') + "1/1/2019 03:00:00", periods=2, freq="30min" + ) + timeindex_2h = pd.date_range("1/1/2019 04:00:00", periods=2, freq="2H") timeindex = timeindex_hourly.append([timeindex_30mins, timeindex_2h]) timeincrement = calculate_timeincrement(timeindex=timeindex) assert timeincrement == solph.sequence([1.0, 1.0, 2.0, 0.5, 0.5, 2.0]) def test_nonequ_timeincrement_fill(): - timeindex_hourly = pd.date_range('1/1/2019', periods=2, freq='H') + timeindex_hourly = pd.date_range("1/1/2019", periods=2, freq="H") timeindex_30mins = pd.date_range( - '1/1/2019 03:00:00', periods=2, freq='30min') - timeindex_2h = pd.date_range('1/1/2019 04:00:00', periods=2, freq='2H') + "1/1/2019 03:00:00", periods=2, freq="30min" + ) + timeindex_2h = pd.date_range("1/1/2019 04:00:00", periods=2, freq="2H") timeindex = timeindex_hourly.append([timeindex_30mins, timeindex_2h]) fvalue = pd.Timedelta(hours=9) - timeincrement = calculate_timeincrement(timeindex=timeindex, - fill_value=fvalue) + timeincrement = calculate_timeincrement( + timeindex=timeindex, fill_value=fvalue + ) assert timeincrement == solph.sequence([9.0, 1.0, 2.0, 0.5, 0.5, 2.0]) def test_nonequ_duplicate_timeindex(): with pytest.raises(IndexError): - timeindex_hourly = pd.date_range('1/1/2019', periods=2, freq='H') - timeindex_45mins = pd.date_range('1/1/2019', periods=2, freq='45min') + timeindex_hourly = pd.date_range("1/1/2019", periods=2, freq="H") + timeindex_45mins = pd.date_range("1/1/2019", periods=2, freq="45min") timeindex = timeindex_hourly.append([timeindex_45mins]) calculate_timeincrement(timeindex=timeindex) @@ -82,35 +85,40 @@ def test_nonequ_with_non_valid_timeindex(): def test_nonequ_with_non_valid_fill(): with pytest.raises(AttributeError): - timeindex = pd.date_range('1/1/2019', periods=2, freq='H') + timeindex = pd.date_range("1/1/2019", periods=2, freq="H") fill_value = 2 calculate_timeincrement(timeindex=timeindex, fill_value=fill_value) def test_optimal_solution(): es = solph.EnergySystem(timeindex=[1]) - bel = solph.Bus(label='bus') + bel = solph.Bus(label="bus") es.add(bel) - es.add(solph.Sink(inputs={bel: solph.Flow( - nominal_value=5, fix=[1])})) + es.add(solph.Sink(inputs={bel: solph.Flow(nominal_value=5, fix=[1])})) es.add(solph.Source(outputs={bel: solph.Flow(variable_costs=5)})) m = solph.models.Model(es, timeincrement=1) - m.solve('cbc') + m.solve("cbc") m.results() solph.processing.meta_results(m) def test_infeasible_model(): - with pytest.raises(ValueError, match=''): + with pytest.raises(ValueError, match=""): with warnings.catch_warnings(record=True) as w: es = solph.EnergySystem(timeindex=[1]) - bel = solph.Bus(label='bus') + bel = solph.Bus(label="bus") es.add(bel) - es.add(solph.Sink(inputs={bel: solph.Flow( - nominal_value=5, fix=[1])})) - es.add(solph.Source(outputs={bel: solph.Flow( - nominal_value=4, variable_costs=5)})) + es.add( + solph.Sink(inputs={bel: solph.Flow(nominal_value=5, fix=[1])}) + ) + es.add( + solph.Source( + outputs={ + bel: solph.Flow(nominal_value=4, variable_costs=5) + } + ) + ) m = solph.models.Model(es, timeincrement=1) - m.solve(solver='cbc') + m.solve(solver="cbc") assert "Optimization ended with status" in str(w[0].message) solph.processing.meta_results(m) diff --git a/tests/test_outputlib/__init__.py b/tests/test_outputlib/__init__.py index db31f8b60..3bd2b905e 100644 --- a/tests/test_outputlib/__init__.py +++ b/tests/test_outputlib/__init__.py @@ -1,4 +1,3 @@ - import os import pandas as pd @@ -11,147 +10,140 @@ from oemof.solph import Source from oemof.solph import Transformer -filename = os.path.join(os.path.dirname(__file__), 'input_data.csv') +filename = os.path.join(os.path.dirname(__file__), "input_data.csv") data = pd.read_csv(filename, sep=",") -bcoal = Bus(label='coal', balanced=False) -bgas = Bus(label='gas', balanced=False) -boil = Bus(label='oil', balanced=False) -blig = Bus(label='lignite', balanced=False) +bcoal = Bus(label="coal", balanced=False) +bgas = Bus(label="gas", balanced=False) +boil = Bus(label="oil", balanced=False) +blig = Bus(label="lignite", balanced=False) # electricity and heat -bel = Bus(label='b_el') -bth = Bus(label='b_th') +bel = Bus(label="b_el") +bth = Bus(label="b_th") # an excess and a shortage variable can help to avoid infeasible problems -excess_el = Sink(label='excess_el', inputs={bel: Flow()}) +excess_el = Sink(label="excess_el", inputs={bel: Flow()}) # shortage_el = Source(label='shortage_el', # outputs={bel: Flow(variable_costs=200)}) # sources wind = Source( - label='wind', + label="wind", outputs={ bel: Flow( - fix=data['wind'], + fix=data["wind"], nominal_value=66.3, ) - } + }, ) pv = Source( - label='pv', + label="pv", outputs={ bel: Flow( - fix=data['pv'], + fix=data["pv"], nominal_value=65.3, ) - } + }, ) # demands (electricity/heat) demand_el = Sink( - label='demand_elec', + label="demand_elec", inputs={ bel: Flow( nominal_value=85, - fix=data['demand_el'], + fix=data["demand_el"], ) - } + }, ) demand_th = Sink( - label='demand_therm', + label="demand_therm", inputs={ bth: Flow( nominal_value=40, - fix=data['demand_th'], + fix=data["demand_th"], ) - } + }, ) # power plants pp_coal = Transformer( - label='pp_coal', + label="pp_coal", inputs={bcoal: Flow()}, - outputs={ - bel: Flow( - nominal_value=20.2, - variable_costs=25 - ) - }, - conversion_factors={bel: 0.39} + outputs={bel: Flow(nominal_value=20.2, variable_costs=25)}, + conversion_factors={bel: 0.39}, ) pp_lig = Transformer( - label='pp_lig', + label="pp_lig", inputs={blig: Flow()}, - outputs={ - bel: Flow( - nominal_value=11.8, - variable_costs=19 - ) - }, - conversion_factors={bel: 0.41} + outputs={bel: Flow(nominal_value=11.8, variable_costs=19)}, + conversion_factors={bel: 0.41}, ) pp_gas = Transformer( - label='pp_gas', + label="pp_gas", inputs={bgas: Flow()}, - outputs={ - bel: Flow( - nominal_value=41, - variable_costs=40 - ) - }, - conversion_factors={bel: 0.50} + outputs={bel: Flow(nominal_value=41, variable_costs=40)}, + conversion_factors={bel: 0.50}, ) pp_oil = Transformer( - label='pp_oil', + label="pp_oil", inputs={boil: Flow()}, - outputs={ - bel: Flow( - nominal_value=5, - variable_costs=50 - ) - }, - conversion_factors={bel: 0.28} + outputs={bel: Flow(nominal_value=5, variable_costs=50)}, + conversion_factors={bel: 0.28}, ) # combined heat and power plant (chp) pp_chp = Transformer( - label='pp_chp', + label="pp_chp", inputs={bgas: Flow()}, outputs={ - bel: Flow( - nominal_value=30, - variable_costs=42 - ), - bth: Flow(nominal_value=40) + bel: Flow(nominal_value=30, variable_costs=42), + bth: Flow(nominal_value=40), }, - conversion_factors={bel: 0.3, bth: 0.4} + conversion_factors={bel: 0.3, bth: 0.4}, ) # heatpump with a coefficient of performance (COP) of 3 -b_heat_source = Bus(label='b_heat_source') +b_heat_source = Bus(label="b_heat_source") -heat_source = Source(label='heat_source', outputs={b_heat_source: Flow()}) +heat_source = Source(label="heat_source", outputs={b_heat_source: Flow()}) cop = 3 heat_pump = Transformer( - label='heat_pump', + label="heat_pump", inputs={bel: Flow(), b_heat_source: Flow()}, outputs={bth: Flow(nominal_value=10)}, - conversion_factors={bel: 1 / 3, b_heat_source: (cop - 1) / cop} + conversion_factors={bel: 1 / 3, b_heat_source: (cop - 1) / cop}, ) -datetimeindex = pd.date_range('1/1/2012', periods=24, freq='H') +datetimeindex = pd.date_range("1/1/2012", periods=24, freq="H") energysystem = EnergySystem(timeindex=datetimeindex) energysystem.add( - bcoal, bgas, boil, bel, bth, blig, excess_el, wind, pv, demand_el, - demand_th, pp_coal, pp_lig, pp_oil, pp_gas, pp_chp, b_heat_source, - heat_source, heat_pump + bcoal, + bgas, + boil, + bel, + bth, + blig, + excess_el, + wind, + pv, + demand_el, + demand_th, + pp_coal, + pp_lig, + pp_oil, + pp_gas, + pp_chp, + b_heat_source, + heat_source, + heat_pump, ) # ################################ optimization ########################### diff --git a/tests/test_outputlib/test_views.py b/tests/test_outputlib/test_views.py index 43a564134..83f63e0b1 100644 --- a/tests/test_outputlib/test_views.py +++ b/tests/test_outputlib/test_views.py @@ -1,4 +1,3 @@ - from nose.tools import eq_ from nose.tools import raises @@ -26,7 +25,7 @@ def test_filter_only_sources(self): nodes = views.filter_nodes( self.results, option=views.NodeOption.HasOnlyOutputs, - exclude_busses=True + exclude_busses=True, ) eq_(len(nodes), 3) @@ -34,7 +33,7 @@ def test_filter_only_sinks(self): nodes = views.filter_nodes( self.results, option=views.NodeOption.HasOnlyInputs, - exclude_busses=True + exclude_busses=True, ) eq_(len(nodes), 3) @@ -42,49 +41,28 @@ def test_filter_no_sources(self): nodes = views.filter_nodes( self.results, option=views.NodeOption.HasInputs, - exclude_busses=True + exclude_busses=True, ) eq_(len(nodes), 9) def test_filter_has_outputs(self): - nodes = views.filter_nodes( - self.results, - option='has_outputs' - ) + nodes = views.filter_nodes(self.results, option="has_outputs") eq_(len(nodes), 16) @raises(ValueError) def test_filter_has_something(self): - views.filter_nodes( - self.results, - option='has_something' - ) + views.filter_nodes(self.results, option="has_something") def test_get_node_by_name(self): - node = views.get_node_by_name(self.results, 'heat_pump') - eq_( - energysystem.groups['heat_pump'], - node - ) + node = views.get_node_by_name(self.results, "heat_pump") + eq_(energysystem.groups["heat_pump"], node) def test_get_multiple_nodes_by_name(self): - node1, node2 = views.get_node_by_name(self.results, 'b_el', 'pp_oil') - eq_( - energysystem.groups['b_el'], - node1 - ) - eq_( - energysystem.groups['pp_oil'], - node2 - ) + node1, node2 = views.get_node_by_name(self.results, "b_el", "pp_oil") + eq_(energysystem.groups["b_el"], node1) + eq_(energysystem.groups["pp_oil"], node2) def test_get_node_by_name_with_wrong_attribute(self): - node1, node2 = views.get_node_by_name(self.results, 'b_el', 'wrong') - eq_( - energysystem.groups['b_el'], - node1 - ) - eq_( - None, - node2 - ) + node1, node2 = views.get_node_by_name(self.results, "b_el", "wrong") + eq_(energysystem.groups["b_el"], node1) + eq_(None, node2) diff --git a/tests/test_processing.py b/tests/test_processing.py index 1e3002329..31a63aff5 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -34,39 +34,36 @@ def setup_class(cls): cls.period = 24 cls.es = EnergySystem( timeindex=pandas.date_range( - '2016-01-01', - periods=cls.period, - freq='H' + "2016-01-01", periods=cls.period, freq="H" ) ) # BUSSES b_el1 = Bus(label="b_el1") b_el2 = Bus(label="b_el2") - b_diesel = Bus(label='b_diesel', balanced=False) + b_diesel = Bus(label="b_diesel", balanced=False) cls.es.add(b_el1, b_el2, b_diesel) # TEST DIESEL: dg = Transformer( - label='diesel', + label="diesel", inputs={b_diesel: Flow(variable_costs=2)}, outputs={ b_el1: Flow( - variable_costs=1, - investment=Investment(ep_costs=0.5) + variable_costs=1, investment=Investment(ep_costs=0.5) ) }, conversion_factors={b_el1: 2}, ) batt = GenericStorage( - label='storage', + label="storage", inputs={b_el1: Flow(variable_costs=3)}, outputs={b_el2: Flow(variable_costs=2.5)}, loss_rate=0.00, initial_storage_level=0, - invest_relation_input_capacity=1/6, - invest_relation_output_capacity=1/6, + invest_relation_input_capacity=1 / 6, + invest_relation_output_capacity=1 / 6, inflow_conversion_factor=1, outflow_conversion_factor=0.8, investment=Investment(ep_costs=0.4), @@ -80,7 +77,7 @@ def setup_class(cls): nominal_value=1, fix=cls.demand_values, ) - } + }, ) cls.es.add(dg, batt, demand) cls.om = Model(cls.es) @@ -90,216 +87,233 @@ def setup_class(cls): cls.mod.solve() def test_flows_with_none_exclusion(self): - b_el2 = self.es.groups['b_el2'] - demand = self.es.groups['demand_el'] - param_results = processing.parameter_as_dict(self.es, - exclude_none=True) + b_el2 = self.es.groups["b_el2"] + demand = self.es.groups["demand_el"] + param_results = processing.parameter_as_dict( + self.es, exclude_none=True + ) assert_series_equal( - param_results[(b_el2, demand)]['scalars'].sort_index(), + param_results[(b_el2, demand)]["scalars"].sort_index(), pandas.Series( { - 'nominal_value': 1, - 'max': 1, - 'min': 0, - 'negative_gradient_costs': 0, - 'positive_gradient_costs': 0, - 'variable_costs': 0, - 'label': str(b_el2.outputs[demand].label), + "nominal_value": 1, + "max": 1, + "min": 0, + "negative_gradient_costs": 0, + "positive_gradient_costs": 0, + "variable_costs": 0, + "label": str(b_el2.outputs[demand].label), } - ).sort_index() + ).sort_index(), ) assert_frame_equal( - param_results[(b_el2, demand)]['sequences'], - pandas.DataFrame( - {'fix': self.demand_values} - ), check_like=True + param_results[(b_el2, demand)]["sequences"], + pandas.DataFrame({"fix": self.demand_values}), + check_like=True, ) def test_flows_without_none_exclusion(self): - b_el2 = self.es.groups['b_el2'] - demand = self.es.groups['demand_el'] - param_results = processing.parameter_as_dict(self.es, - exclude_none=False) + b_el2 = self.es.groups["b_el2"] + demand = self.es.groups["demand_el"] + param_results = processing.parameter_as_dict( + self.es, exclude_none=False + ) scalar_attributes = { - 'integer': None, - 'investment': None, - 'nominal_value': 1, - 'nonconvex': None, - 'summed_max': None, - 'summed_min': None, - 'max': 1, - 'min': 0, - 'negative_gradient_ub': None, - 'negative_gradient_costs': 0, - 'positive_gradient_ub': None, - 'positive_gradient_costs': 0, - 'variable_costs': 0, - 'flow': None, - 'values': None, - 'label': str(b_el2.outputs[demand].label), + "integer": None, + "investment": None, + "nominal_value": 1, + "nonconvex": None, + "summed_max": None, + "summed_min": None, + "max": 1, + "min": 0, + "negative_gradient_ub": None, + "negative_gradient_costs": 0, + "positive_gradient_ub": None, + "positive_gradient_costs": 0, + "variable_costs": 0, + "flow": None, + "values": None, + "label": str(b_el2.outputs[demand].label), } assert_series_equal( - param_results[(b_el2, demand)]['scalars'].sort_index(), - pandas.Series(scalar_attributes).sort_index() + param_results[(b_el2, demand)]["scalars"].sort_index(), + pandas.Series(scalar_attributes).sort_index(), ) sequences_attributes = { - 'fix': self.demand_values, + "fix": self.demand_values, } - default_sequences = [ - 'fix' - ] + default_sequences = ["fix"] for attr in default_sequences: if attr not in sequences_attributes: sequences_attributes[attr] = [None] assert_frame_equal( - param_results[(b_el2, demand)]['sequences'], - pandas.DataFrame(sequences_attributes), check_like=True + param_results[(b_el2, demand)]["sequences"], + pandas.DataFrame(sequences_attributes), + check_like=True, ) def test_nodes_with_none_exclusion(self): param_results = processing.parameter_as_dict( - self.es, exclude_none=True) + self.es, exclude_none=True + ) param_results = processing.convert_keys_to_strings(param_results) assert_series_equal( - param_results[('storage', 'None')]['scalars'], - pandas.Series({ - 'balanced': True, - 'initial_storage_level': 0, - 'invest_relation_input_capacity': 1/6, - 'invest_relation_output_capacity': 1/6, - 'investment_ep_costs': 0.4, - 'investment_existing': 0, - 'investment_maximum': float('inf'), - 'investment_minimum': 0, - 'investment_nonconvex': False, - 'investment_offset': 0, - 'label': 'storage', - 'fixed_losses_absolute': 0, - 'fixed_losses_relative': 0, - 'inflow_conversion_factor': 1, - 'loss_rate': 0, - 'max_storage_level': 1, - 'min_storage_level': 0, - 'outflow_conversion_factor': 0.8, - }) + param_results[("storage", "None")]["scalars"], + pandas.Series( + { + "balanced": True, + "initial_storage_level": 0, + "invest_relation_input_capacity": 1 / 6, + "invest_relation_output_capacity": 1 / 6, + "investment_ep_costs": 0.4, + "investment_existing": 0, + "investment_maximum": float("inf"), + "investment_minimum": 0, + "investment_nonconvex": False, + "investment_offset": 0, + "label": "storage", + "fixed_losses_absolute": 0, + "fixed_losses_relative": 0, + "inflow_conversion_factor": 1, + "loss_rate": 0, + "max_storage_level": 1, + "min_storage_level": 0, + "outflow_conversion_factor": 0.8, + } + ), ) assert_frame_equal( - param_results[('storage', 'None')]['sequences'], - pandas.DataFrame() + param_results[("storage", "None")]["sequences"], pandas.DataFrame() ) def test_nodes_with_none_exclusion_old_name(self): param_results = processing.parameter_as_dict( - self.es, exclude_none=True) + self.es, exclude_none=True + ) param_results = processing.convert_keys_to_strings( - param_results, keep_none_type=True) + param_results, keep_none_type=True + ) assert_series_equal( - param_results[('storage', None)]['scalars'], - pandas.Series({ - 'balanced': True, - 'initial_storage_level': 0, - 'invest_relation_input_capacity': 1/6, - 'invest_relation_output_capacity': 1/6, - 'investment_ep_costs': 0.4, - 'investment_existing': 0, - 'investment_maximum': float('inf'), - 'investment_minimum': 0, - 'investment_nonconvex': False, - 'investment_offset': 0, - 'label': 'storage', - 'fixed_losses_absolute': 0, - 'fixed_losses_relative': 0, - 'inflow_conversion_factor': 1, - 'loss_rate': 0, - 'max_storage_level': 1, - 'min_storage_level': 0, - 'outflow_conversion_factor': 0.8, - }) + param_results[("storage", None)]["scalars"], + pandas.Series( + { + "balanced": True, + "initial_storage_level": 0, + "invest_relation_input_capacity": 1 / 6, + "invest_relation_output_capacity": 1 / 6, + "investment_ep_costs": 0.4, + "investment_existing": 0, + "investment_maximum": float("inf"), + "investment_minimum": 0, + "investment_nonconvex": False, + "investment_offset": 0, + "label": "storage", + "fixed_losses_absolute": 0, + "fixed_losses_relative": 0, + "inflow_conversion_factor": 1, + "loss_rate": 0, + "max_storage_level": 1, + "min_storage_level": 0, + "outflow_conversion_factor": 0.8, + } + ), ) assert_frame_equal( - param_results[('storage', None)]['sequences'], - pandas.DataFrame() + param_results[("storage", None)]["sequences"], pandas.DataFrame() ) def test_nodes_without_none_exclusion(self): - diesel = self.es.groups['diesel'] + diesel = self.es.groups["diesel"] param_results = processing.parameter_as_dict( - self.es, exclude_none=False) + self.es, exclude_none=False + ) assert_series_equal( - param_results[(diesel, None)]['scalars'], - pandas.Series({ - 'label': 'diesel', - 'conversion_factors_b_el1': 2, - 'conversion_factors_b_diesel': 1, - }) + param_results[(diesel, None)]["scalars"], + pandas.Series( + { + "label": "diesel", + "conversion_factors_b_el1": 2, + "conversion_factors_b_diesel": 1, + } + ), ) assert_frame_equal( - param_results[(diesel, None)]['sequences'], - pandas.DataFrame() + param_results[(diesel, None)]["sequences"], pandas.DataFrame() ) def test_parameter_with_node_view(self): param_results = processing.parameter_as_dict( - self.es, exclude_none=True) - bel1 = views.node(param_results, 'b_el1') + self.es, exclude_none=True + ) + bel1 = views.node(param_results, "b_el1") assert ( - bel1['scalars'][[(('b_el1', 'storage'), 'variable_costs')]].values + bel1["scalars"][[(("b_el1", "storage"), "variable_costs")]].values == 3 ) - bel1_m = views.node(param_results, 'b_el1', multiindex=True) - eq_(bel1_m['scalars'][('b_el1', 'storage', 'variable_costs')], 3) + bel1_m = views.node(param_results, "b_el1", multiindex=True) + eq_(bel1_m["scalars"][("b_el1", "storage", "variable_costs")], 3) def test_multiindex_sequences(self): results = processing.results(self.om) - bel1 = views.node(results, 'b_el1', multiindex=True) - eq_(int(bel1['sequences'][('diesel', 'b_el1', 'flow')].sum()), 2875) + bel1 = views.node(results, "b_el1", multiindex=True) + eq_(int(bel1["sequences"][("diesel", "b_el1", "flow")].sum()), 2875) def test_error_from_nan_values(self): - trsf = self.es.groups['diesel'] - bus = self.es.groups['b_el1'] - self.mod.flow[trsf, bus, 5] = float('nan') + trsf = self.es.groups["diesel"] + bus = self.es.groups["b_el1"] + self.mod.flow[trsf, bus, 5] = float("nan") with assert_raises(ValueError): processing.results(self.mod) def test_duals(self): results = processing.results(self.om) - bel = views.node(results, 'b_el1', multiindex=True) - eq_(int(bel['sequences']['b_el1', 'None', 'duals'].sum()), 48) + bel = views.node(results, "b_el1", multiindex=True) + eq_(int(bel["sequences"]["b_el1", "None", "duals"].sum()), 48) def test_node_weight_by_type(self): results = processing.results(self.om) storage_content = views.node_weight_by_type( - results, node_type=GenericStorage) - eq_(round(float(storage_content.sum()), 6), - 1437.500003) + results, node_type=GenericStorage + ) + eq_(round(float(storage_content.sum()), 6), 1437.500003) def test_output_by_type_view(self): results = processing.results(self.om) - transformer_output = views.node_output_by_type(results, - node_type=Transformer) - compare = views.node( - results, 'diesel', multiindex=True)['sequences'][( - 'diesel', 'b_el1', 'flow')] + transformer_output = views.node_output_by_type( + results, node_type=Transformer + ) + compare = views.node(results, "diesel", multiindex=True)["sequences"][ + ("diesel", "b_el1", "flow") + ] eq_(int(transformer_output.sum()), int(compare.sum())) def test_input_by_type_view(self): results = processing.results(self.om) sink_input = views.node_input_by_type(results, node_type=Sink) - compare = views.node(results, 'demand_el', multiindex=True) - eq_(int(sink_input.sum()), - int(compare['sequences'][('b_el2', 'demand_el', 'flow')].sum())) + compare = views.node(results, "demand_el", multiindex=True) + eq_( + int(sink_input.sum()), + int(compare["sequences"][("b_el2", "demand_el", "flow")].sum()), + ) def test_net_storage_flow(self): results = processing.results(self.om) storage_flow = views.net_storage_flow( - results, node_type=GenericStorage) - compare = views.node( - results, 'storage', multiindex=True)['sequences'] + results, node_type=GenericStorage + ) + compare = views.node(results, "storage", multiindex=True)["sequences"] eq_( - ((compare[('storage', 'b_el2', 'flow')] - - compare[('b_el1', 'storage', 'flow')]).to_frame() == - storage_flow.values).all()[0], True) + ( + ( + compare[("storage", "b_el2", "flow")] + - compare[("b_el1", "storage", "flow")] + ).to_frame() + == storage_flow.values + ).all()[0], + True, + ) def test_output_by_type_view_empty(self): results = processing.results(self.om) diff --git a/tests/test_scripts/test_solph/test_connect_invest/test_connect_invest.py b/tests/test_scripts/test_solph/test_connect_invest/test_connect_invest.py index 4c0d055d2..792fc6665 100644 --- a/tests/test_scripts/test_solph/test_connect_invest/test_connect_invest.py +++ b/tests/test_scripts/test_solph/test_connect_invest/test_connect_invest.py @@ -32,88 +32,104 @@ def test_connect_invest(): - date_time_index = pd.date_range('1/1/2012', periods=24 * 7, freq='H') + date_time_index = pd.date_range("1/1/2012", periods=24 * 7, freq="H") energysystem = EnergySystem(timeindex=date_time_index) network.Node.registry = energysystem # Read data file - full_filename = os.path.join(os.path.dirname(__file__), - 'connect_invest.csv') + full_filename = os.path.join( + os.path.dirname(__file__), "connect_invest.csv" + ) data = pd.read_csv(full_filename, sep=",") - logging.info('Create oemof objects') + logging.info("Create oemof objects") # create electricity bus bel1 = Bus(label="electricity1") bel2 = Bus(label="electricity2") # create excess component for the electricity bus to allow overproduction - Sink(label='excess_bel', inputs={bel2: Flow()}) - Source(label='shortage', outputs={bel2: Flow( - variable_costs=50000)}) + Sink(label="excess_bel", inputs={bel2: Flow()}) + Source(label="shortage", outputs={bel2: Flow(variable_costs=50000)}) # create fixed source object representing wind power plants - Source(label='wind', outputs={bel1: Flow( - fix=data['wind'], nominal_value=1000000)}) + Source( + label="wind", + outputs={bel1: Flow(fix=data["wind"], nominal_value=1000000)}, + ) # create simple sink object representing the electrical demand - Sink(label='demand', inputs={bel1: Flow( - fix=data['demand_el'], nominal_value=1)}) + Sink( + label="demand", + inputs={bel1: Flow(fix=data["demand_el"], nominal_value=1)}, + ) storage = components.GenericStorage( - label='storage', + label="storage", inputs={bel1: Flow(variable_costs=10e10)}, outputs={bel1: Flow(variable_costs=10e10)}, - loss_rate=0.00, initial_storage_level=0, - invest_relation_input_capacity=1/6, - invest_relation_output_capacity=1/6, - inflow_conversion_factor=1, outflow_conversion_factor=0.8, + loss_rate=0.00, + initial_storage_level=0, + invest_relation_input_capacity=1 / 6, + invest_relation_output_capacity=1 / 6, + inflow_conversion_factor=1, + outflow_conversion_factor=0.8, investment=Investment(ep_costs=0.2), ) line12 = Transformer( label="line12", inputs={bel1: Flow()}, - outputs={bel2: Flow(investment=Investment(ep_costs=20))}) + outputs={bel2: Flow(investment=Investment(ep_costs=20))}, + ) line21 = Transformer( label="line21", inputs={bel2: Flow()}, - outputs={bel1: Flow(investment=Investment(ep_costs=20))}) + outputs={bel1: Flow(investment=Investment(ep_costs=20))}, + ) om = Model(energysystem) constraints.equate_variables( - om, om.InvestmentFlow.invest[line12, bel2], - om.InvestmentFlow.invest[line21, bel1], 2) + om, + om.InvestmentFlow.invest[line12, bel2], + om.InvestmentFlow.invest[line21, bel1], + 2, + ) constraints.equate_variables( - om, om.InvestmentFlow.invest[line12, bel2], - om.GenericInvestmentStorageBlock.invest[storage]) + om, + om.InvestmentFlow.invest[line12, bel2], + om.GenericInvestmentStorageBlock.invest[storage], + ) # if tee_switch is true solver messages will be displayed - logging.info('Solve the optimization problem') - om.solve(solver='cbc') + logging.info("Solve the optimization problem") + om.solve(solver="cbc") # check if the new result object is working for custom components results = processing.results(om) my_results = dict() - my_results['line12'] = float(views.node(results, 'line12')['scalars']) - my_results['line21'] = float(views.node(results, 'line21')['scalars']) - stor_res = views.node(results, 'storage')['scalars'] - my_results['storage_in'] = stor_res[[ - (('electricity1', 'storage'), 'invest')]] - my_results['storage'] = stor_res[[(('storage', 'None'), 'invest')]] - my_results['storage_out'] = stor_res[[ - (('storage', 'electricity1'), 'invest')]] + my_results["line12"] = float(views.node(results, "line12")["scalars"]) + my_results["line21"] = float(views.node(results, "line21")["scalars"]) + stor_res = views.node(results, "storage")["scalars"] + my_results["storage_in"] = stor_res[ + [(("electricity1", "storage"), "invest")] + ] + my_results["storage"] = stor_res[[(("storage", "None"), "invest")]] + my_results["storage_out"] = stor_res[ + [(("storage", "electricity1"), "invest")] + ] connect_invest_dict = { - 'line12': 814705, - 'line21': 1629410, - 'storage': 814705, - 'storage_in': 135784, - 'storage_out': 135784} + "line12": 814705, + "line21": 1629410, + "storage": 814705, + "storage_in": 135784, + "storage_out": 135784, + } for key in connect_invest_dict.keys(): eq_(int(round(my_results[key])), int(round(connect_invest_dict[key]))) diff --git a/tests/test_scripts/test_solph/test_flexible_modelling/test_add_constraints.py b/tests/test_scripts/test_solph/test_flexible_modelling/test_add_constraints.py index 21a9399ed..5cfaee4e6 100644 --- a/tests/test_scripts/test_solph/test_flexible_modelling/test_add_constraints.py +++ b/tests/test_scripts/test_solph/test_flexible_modelling/test_add_constraints.py @@ -29,31 +29,34 @@ from oemof.solph import Transformer -def test_add_constraints_example(solver='cbc', nologg=False): +def test_add_constraints_example(solver="cbc", nologg=False): if not nologg: logging.basicConfig(level=logging.INFO) # ##### creating an oemof solph optimization model, nothing special here ## # create an energy system object for the oemof solph nodes - es = EnergySystem(timeindex=pd.date_range('1/1/2012', periods=4, freq='H')) + es = EnergySystem(timeindex=pd.date_range("1/1/2012", periods=4, freq="H")) Node.registry = es # add some nodes boil = Bus(label="oil", balanced=False) blig = Bus(label="lignite", balanced=False) b_el = Bus(label="b_el") - Sink(label="Sink", - inputs={b_el: Flow(nominal_value=40, - fix=[0.5, 0.4, 0.3, 1])}) - pp_oil = Transformer(label='pp_oil', - inputs={boil: Flow()}, - outputs={b_el: Flow(nominal_value=50, - variable_costs=25)}, - conversion_factors={b_el: 0.39}) - Transformer(label='pp_lig', - inputs={blig: Flow()}, - outputs={b_el: Flow(nominal_value=50, - variable_costs=10)}, - conversion_factors={b_el: 0.41}) + Sink( + label="Sink", + inputs={b_el: Flow(nominal_value=40, fix=[0.5, 0.4, 0.3, 1])}, + ) + pp_oil = Transformer( + label="pp_oil", + inputs={boil: Flow()}, + outputs={b_el: Flow(nominal_value=50, variable_costs=25)}, + conversion_factors={b_el: 0.39}, + ) + Transformer( + label="pp_lig", + inputs={blig: Flow()}, + outputs={b_el: Flow(nominal_value=50, variable_costs=10)}, + conversion_factors={b_el: 0.41}, + ) # create the model om = Model(energysystem=es) @@ -78,32 +81,44 @@ def test_add_constraints_example(solver='cbc', nologg=False): # create a pyomo set with the flows (i.e. list of tuples), # there will of course be only one flow inside this set, the one we used to # add outflow_share - myblock.MYFLOWS = po.Set(initialize=[k for (k, v) in om.flows.items() - if hasattr(v, 'outflow_share')]) + myblock.MYFLOWS = po.Set( + initialize=[ + k for (k, v) in om.flows.items() if hasattr(v, "outflow_share") + ] + ) # pyomo does not need a po.Set, we can use a simple list as well - myblock.COMMODITYFLOWS = [k for (k, v) in om.flows.items() - if hasattr(v, 'emission_factor')] + myblock.COMMODITYFLOWS = [ + k for (k, v) in om.flows.items() if hasattr(v, "emission_factor") + ] # add the sub-model to the oemof Model instance - om.add_component('MyBlock', myblock) + om.add_component("MyBlock", myblock) def _inflow_share_rule(m, si, e, ti): """pyomo rule definition: Here we can use all objects from the block or the om object, in this case we don't need anything from the block except the newly defined set MYFLOWS. """ - expr = (om.flow[si, e, ti] >= om.flows[si, e].outflow_share[ti] * - sum(om.flow[i, o, ti] for (i, o) in om.FLOWS if o == e)) + expr = om.flow[si, e, ti] >= om.flows[si, e].outflow_share[ti] * sum( + om.flow[i, o, ti] for (i, o) in om.FLOWS if o == e + ) return expr - myblock.inflow_share = po.Constraint(myblock.MYFLOWS, om.TIMESTEPS, - rule=_inflow_share_rule) + myblock.inflow_share = po.Constraint( + myblock.MYFLOWS, om.TIMESTEPS, rule=_inflow_share_rule + ) # add emission constraint - myblock.emission_constr = po.Constraint(expr=( - sum(om.flow[i, o, t] + myblock.emission_constr = po.Constraint( + expr=( + sum( + om.flow[i, o, t] for (i, o) in myblock.COMMODITYFLOWS - for t in om.TIMESTEPS) <= emission_limit)) + for t in om.TIMESTEPS + ) + <= emission_limit + ) + ) # solve and write results to dictionary # you may print the model with om.pprint() diff --git a/tests/test_scripts/test_solph/test_generic_caes/test_generic_caes.py b/tests/test_scripts/test_solph/test_generic_caes/test_generic_caes.py index ec3db8756..5d2b23c6d 100644 --- a/tests/test_scripts/test_solph/test_generic_caes/test_generic_caes.py +++ b/tests/test_scripts/test_solph/test_generic_caes/test_generic_caes.py @@ -31,99 +31,107 @@ def test_gen_caes(): # read sequence data - full_filename = os.path.join(os.path.dirname(__file__), 'generic_caes.csv') + full_filename = os.path.join(os.path.dirname(__file__), "generic_caes.csv") data = pd.read_csv(full_filename) # select periods - periods = len(data)-1 + periods = len(data) - 1 # create an energy system - idx = pd.date_range('1/1/2017', periods=periods, freq='H') + idx = pd.date_range("1/1/2017", periods=periods, freq="H") es = EnergySystem(timeindex=idx) Node.registry = es # resources - bgas = Bus(label='bgas') + bgas = Bus(label="bgas") - Source(label='rgas', outputs={ - bgas: Flow(variable_costs=20)}) + Source(label="rgas", outputs={bgas: Flow(variable_costs=20)}) # power - bel_source = Bus(label='bel_source') - Source(label='source_el', outputs={ - bel_source: Flow(variable_costs=data['price_el_source'])}) - - bel_sink = Bus(label='bel_sink') - Sink(label='sink_el', inputs={ - bel_sink: Flow(variable_costs=data['price_el_sink'])}) + bel_source = Bus(label="bel_source") + Source( + label="source_el", + outputs={bel_source: Flow(variable_costs=data["price_el_source"])}, + ) + + bel_sink = Bus(label="bel_sink") + Sink( + label="sink_el", + inputs={bel_sink: Flow(variable_costs=data["price_el_sink"])}, + ) # dictionary with parameters for a specific CAES plant # based on thermal modelling and linearization techniques concept = { - 'cav_e_in_b': 0, - 'cav_e_in_m': 0.6457267578, - 'cav_e_out_b': 0, - 'cav_e_out_m': 0.3739636077, - 'cav_eta_temp': 1.0, - 'cav_level_max': 211.11, - 'cmp_p_max_b': 86.0918959849, - 'cmp_p_max_m': 0.0679999932, - 'cmp_p_min': 1, - 'cmp_q_out_b': -19.3996965679, - 'cmp_q_out_m': 1.1066036114, - 'cmp_q_tes_share': 0, - 'exp_p_max_b': 46.1294016678, - 'exp_p_max_m': 0.2528340303, - 'exp_p_min': 1, - 'exp_q_in_b': -2.2073411014, - 'exp_q_in_m': 1.129249765, - 'exp_q_tes_share': 0, - 'tes_eta_temp': 1.0, - 'tes_level_max': 0.0 + "cav_e_in_b": 0, + "cav_e_in_m": 0.6457267578, + "cav_e_out_b": 0, + "cav_e_out_m": 0.3739636077, + "cav_eta_temp": 1.0, + "cav_level_max": 211.11, + "cmp_p_max_b": 86.0918959849, + "cmp_p_max_m": 0.0679999932, + "cmp_p_min": 1, + "cmp_q_out_b": -19.3996965679, + "cmp_q_out_m": 1.1066036114, + "cmp_q_tes_share": 0, + "exp_p_max_b": 46.1294016678, + "exp_p_max_m": 0.2528340303, + "exp_p_min": 1, + "exp_q_in_b": -2.2073411014, + "exp_q_in_m": 1.129249765, + "exp_q_tes_share": 0, + "tes_eta_temp": 1.0, + "tes_level_max": 0.0, } # generic compressed air energy storage (caes) plant custom.GenericCAES( - label='caes', + label="caes", electrical_input={bel_source: Flow()}, fuel_input={bgas: Flow()}, electrical_output={bel_sink: Flow()}, - params=concept, fixed_costs=0) + params=concept, + fixed_costs=0, + ) # create an optimization problem and solve it om = Model(es) # solve model - om.solve(solver='cbc') + om.solve(solver="cbc") # create result object results = processing.results(om) - data = views.node( - results, 'caes', keep_none_type=True - )['sequences'].sum(axis=0).to_dict() + data = ( + views.node(results, "caes", keep_none_type=True)["sequences"] + .sum(axis=0) + .to_dict() + ) test_dict = { - (('caes', None), 'cav_level'): 25658.82964382, - (('caes', None), 'exp_p'): 5020.801997000007, - (('caes', None), 'exp_q_fuel_in'): 5170.880360999999, - (('caes', None), 'tes_e_out'): 0.0, - (('caes', None), 'exp_st'): 226.0, - (('bgas', 'caes'), 'flow'): 5170.880360999999, - (('caes', None), 'cav_e_out'): 1877.5972265299995, - (('caes', None), 'exp_p_max'): 17512.352336, - (('caes', None), 'cmp_q_waste'): 2499.9125993000007, - (('caes', None), 'cmp_p'): 2907.7271520000004, - (('caes', None), 'exp_q_add_in'): 0.0, - (('caes', None), 'cmp_st'): 37.0, - (('caes', None), 'cmp_q_out_sum'): 2499.9125993000007, - (('caes', None), 'tes_level'): 0.0, - (('caes', None), 'tes_e_in'): 0.0, - (('caes', None), 'exp_q_in_sum'): 5170.880360999999, - (('caes', None), 'cmp_p_max'): 22320.76334300001, - (('caes', 'bel_sink'), 'flow'): 5020.801997000007, - (('bel_source', 'caes'), 'flow'): 2907.7271520000004, - (('caes', None), 'cav_e_in'): 1877.597226} + (("caes", None), "cav_level"): 25658.82964382, + (("caes", None), "exp_p"): 5020.801997000007, + (("caes", None), "exp_q_fuel_in"): 5170.880360999999, + (("caes", None), "tes_e_out"): 0.0, + (("caes", None), "exp_st"): 226.0, + (("bgas", "caes"), "flow"): 5170.880360999999, + (("caes", None), "cav_e_out"): 1877.5972265299995, + (("caes", None), "exp_p_max"): 17512.352336, + (("caes", None), "cmp_q_waste"): 2499.9125993000007, + (("caes", None), "cmp_p"): 2907.7271520000004, + (("caes", None), "exp_q_add_in"): 0.0, + (("caes", None), "cmp_st"): 37.0, + (("caes", None), "cmp_q_out_sum"): 2499.9125993000007, + (("caes", None), "tes_level"): 0.0, + (("caes", None), "tes_e_in"): 0.0, + (("caes", None), "exp_q_in_sum"): 5170.880360999999, + (("caes", None), "cmp_p_max"): 22320.76334300001, + (("caes", "bel_sink"), "flow"): 5020.801997000007, + (("bel_source", "caes"), "flow"): 2907.7271520000004, + (("caes", None), "cav_e_in"): 1877.597226, + } for key in test_dict.keys(): eq_(int(round(data[key])), int(round(test_dict[key]))) diff --git a/tests/test_scripts/test_solph/test_generic_chp/test_generic_chp.py b/tests/test_scripts/test_solph/test_generic_chp/test_generic_chp.py index edc8dbf59..5fbe293ff 100644 --- a/tests/test_scripts/test_solph/test_generic_chp/test_generic_chp.py +++ b/tests/test_scripts/test_solph/test_generic_chp/test_generic_chp.py @@ -25,67 +25,78 @@ def test_gen_chp(): # read sequence data - full_filename = os.path.join(os.path.dirname(__file__), 'ccet.csv') + full_filename = os.path.join(os.path.dirname(__file__), "ccet.csv") data = pd.read_csv(full_filename) # select periods - periods = len(data)-1 + periods = len(data) - 1 # create an energy system - idx = pd.date_range('1/1/2017', periods=periods, freq='H') + idx = pd.date_range("1/1/2017", periods=periods, freq="H") es = solph.EnergySystem(timeindex=idx) Node.registry = es # resources - bgas = solph.Bus(label='bgas') + bgas = solph.Bus(label="bgas") - solph.Source(label='rgas', outputs={bgas: solph.Flow()}) + solph.Source(label="rgas", outputs={bgas: solph.Flow()}) # heat - bth = solph.Bus(label='bth') + bth = solph.Bus(label="bth") - solph.Source(label='source_th', - outputs={bth: solph.Flow(variable_costs=1000)}) + solph.Source( + label="source_th", outputs={bth: solph.Flow(variable_costs=1000)} + ) - solph.Sink(label='demand_th', inputs={bth: solph.Flow( - fix=data['demand_th'], nominal_value=200)}) + solph.Sink( + label="demand_th", + inputs={bth: solph.Flow(fix=data["demand_th"], nominal_value=200)}, + ) # power - bel = solph.Bus(label='bel') + bel = solph.Bus(label="bel") - solph.Sink(label='demand_el', inputs={bel: solph.Flow( - variable_costs=data['price_el'])}) + solph.Sink( + label="demand_el", + inputs={bel: solph.Flow(variable_costs=data["price_el"])}, + ) # generic chp # (for back pressure characteristics Q_CW_min=0 and back_pressure=True) solph.components.GenericCHP( - label='combined_cycle_extraction_turbine', - fuel_input={bgas: solph.Flow( - H_L_FG_share_max=data['H_L_FG_share_max'])}, - electrical_output={bel: solph.Flow( - P_max_woDH=data['P_max_woDH'], - P_min_woDH=data['P_min_woDH'], - Eta_el_max_woDH=data['Eta_el_max_woDH'], - Eta_el_min_woDH=data['Eta_el_min_woDH'])}, - heat_output={bth: solph.Flow( - Q_CW_min=data['Q_CW_min'])}, - Beta=data['Beta'], back_pressure=False) + label="combined_cycle_extraction_turbine", + fuel_input={ + bgas: solph.Flow(H_L_FG_share_max=data["H_L_FG_share_max"]) + }, + electrical_output={ + bel: solph.Flow( + P_max_woDH=data["P_max_woDH"], + P_min_woDH=data["P_min_woDH"], + Eta_el_max_woDH=data["Eta_el_max_woDH"], + Eta_el_min_woDH=data["Eta_el_min_woDH"], + ) + }, + heat_output={bth: solph.Flow(Q_CW_min=data["Q_CW_min"])}, + Beta=data["Beta"], + back_pressure=False, + ) # create an optimization problem and solve it om = solph.Model(es) # solve model - om.solve(solver='cbc') + om.solve(solver="cbc") # create result object results = processing.results(om) - data = views.node(results, 'bth')['sequences'].sum(axis=0).to_dict() + data = views.node(results, "bth")["sequences"].sum(axis=0).to_dict() test_dict = { - (('bth', 'demand_th'), 'flow'): 20000.0, - (('combined_cycle_extraction_turbine', 'bth'), 'flow'): 14070.15215799, - (('source_th', 'bth'), 'flow'): 5929.8478649200015} + (("bth", "demand_th"), "flow"): 20000.0, + (("combined_cycle_extraction_turbine", "bth"), "flow"): 14070.15215799, + (("source_th", "bth"), "flow"): 5929.8478649200015, + } for key in test_dict.keys(): eq_(int(round(data[key])), int(round(test_dict[key]))) diff --git a/tests/test_scripts/test_solph/test_invest_fix_flow/test_simple_invest_fixed.py b/tests/test_scripts/test_solph/test_invest_fix_flow/test_simple_invest_fixed.py index fb11d0f51..0a2c4ff37 100644 --- a/tests/test_scripts/test_solph/test_invest_fix_flow/test_simple_invest_fixed.py +++ b/tests/test_scripts/test_solph/test_invest_fix_flow/test_simple_invest_fixed.py @@ -29,23 +29,20 @@ from oemof.solph import views -def test_dispatch_fix_example(solver='cbc', periods=10): +def test_dispatch_fix_example(solver="cbc", periods=10): """Invest in a flow with a `fix` sequence containing values > 1.""" Node.registry = None - filename = os.path.join(os.path.dirname(__file__), 'input_data.csv') + filename = os.path.join(os.path.dirname(__file__), "input_data.csv") data = pd.read_csv(filename, sep=",") # ######################### create energysystem components ################ # electricity and heat - bel = Bus(label='b_el') + bel = Bus(label="b_el") # an excess and a shortage variable can help to avoid infeasible problems - excess_el = Sink( - label='excess_el', - inputs={bel: Flow()} - ) + excess_el = Sink(label="excess_el", inputs={bel: Flow()}) # shortage_el = Source(label='shortage_el', # outputs={bel: Flow(variable_costs=200)}) @@ -54,27 +51,19 @@ def test_dispatch_fix_example(solver='cbc', periods=10): ep_pv = economics.annuity(capex=1500, n=20, wacc=0.05) pv = Source( - label='pv', + label="pv", outputs={ - bel: Flow( - fix=data['pv'], - investment=Investment(ep_costs=ep_pv) - ) - } + bel: Flow(fix=data["pv"], investment=Investment(ep_costs=ep_pv)) + }, ) # demands (electricity/heat) demand_el = Sink( - label='demand_elec', - inputs={ - bel: Flow( - nominal_value=85, - fix=data['demand_el'] - ) - } + label="demand_elec", + inputs={bel: Flow(nominal_value=85, fix=data["demand_el"])}, ) - datetimeindex = pd.date_range('1/1/2012', periods=periods, freq='H') + datetimeindex = pd.date_range("1/1/2012", periods=periods, freq="H") energysystem = EnergySystem(timeindex=datetimeindex) @@ -98,13 +87,13 @@ def test_dispatch_fix_example(solver='cbc', periods=10): # investment values within a pandas.Series object. # in this case the entry data['scalars'] does not exist since no investment # variables are used - data = views.node(results, 'b_el') + data = views.node(results, "b_el") # generate results to be evaluated in tests - comp_results = data['sequences'].sum(axis=0).to_dict() - comp_results['pv_capacity'] = results[(pv, bel)]['scalars'].invest + comp_results = data["sequences"].sum(axis=0).to_dict() + comp_results["pv_capacity"] = results[(pv, bel)]["scalars"].invest - assert comp_results[(('pv', 'b_el'), 'flow')] > 0 + assert comp_results[(("pv", "b_el"), "flow")] > 0 # test_results = { # (('pv', 'b_el'), 'flow'): 2150.5, diff --git a/tests/test_scripts/test_solph/test_lopf/test_lopf.py b/tests/test_scripts/test_solph/test_lopf/test_lopf.py index 56f1cdc33..6a7226896 100644 --- a/tests/test_scripts/test_solph/test_lopf/test_lopf.py +++ b/tests/test_scripts/test_solph/test_lopf/test_lopf.py @@ -99,11 +99,7 @@ def test_lopf(solver="cbc"): es.add( Sink( label="load", - inputs={ - b_el2: Flow( - nominal_value=100, fix=1 - ) - }, + inputs={b_el2: Flow(nominal_value=100, fix=1)}, ) ) diff --git a/tests/test_scripts/test_solph/test_piecewiselineartransformer/test_piecewiselineartransformer.py b/tests/test_scripts/test_solph/test_piecewiselineartransformer/test_piecewiselineartransformer.py index b89b059b3..f1812bd00 100644 --- a/tests/test_scripts/test_solph/test_piecewiselineartransformer/test_piecewiselineartransformer.py +++ b/tests/test_scripts/test_solph/test_piecewiselineartransformer/test_piecewiselineartransformer.py @@ -44,6 +44,7 @@ def test_pwltf(): # Define conversion function and breakpoints def conv_func(x): return 0.01 * x ** 2 + in_breakpoints = np.arange(0, 110, 25) out_breakpoints = conv_func(in_breakpoints) diff --git a/tests/test_scripts/test_solph/test_simple_model/test_simple_dispatch.py b/tests/test_scripts/test_solph/test_simple_model/test_simple_dispatch.py index 7dea5c4ba..e412e9637 100644 --- a/tests/test_scripts/test_solph/test_simple_model/test_simple_dispatch.py +++ b/tests/test_scripts/test_solph/test_simple_model/test_simple_dispatch.py @@ -29,94 +29,125 @@ from oemof.solph import views -def test_dispatch_example(solver='cbc', periods=24*5): +def test_dispatch_example(solver="cbc", periods=24 * 5): """Create an energy system and optimize the dispatch at least costs.""" - filename = os.path.join(os.path.dirname(__file__), 'input_data.csv') + filename = os.path.join(os.path.dirname(__file__), "input_data.csv") data = pd.read_csv(filename, sep=",") # ######################### create energysystem components ################ # resource buses - bcoal = Bus(label='coal', balanced=False) - bgas = Bus(label='gas', balanced=False) - boil = Bus(label='oil', balanced=False) - blig = Bus(label='lignite', balanced=False) + bcoal = Bus(label="coal", balanced=False) + bgas = Bus(label="gas", balanced=False) + boil = Bus(label="oil", balanced=False) + blig = Bus(label="lignite", balanced=False) # electricity and heat - bel = Bus(label='b_el') - bth = Bus(label='b_th') + bel = Bus(label="b_el") + bth = Bus(label="b_th") # an excess and a shortage variable can help to avoid infeasible problems - excess_el = Sink(label='excess_el', inputs={bel: Flow()}) + excess_el = Sink(label="excess_el", inputs={bel: Flow()}) # shortage_el = Source(label='shortage_el', # outputs={bel: Flow(variable_costs=200)}) # sources - wind = Source(label='wind', outputs={bel: Flow(fix=data['wind'], - nominal_value=66.3)}) + wind = Source( + label="wind", outputs={bel: Flow(fix=data["wind"], nominal_value=66.3)} + ) - pv = Source(label='pv', outputs={bel: Flow(fix=data['pv'], - nominal_value=65.3)}) + pv = Source( + label="pv", outputs={bel: Flow(fix=data["pv"], nominal_value=65.3)} + ) # demands (electricity/heat) - demand_el = Sink(label='demand_elec', inputs={bel: Flow(nominal_value=85, - fix=data['demand_el'])}) + demand_el = Sink( + label="demand_elec", + inputs={bel: Flow(nominal_value=85, fix=data["demand_el"])}, + ) - demand_th = Sink(label='demand_therm', - inputs={bth: Flow(nominal_value=40, - fix=data['demand_th'])}) + demand_th = Sink( + label="demand_therm", + inputs={bth: Flow(nominal_value=40, fix=data["demand_th"])}, + ) # power plants - pp_coal = Transformer(label='pp_coal', - inputs={bcoal: Flow()}, - outputs={bel: Flow(nominal_value=20.2, - variable_costs=25)}, - conversion_factors={bel: 0.39}) - - pp_lig = Transformer(label='pp_lig', - inputs={blig: Flow()}, - outputs={bel: Flow(nominal_value=11.8, - variable_costs=19)}, - conversion_factors={bel: 0.41}) - - pp_gas = Transformer(label='pp_gas', - inputs={bgas: Flow()}, - outputs={bel: Flow(nominal_value=41, - variable_costs=40)}, - conversion_factors={bel: 0.50}) - - pp_oil = Transformer(label='pp_oil', - inputs={boil: Flow()}, - outputs={bel: Flow(nominal_value=5, - variable_costs=50)}, - conversion_factors={bel: 0.28}) + pp_coal = Transformer( + label="pp_coal", + inputs={bcoal: Flow()}, + outputs={bel: Flow(nominal_value=20.2, variable_costs=25)}, + conversion_factors={bel: 0.39}, + ) + + pp_lig = Transformer( + label="pp_lig", + inputs={blig: Flow()}, + outputs={bel: Flow(nominal_value=11.8, variable_costs=19)}, + conversion_factors={bel: 0.41}, + ) + + pp_gas = Transformer( + label="pp_gas", + inputs={bgas: Flow()}, + outputs={bel: Flow(nominal_value=41, variable_costs=40)}, + conversion_factors={bel: 0.50}, + ) + + pp_oil = Transformer( + label="pp_oil", + inputs={boil: Flow()}, + outputs={bel: Flow(nominal_value=5, variable_costs=50)}, + conversion_factors={bel: 0.28}, + ) # combined heat and power plant (chp) - pp_chp = Transformer(label='pp_chp', - inputs={bgas: Flow()}, - outputs={bel: Flow(nominal_value=30, - variable_costs=42), - bth: Flow(nominal_value=40)}, - conversion_factors={bel: 0.3, bth: 0.4}) + pp_chp = Transformer( + label="pp_chp", + inputs={bgas: Flow()}, + outputs={ + bel: Flow(nominal_value=30, variable_costs=42), + bth: Flow(nominal_value=40), + }, + conversion_factors={bel: 0.3, bth: 0.4}, + ) # heatpump with a coefficient of performance (COP) of 3 - b_heat_source = Bus(label='b_heat_source') + b_heat_source = Bus(label="b_heat_source") - heat_source = Source(label='heat_source', outputs={b_heat_source: Flow()}) + heat_source = Source(label="heat_source", outputs={b_heat_source: Flow()}) cop = 3 - heat_pump = Transformer(label='heat_pump', - inputs={bel: Flow(), b_heat_source: Flow()}, - outputs={bth: Flow(nominal_value=10)}, - conversion_factors={ - bel: 1/3, b_heat_source: (cop-1)/cop}) - - datetimeindex = pd.date_range('1/1/2012', periods=periods, freq='H') + heat_pump = Transformer( + label="heat_pump", + inputs={bel: Flow(), b_heat_source: Flow()}, + outputs={bth: Flow(nominal_value=10)}, + conversion_factors={bel: 1 / 3, b_heat_source: (cop - 1) / cop}, + ) + + datetimeindex = pd.date_range("1/1/2012", periods=periods, freq="H") energysystem = EnergySystem(timeindex=datetimeindex) - energysystem.add(bcoal, bgas, boil, bel, bth, blig, excess_el, wind, pv, - demand_el, demand_th, pp_coal, pp_lig, pp_oil, pp_gas, - pp_chp, b_heat_source, heat_source, heat_pump) + energysystem.add( + bcoal, + bgas, + boil, + bel, + bth, + blig, + excess_el, + wind, + pv, + demand_el, + demand_th, + pp_coal, + pp_lig, + pp_oil, + pp_gas, + pp_chp, + b_heat_source, + heat_source, + heat_pump, + ) # ################################ optimization ########################### @@ -141,22 +172,22 @@ def test_dispatch_example(solver='cbc', periods=24*5): # investment values within a pandas.Series object. # in this case the entry data['scalars'] does not exist since no investment # variables are used - data = views.node(results, 'b_el') + data = views.node(results, "b_el") # generate results to be evaluated in tests - results = data['sequences'].sum(axis=0).to_dict() + results = data["sequences"].sum(axis=0).to_dict() test_results = { - (('wind', 'b_el'), 'flow'): 1773, - (('pv', 'b_el'), 'flow'): 605, - (('b_el', 'demand_elec'), 'flow'): 7440, - (('b_el', 'excess_el'), 'flow'): 139, - (('pp_chp', 'b_el'), 'flow'): 666, - (('pp_lig', 'b_el'), 'flow'): 1210, - (('pp_gas', 'b_el'), 'flow'): 1519, - (('pp_coal', 'b_el'), 'flow'): 1925, - (('pp_oil', 'b_el'), 'flow'): 0, - (('b_el', 'heat_pump'), 'flow'): 118, + (("wind", "b_el"), "flow"): 1773, + (("pv", "b_el"), "flow"): 605, + (("b_el", "demand_elec"), "flow"): 7440, + (("b_el", "excess_el"), "flow"): 139, + (("pp_chp", "b_el"), "flow"): 666, + (("pp_lig", "b_el"), "flow"): 1210, + (("pp_gas", "b_el"), "flow"): 1519, + (("pp_coal", "b_el"), "flow"): 1925, + (("pp_oil", "b_el"), "flow"): 0, + (("b_el", "heat_pump"), "flow"): 118, } for key in test_results.keys(): diff --git a/tests/test_scripts/test_solph/test_simple_model/test_simple_dispatch_one.py b/tests/test_scripts/test_solph/test_simple_model/test_simple_dispatch_one.py index 5026c6e23..1088fb5ee 100644 --- a/tests/test_scripts/test_solph/test_simple_model/test_simple_dispatch_one.py +++ b/tests/test_scripts/test_solph/test_simple_model/test_simple_dispatch_one.py @@ -25,57 +25,74 @@ from oemof.solph import views -def test_dispatch_one_time_step(solver='cbc'): +def test_dispatch_one_time_step(solver="cbc"): """Create an energy system and optimize the dispatch at least costs.""" # ######################### create energysystem components ################ Node.registry = None # resource buses - bgas = Bus(label='gas', balanced=False) + bgas = Bus(label="gas", balanced=False) # electricity and heat - bel = Bus(label='b_el') - bth = Bus(label='b_th') + bel = Bus(label="b_el") + bth = Bus(label="b_th") # an excess and a shortage variable can help to avoid infeasible problems - excess_el = Sink(label='excess_el', inputs={bel: Flow()}) + excess_el = Sink(label="excess_el", inputs={bel: Flow()}) # sources - wind = Source(label='wind', outputs={bel: Flow( - fix=0.5, nominal_value=66.3)}) + wind = Source( + label="wind", outputs={bel: Flow(fix=0.5, nominal_value=66.3)} + ) # demands (electricity/heat) - demand_el = Sink(label='demand_elec', inputs={bel: Flow(nominal_value=85, - fix=0.3)}) + demand_el = Sink( + label="demand_elec", inputs={bel: Flow(nominal_value=85, fix=0.3)} + ) - demand_th = Sink(label='demand_therm', - inputs={bth: Flow(nominal_value=40, - fix=0.2)}) + demand_th = Sink( + label="demand_therm", inputs={bth: Flow(nominal_value=40, fix=0.2)} + ) # combined heat and power plant (chp) - pp_chp = Transformer(label='pp_chp', - inputs={bgas: Flow()}, - outputs={bel: Flow(nominal_value=30, - variable_costs=42), - bth: Flow(nominal_value=40)}, - conversion_factors={bel: 0.3, bth: 0.4}) + pp_chp = Transformer( + label="pp_chp", + inputs={bgas: Flow()}, + outputs={ + bel: Flow(nominal_value=30, variable_costs=42), + bth: Flow(nominal_value=40), + }, + conversion_factors={bel: 0.3, bth: 0.4}, + ) # heatpump with a coefficient of performance (COP) of 3 - b_heat_source = Bus(label='b_heat_source') + b_heat_source = Bus(label="b_heat_source") - heat_source = Source(label='heat_source', outputs={b_heat_source: Flow()}) + heat_source = Source(label="heat_source", outputs={b_heat_source: Flow()}) cop = 3 - heat_pump = Transformer(label='heat_pump', - inputs={bel: Flow(), b_heat_source: Flow()}, - outputs={bth: Flow(nominal_value=10)}, - conversion_factors={ - bel: 1/3, b_heat_source: (cop-1)/cop}) + heat_pump = Transformer( + label="heat_pump", + inputs={bel: Flow(), b_heat_source: Flow()}, + outputs={bth: Flow(nominal_value=10)}, + conversion_factors={bel: 1 / 3, b_heat_source: (cop - 1) / cop}, + ) energysystem = EnergySystem(timeindex=[1]) - energysystem.add(bgas, bel, bth, excess_el, wind, demand_el, demand_th, - pp_chp, b_heat_source, heat_source, heat_pump) + energysystem.add( + bgas, + bel, + bth, + excess_el, + wind, + demand_el, + demand_th, + pp_chp, + b_heat_source, + heat_source, + heat_pump, + ) # ################################ optimization ########################### @@ -89,16 +106,16 @@ def test_dispatch_one_time_step(solver='cbc'): optimization_model.results() # ################################ results ################################ - data = views.node(processing.results(om=optimization_model), 'b_el') + data = views.node(processing.results(om=optimization_model), "b_el") # generate results to be evaluated in tests - results = data['sequences'].sum(axis=0).to_dict() + results = data["sequences"].sum(axis=0).to_dict() test_results = { - (('wind', 'b_el'), 'flow'): 33, - (('b_el', 'demand_elec'), 'flow'): 26, - (('b_el', 'excess_el'), 'flow'): 5, - (('b_el', 'heat_pump'), 'flow'): 3, + (("wind", "b_el"), "flow"): 33, + (("b_el", "demand_elec"), "flow"): 26, + (("b_el", "excess_el"), "flow"): 5, + (("b_el", "heat_pump"), "flow"): 3, } for key in test_results.keys(): diff --git a/tests/test_scripts/test_solph/test_simple_model/test_simple_invest.py b/tests/test_scripts/test_solph/test_simple_model/test_simple_invest.py index adfa8557e..7c2b37096 100644 --- a/tests/test_scripts/test_solph/test_simple_model/test_simple_invest.py +++ b/tests/test_scripts/test_solph/test_simple_model/test_simple_invest.py @@ -32,99 +32,140 @@ from oemof.solph import views -def test_dispatch_example(solver='cbc', periods=24*5): +def test_dispatch_example(solver="cbc", periods=24 * 5): """Create an energy system and optimize the dispatch at least costs.""" Node.registry = None - filename = os.path.join(os.path.dirname(__file__), 'input_data.csv') + filename = os.path.join(os.path.dirname(__file__), "input_data.csv") data = pd.read_csv(filename, sep=",") # ######################### create energysystem components ################ # resource buses - bcoal = Bus(label='coal', balanced=False) - bgas = Bus(label='gas', balanced=False) - boil = Bus(label='oil', balanced=False) - blig = Bus(label='lignite', balanced=False) + bcoal = Bus(label="coal", balanced=False) + bgas = Bus(label="gas", balanced=False) + boil = Bus(label="oil", balanced=False) + blig = Bus(label="lignite", balanced=False) # electricity and heat - bel = Bus(label='b_el') - bth = Bus(label='b_th') + bel = Bus(label="b_el") + bth = Bus(label="b_th") # an excess and a shortage variable can help to avoid infeasible problems - excess_el = Sink(label='excess_el', inputs={bel: Flow()}) + excess_el = Sink(label="excess_el", inputs={bel: Flow()}) # shortage_el = Source(label='shortage_el', # outputs={bel: Flow(variable_costs=200)}) # sources ep_wind = economics.annuity(capex=1000, n=20, wacc=0.05) - wind = Source(label='wind', outputs={bel: Flow( - fix=data['wind'], - investment=Investment(ep_costs=ep_wind, existing=100))}) + wind = Source( + label="wind", + outputs={ + bel: Flow( + fix=data["wind"], + investment=Investment(ep_costs=ep_wind, existing=100), + ) + }, + ) ep_pv = economics.annuity(capex=1500, n=20, wacc=0.05) - pv = Source(label='pv', outputs={bel: Flow( - fix=data['pv'], - investment=Investment(ep_costs=ep_pv, existing=80))}) + pv = Source( + label="pv", + outputs={ + bel: Flow( + fix=data["pv"], + investment=Investment(ep_costs=ep_pv, existing=80), + ) + }, + ) # demands (electricity/heat) - demand_el = Sink(label='demand_elec', inputs={bel: Flow(nominal_value=85, - fix=data['demand_el'])}) + demand_el = Sink( + label="demand_elec", + inputs={bel: Flow(nominal_value=85, fix=data["demand_el"])}, + ) - demand_th = Sink(label='demand_therm', - inputs={bth: Flow(nominal_value=40, - fix=data['demand_th'])}) + demand_th = Sink( + label="demand_therm", + inputs={bth: Flow(nominal_value=40, fix=data["demand_th"])}, + ) # power plants - pp_coal = Transformer(label='pp_coal', - inputs={bcoal: Flow()}, - outputs={bel: Flow(nominal_value=20.2, - variable_costs=25)}, - conversion_factors={bel: 0.39}) - - pp_lig = Transformer(label='pp_lig', - inputs={blig: Flow()}, - outputs={bel: Flow(nominal_value=11.8, - variable_costs=19)}, - conversion_factors={bel: 0.41}) - - pp_gas = Transformer(label='pp_gas', - inputs={bgas: Flow()}, - outputs={bel: Flow(nominal_value=41, - variable_costs=40)}, - conversion_factors={bel: 0.50}) - - pp_oil = Transformer(label='pp_oil', - inputs={boil: Flow()}, - outputs={bel: Flow(nominal_value=5, - variable_costs=50)}, - conversion_factors={bel: 0.28}) + pp_coal = Transformer( + label="pp_coal", + inputs={bcoal: Flow()}, + outputs={bel: Flow(nominal_value=20.2, variable_costs=25)}, + conversion_factors={bel: 0.39}, + ) + + pp_lig = Transformer( + label="pp_lig", + inputs={blig: Flow()}, + outputs={bel: Flow(nominal_value=11.8, variable_costs=19)}, + conversion_factors={bel: 0.41}, + ) + + pp_gas = Transformer( + label="pp_gas", + inputs={bgas: Flow()}, + outputs={bel: Flow(nominal_value=41, variable_costs=40)}, + conversion_factors={bel: 0.50}, + ) + + pp_oil = Transformer( + label="pp_oil", + inputs={boil: Flow()}, + outputs={bel: Flow(nominal_value=5, variable_costs=50)}, + conversion_factors={bel: 0.28}, + ) # combined heat and power plant (chp) - pp_chp = Transformer(label='pp_chp', - inputs={bgas: Flow()}, - outputs={bel: Flow(nominal_value=30, - variable_costs=42), - bth: Flow(nominal_value=40)}, - conversion_factors={bel: 0.3, bth: 0.4}) + pp_chp = Transformer( + label="pp_chp", + inputs={bgas: Flow()}, + outputs={ + bel: Flow(nominal_value=30, variable_costs=42), + bth: Flow(nominal_value=40), + }, + conversion_factors={bel: 0.3, bth: 0.4}, + ) # heatpump with a coefficient of performance (COP) of 3 - b_heat_source = Bus(label='b_heat_source') + b_heat_source = Bus(label="b_heat_source") - heat_source = Source(label='heat_source', outputs={b_heat_source: Flow()}) + heat_source = Source(label="heat_source", outputs={b_heat_source: Flow()}) cop = 3 - heat_pump = Transformer(label='el_heat_pump', - inputs={bel: Flow(), b_heat_source: Flow()}, - outputs={bth: Flow(nominal_value=10)}, - conversion_factors={ - bel: 1/3, b_heat_source: (cop-1)/cop}) - - datetimeindex = pd.date_range('1/1/2012', periods=periods, freq='H') + heat_pump = Transformer( + label="el_heat_pump", + inputs={bel: Flow(), b_heat_source: Flow()}, + outputs={bth: Flow(nominal_value=10)}, + conversion_factors={bel: 1 / 3, b_heat_source: (cop - 1) / cop}, + ) + + datetimeindex = pd.date_range("1/1/2012", periods=periods, freq="H") energysystem = EnergySystem(timeindex=datetimeindex) - energysystem.add(bcoal, bgas, boil, bel, bth, blig, excess_el, wind, pv, - demand_el, demand_th, pp_coal, pp_lig, pp_oil, pp_gas, - pp_chp, b_heat_source, heat_source, heat_pump) + energysystem.add( + bcoal, + bgas, + boil, + bel, + bth, + blig, + excess_el, + wind, + pv, + demand_el, + demand_th, + pp_coal, + pp_lig, + pp_oil, + pp_gas, + pp_chp, + b_heat_source, + heat_source, + heat_pump, + ) # ################################ optimization ########################### @@ -147,26 +188,26 @@ def test_dispatch_example(solver='cbc', periods=24*5): # investment values within a pandas.Series object. # in this case the entry data['scalars'] does not exist since no investment # variables are used - data = views.node(results, 'b_el') + data = views.node(results, "b_el") # generate results to be evaluated in tests - comp_results = data['sequences'].sum(axis=0).to_dict() - comp_results['pv_capacity'] = results[(pv, bel)]['scalars'].invest - comp_results['wind_capacity'] = results[(wind, bel)]['scalars'].invest + comp_results = data["sequences"].sum(axis=0).to_dict() + comp_results["pv_capacity"] = results[(pv, bel)]["scalars"].invest + comp_results["wind_capacity"] = results[(wind, bel)]["scalars"].invest test_results = { - (('wind', 'b_el'), 'flow'): 9239, - (('pv', 'b_el'), 'flow'): 1147, - (('b_el', 'demand_elec'), 'flow'): 7440, - (('b_el', 'excess_el'), 'flow'): 6261, - (('pp_chp', 'b_el'), 'flow'): 477, - (('pp_lig', 'b_el'), 'flow'): 850, - (('pp_gas', 'b_el'), 'flow'): 934, - (('pp_coal', 'b_el'), 'flow'): 1256, - (('pp_oil', 'b_el'), 'flow'): 0, - (('b_el', 'el_heat_pump'), 'flow'): 202, - 'pv_capacity': 44, - 'wind_capacity': 246, + (("wind", "b_el"), "flow"): 9239, + (("pv", "b_el"), "flow"): 1147, + (("b_el", "demand_elec"), "flow"): 7440, + (("b_el", "excess_el"), "flow"): 6261, + (("pp_chp", "b_el"), "flow"): 477, + (("pp_lig", "b_el"), "flow"): 850, + (("pp_gas", "b_el"), "flow"): 934, + (("pp_coal", "b_el"), "flow"): 1256, + (("pp_oil", "b_el"), "flow"): 0, + (("b_el", "el_heat_pump"), "flow"): 202, + "pv_capacity": 44, + "wind_capacity": 246, } for key in test_results.keys(): diff --git a/tests/test_scripts/test_solph/test_storage_investment/test_invest_storage_regression.py b/tests/test_scripts/test_solph/test_storage_investment/test_invest_storage_regression.py index a8093e7a3..4e071759d 100644 --- a/tests/test_scripts/test_solph/test_storage_investment/test_invest_storage_regression.py +++ b/tests/test_scripts/test_solph/test_storage_investment/test_invest_storage_regression.py @@ -18,46 +18,60 @@ from oemof.solph import views -def test_regression_investment_storage(solver='cbc'): +def test_regression_investment_storage(solver="cbc"): """The problem was infeasible if the existing capacity and the maximum was defined in the Flow. """ - logging.info('Initialize the energy system') - date_time_index = pd.date_range('1/1/2012', periods=4, freq='H') + logging.info("Initialize the energy system") + date_time_index = pd.date_range("1/1/2012", periods=4, freq="H") energysystem = solph.EnergySystem(timeindex=date_time_index) Node.registry = energysystem # Buses - bgas = solph.Bus(label=('natural', 'gas')) - bel = solph.Bus(label='electricity') - - solph.Sink(label='demand', inputs={bel: solph.Flow( - fix=[209643, 207497, 200108, 191892], - nominal_value=1)}) + bgas = solph.Bus(label=("natural", "gas")) + bel = solph.Bus(label="electricity") + + solph.Sink( + label="demand", + inputs={ + bel: solph.Flow( + fix=[209643, 207497, 200108, 191892], nominal_value=1 + ) + }, + ) # Sources - solph.Source(label='rgas', outputs={bgas: solph.Flow()}) + solph.Source(label="rgas", outputs={bgas: solph.Flow()}) # Transformer solph.Transformer( - label='pp_gas', + label="pp_gas", inputs={bgas: solph.Flow()}, outputs={bel: solph.Flow(nominal_value=300000)}, - conversion_factors={bel: 0.58}) + conversion_factors={bel: 0.58}, + ) # Investment storage solph.components.GenericStorage( - label='storage', - inputs={bel: solph.Flow(investment=solph.Investment( - existing=625046/6, maximum=0))}, - outputs={bel: solph.Flow(investment=solph.Investment( - existing=104174.33, maximum=1))}, - loss_rate=0.00, initial_storage_level=0, - invest_relation_input_capacity=1/6, - invest_relation_output_capacity=1/6, - inflow_conversion_factor=1, outflow_conversion_factor=0.8, + label="storage", + inputs={ + bel: solph.Flow( + investment=solph.Investment(existing=625046 / 6, maximum=0) + ) + }, + outputs={ + bel: solph.Flow( + investment=solph.Investment(existing=104174.33, maximum=1) + ) + }, + loss_rate=0.00, + initial_storage_level=0, + invest_relation_input_capacity=1 / 6, + invest_relation_output_capacity=1 / 6, + inflow_conversion_factor=1, + outflow_conversion_factor=0.8, investment=solph.Investment(ep_costs=50, existing=625046), ) @@ -68,8 +82,9 @@ def test_regression_investment_storage(solver='cbc'): # Results results = solph.processing.results(om) - electricity_bus = views.node(results, 'electricity') - my_results = electricity_bus['sequences'].sum(axis=0).to_dict() - storage = energysystem.groups['storage'] - my_results['storage_invest'] = results[(storage, None)]['scalars'][ - 'invest'] + electricity_bus = views.node(results, "electricity") + my_results = electricity_bus["sequences"].sum(axis=0).to_dict() + storage = energysystem.groups["storage"] + my_results["storage_invest"] = results[(storage, None)]["scalars"][ + "invest" + ] diff --git a/tests/test_scripts/test_solph/test_storage_investment/test_storage_investment.py b/tests/test_scripts/test_solph/test_storage_investment/test_storage_investment.py index b1ffab220..149fc0840 100644 --- a/tests/test_scripts/test_solph/test_storage_investment/test_storage_investment.py +++ b/tests/test_scripts/test_solph/test_storage_investment/test_storage_investment.py @@ -50,12 +50,13 @@ PP_GAS = None -def test_optimise_storage_size(filename="storage_investment.csv", - solver='cbc'): +def test_optimise_storage_size( + filename="storage_investment.csv", solver="cbc" +): global PP_GAS - logging.info('Initialize the energy system') - date_time_index = pd.date_range('1/1/2012', periods=400, freq='H') + logging.info("Initialize the energy system") + date_time_index = pd.date_range("1/1/2012", periods=400, freq="H") energysystem = solph.EnergySystem(timeindex=date_time_index) Node.registry = energysystem @@ -68,38 +69,53 @@ def test_optimise_storage_size(filename="storage_investment.csv", bel = solph.Bus(label="electricity") # Sinks - solph.Sink(label='excess_bel', inputs={bel: solph.Flow()}) + solph.Sink(label="excess_bel", inputs={bel: solph.Flow()}) - solph.Sink(label='demand', inputs={bel: solph.Flow( - fix=data['demand_el'], nominal_value=1)}) + solph.Sink( + label="demand", + inputs={bel: solph.Flow(fix=data["demand_el"], nominal_value=1)}, + ) # Sources - solph.Source(label='rgas', outputs={bgas: solph.Flow( - nominal_value=194397000 * 400 / 8760, summed_max=1)}) + solph.Source( + label="rgas", + outputs={ + bgas: solph.Flow( + nominal_value=194397000 * 400 / 8760, summed_max=1 + ) + }, + ) - solph.Source(label='wind', outputs={bel: solph.Flow( - fix=data['wind'], nominal_value=1000000)}) + solph.Source( + label="wind", + outputs={bel: solph.Flow(fix=data["wind"], nominal_value=1000000)}, + ) - solph.Source(label='pv', outputs={bel: solph.Flow( - fix=data['pv'], nominal_value=582000)}) + solph.Source( + label="pv", + outputs={bel: solph.Flow(fix=data["pv"], nominal_value=582000)}, + ) # Transformer PP_GAS = solph.Transformer( - label='pp_gas', + label="pp_gas", inputs={bgas: solph.Flow()}, outputs={bel: solph.Flow(nominal_value=10e10, variable_costs=50)}, - conversion_factors={bel: 0.58}) + conversion_factors={bel: 0.58}, + ) # Investment storage epc = economics.annuity(capex=1000, n=20, wacc=0.05) solph.components.GenericStorage( - label='storage', + label="storage", inputs={bel: solph.Flow(variable_costs=10e10)}, outputs={bel: solph.Flow(variable_costs=10e10)}, - loss_rate=0.00, initial_storage_level=0, - invest_relation_input_capacity=1/6, - invest_relation_output_capacity=1/6, - inflow_conversion_factor=1, outflow_conversion_factor=0.8, + loss_rate=0.00, + initial_storage_level=0, + invest_relation_input_capacity=1 / 6, + invest_relation_output_capacity=1 / 6, + inflow_conversion_factor=1, + outflow_conversion_factor=0.8, investment=solph.Investment(ep_costs=epc, existing=6851), ) @@ -107,8 +123,8 @@ def test_optimise_storage_size(filename="storage_investment.csv", om = solph.Model(energysystem) om.receive_duals() om.solve(solver=solver) - energysystem.results['main'] = processing.results(om) - energysystem.results['meta'] = processing.meta_results(om) + energysystem.results["main"] = processing.results(om) + energysystem.results["meta"] = processing.meta_results(om) # Check dump and restore energysystem.dump() @@ -119,92 +135,98 @@ def test_results_with_actual_dump(): energysystem.restore() # Results - results = energysystem.results['main'] - meta = energysystem.results['meta'] + results = energysystem.results["main"] + meta = energysystem.results["meta"] - electricity_bus = views.node(results, 'electricity') - my_results = electricity_bus['sequences'].sum(axis=0).to_dict() - storage = energysystem.groups['storage'] - my_results['storage_invest'] = results[(storage, None)]['scalars'][ - 'invest'] + electricity_bus = views.node(results, "electricity") + my_results = electricity_bus["sequences"].sum(axis=0).to_dict() + storage = energysystem.groups["storage"] + my_results["storage_invest"] = results[(storage, None)]["scalars"][ + "invest" + ] stor_invest_dict = { - 'storage_invest': 2040000, - (('electricity', 'None'), 'duals'): 10800000000321, - (('electricity', 'demand'), 'flow'): 105867395, - (('electricity', 'excess_bel'), 'flow'): 211771291, - (('electricity', 'storage'), 'flow'): 2350931, - (('pp_gas', 'electricity'), 'flow'): 5148414, - (('pv', 'electricity'), 'flow'): 7488607, - (('storage', 'electricity'), 'flow'): 1880745, - (('wind', 'electricity'), 'flow'): 305471851} + "storage_invest": 2040000, + (("electricity", "None"), "duals"): 10800000000321, + (("electricity", "demand"), "flow"): 105867395, + (("electricity", "excess_bel"), "flow"): 211771291, + (("electricity", "storage"), "flow"): 2350931, + (("pp_gas", "electricity"), "flow"): 5148414, + (("pv", "electricity"), "flow"): 7488607, + (("storage", "electricity"), "flow"): 1880745, + (("wind", "electricity"), "flow"): 305471851, + } for key in stor_invest_dict.keys(): eq_(int(round(my_results[key])), int(round(stor_invest_dict[key]))) # Solver results - eq_(str(meta['solver']['Termination condition']), 'optimal') - eq_(meta['solver']['Error rc'], 0) - eq_(str(meta['solver']['Status']), 'ok') + eq_(str(meta["solver"]["Termination condition"]), "optimal") + eq_(meta["solver"]["Error rc"], 0) + eq_(str(meta["solver"]["Status"]), "ok") # Problem results - eq_(meta['problem']['Lower bound'], 4.231675777e+17) - eq_(meta['problem']['Upper bound'], 4.231675777e+17) - eq_(meta['problem']['Number of variables'], 2805) - eq_(meta['problem']['Number of constraints'], 2806) - eq_(meta['problem']['Number of nonzeros'], 1197) - eq_(meta['problem']['Number of objectives'], 1) - eq_(str(meta['problem']['Sense']), 'minimize') + eq_(meta["problem"]["Lower bound"], 4.231675777e17) + eq_(meta["problem"]["Upper bound"], 4.231675777e17) + eq_(meta["problem"]["Number of variables"], 2805) + eq_(meta["problem"]["Number of constraints"], 2806) + eq_(meta["problem"]["Number of nonzeros"], 1197) + eq_(meta["problem"]["Number of objectives"], 1) + eq_(str(meta["problem"]["Sense"]), "minimize") # Objective function - eq_(round(meta['objective']), 423167578261115584) + eq_(round(meta["objective"]), 423167578261115584) -@skip("Opening an old dump may fail due to different python versions or" - " version of other packages. We can try to reactivate the test with" - " v0.4.0.") +@skip( + "Opening an old dump may fail due to different python versions or" + " version of other packages. We can try to reactivate the test with" + " v0.4.0." +) def test_results_with_old_dump(): """ Test again with a stored dump created with v0.3.2dev (896a6d50) """ energysystem = solph.EnergySystem() energysystem.restore( - dpath=os.path.dirname(os.path.realpath(__file__)), - filename='es_dump_test_3_2dev.oemof') + dpath=os.path.dirname(os.path.realpath(__file__)), + filename="es_dump_test_3_2dev.oemof", + ) - results = energysystem.results['main'] + results = energysystem.results["main"] - electricity_bus = views.node(results, 'electricity') - my_results = electricity_bus['sequences'].sum(axis=0).to_dict() - storage = energysystem.groups['storage'] - my_results['storage_invest'] = results[(storage, None)]['scalars'][ - 'invest'] + electricity_bus = views.node(results, "electricity") + my_results = electricity_bus["sequences"].sum(axis=0).to_dict() + storage = energysystem.groups["storage"] + my_results["storage_invest"] = results[(storage, None)]["scalars"][ + "invest" + ] stor_invest_dict = { - 'storage_invest': 2040000, - (('electricity', 'demand'), 'flow'): 105867395, - (('electricity', 'excess_bel'), 'flow'): 211771291, - (('electricity', 'storage'), 'flow'): 2350931, - (('pp_gas', 'electricity'), 'flow'): 5148414, - (('pv', 'electricity'), 'flow'): 7488607, - (('storage', 'electricity'), 'flow'): 1880745, - (('wind', 'electricity'), 'flow'): 305471851} + "storage_invest": 2040000, + (("electricity", "demand"), "flow"): 105867395, + (("electricity", "excess_bel"), "flow"): 211771291, + (("electricity", "storage"), "flow"): 2350931, + (("pp_gas", "electricity"), "flow"): 5148414, + (("pv", "electricity"), "flow"): 7488607, + (("storage", "electricity"), "flow"): 1880745, + (("wind", "electricity"), "flow"): 305471851, + } for key in stor_invest_dict.keys(): eq_(int(round(my_results[key])), int(round(stor_invest_dict[key]))) def test_solph_transformer_attributes_before_dump_and_after_restore(): - """ dump/restore should preserve all attributes of `solph.Transformer` - """ + """dump/restore should preserve all attributes of `solph.Transformer`""" energysystem = solph.EnergySystem() energysystem.restore() - trsf_attr_before_dump = sorted( - [x for x in dir(PP_GAS) if '__' not in x]) + trsf_attr_before_dump = sorted([x for x in dir(PP_GAS) if "__" not in x]) trsf_attr_after_restore = sorted( - [x for x in dir(energysystem.groups['pp_gas']) if '__' not in x]) + [x for x in dir(energysystem.groups["pp_gas"]) if "__" not in x] + ) # Compare attributes before dump and after restore eq_(trsf_attr_before_dump, trsf_attr_after_restore) diff --git a/tests/test_scripts/test_solph/test_storage_investment/test_storage_with_tuple_label.py b/tests/test_scripts/test_solph/test_storage_investment/test_storage_with_tuple_label.py index 12e00248f..7715df294 100644 --- a/tests/test_scripts/test_solph/test_storage_investment/test_storage_with_tuple_label.py +++ b/tests/test_scripts/test_solph/test_storage_investment/test_storage_with_tuple_label.py @@ -47,24 +47,25 @@ from oemof.solph import views -class Label(namedtuple('solph_label', ['tag1', 'tag2', 'tag3'])): +class Label(namedtuple("solph_label", ["tag1", "tag2", "tag3"])): __slots__ = () def __str__(self): - return '_'.join(map(str, self._asdict().values())) + return "_".join(map(str, self._asdict().values())) def test_label(): - my_label = Label('arg', 5, None) - eq_(str(my_label), 'arg_5_None') + my_label = Label("arg", 5, None) + eq_(str(my_label), "arg_5_None") eq_(repr(my_label), "Label(tag1='arg', tag2=5, tag3=None)") -def test_tuples_as_labels_example(filename="storage_investment.csv", - solver='cbc'): +def test_tuples_as_labels_example( + filename="storage_investment.csv", solver="cbc" +): - logging.info('Initialize the energy system') - date_time_index = pd.date_range('1/1/2012', periods=40, freq='H') + logging.info("Initialize the energy system") + date_time_index = pd.date_range("1/1/2012", periods=40, freq="H") energysystem = solph.EnergySystem(timeindex=date_time_index) Node.registry = energysystem @@ -73,57 +74,72 @@ def test_tuples_as_labels_example(filename="storage_investment.csv", data = pd.read_csv(full_filename, sep=",") # Buses - bgas = solph.Bus(label=Label('bus', 'natural_gas', None)) - bel = solph.Bus(label=Label('bus', 'electricity', '')) + bgas = solph.Bus(label=Label("bus", "natural_gas", None)) + bel = solph.Bus(label=Label("bus", "electricity", "")) # Sinks - solph.Sink(label=Label('sink', 'electricity', 'excess'), - inputs={bel: solph.Flow()}) + solph.Sink( + label=Label("sink", "electricity", "excess"), + inputs={bel: solph.Flow()}, + ) - solph.Sink(label=Label('sink', 'electricity', 'demand'), - inputs={bel: solph.Flow( - fix=data['demand_el'], - nominal_value=1)}) + solph.Sink( + label=Label("sink", "electricity", "demand"), + inputs={bel: solph.Flow(fix=data["demand_el"], nominal_value=1)}, + ) # Sources - solph.Source(label=Label('source', 'natural_gas', 'commodity'), - outputs={bgas: solph.Flow( - nominal_value=194397000 * 400 / 8760, summed_max=1)}) + solph.Source( + label=Label("source", "natural_gas", "commodity"), + outputs={ + bgas: solph.Flow( + nominal_value=194397000 * 400 / 8760, summed_max=1 + ) + }, + ) - solph.Source(label=Label('renewable', 'electricity', 'wind'), - outputs={bel: solph.Flow( - fix=data['wind'], - nominal_value=1000000)}) + solph.Source( + label=Label("renewable", "electricity", "wind"), + outputs={bel: solph.Flow(fix=data["wind"], nominal_value=1000000)}, + ) - solph.Source(label=Label('renewable', 'electricity', 'pv'), - outputs={bel: solph.Flow( - fix=data['pv'], nominal_value=582000, - )}) + solph.Source( + label=Label("renewable", "electricity", "pv"), + outputs={ + bel: solph.Flow( + fix=data["pv"], + nominal_value=582000, + ) + }, + ) # Transformer solph.Transformer( - label=Label('pp', 'electricity', 'natural_gas'), + label=Label("pp", "electricity", "natural_gas"), inputs={bgas: solph.Flow()}, outputs={bel: solph.Flow(nominal_value=10e10, variable_costs=50)}, - conversion_factors={bel: 0.58}) + conversion_factors={bel: 0.58}, + ) # Investment storage solph.components.GenericStorage( - label=Label('storage', 'electricity', 'battery'), + label=Label("storage", "electricity", "battery"), nominal_storage_capacity=204685, inputs={bel: solph.Flow(variable_costs=10e10)}, outputs={bel: solph.Flow(variable_costs=10e10)}, - loss_rate=0.00, initial_storage_level=0, - invest_relation_input_capacity=1/6, - invest_relation_output_capacity=1/6, - inflow_conversion_factor=1, outflow_conversion_factor=0.8, + loss_rate=0.00, + initial_storage_level=0, + invest_relation_input_capacity=1 / 6, + invest_relation_output_capacity=1 / 6, + inflow_conversion_factor=1, + outflow_conversion_factor=0.8, ) # Solve model om = solph.Model(energysystem) om.solve(solver=solver) - energysystem.results['main'] = processing.results(om) - energysystem.results['meta'] = processing.meta_results(om) + energysystem.results["main"] = processing.results(om) + energysystem.results["meta"] = processing.meta_results(om) # Check dump and restore energysystem.dump() @@ -131,51 +147,58 @@ def test_tuples_as_labels_example(filename="storage_investment.csv", es.restore() # Results - results = es.results['main'] - meta = es.results['meta'] + results = es.results["main"] + meta = es.results["meta"] - electricity_bus = views.node(results, 'bus_electricity_') - my_results = electricity_bus['sequences'].sum(axis=0).to_dict() - storage = es.groups['storage_electricity_battery'] + electricity_bus = views.node(results, "bus_electricity_") + my_results = electricity_bus["sequences"].sum(axis=0).to_dict() + storage = es.groups["storage_electricity_battery"] storage_node = views.node(results, storage) - my_results['max_load'] = storage_node['sequences'].max()[[ - ((storage, None), 'storage_content')]] - commodity_bus = views.node(results, 'bus_natural_gas_None') + my_results["max_load"] = storage_node["sequences"].max()[ + [((storage, None), "storage_content")] + ] + commodity_bus = views.node(results, "bus_natural_gas_None") - gas_usage = commodity_bus['sequences'][ - (('source_natural_gas_commodity', 'bus_natural_gas_None'), 'flow')] + gas_usage = commodity_bus["sequences"][ + (("source_natural_gas_commodity", "bus_natural_gas_None"), "flow") + ] - my_results['gas_usage'] = gas_usage.sum() + my_results["gas_usage"] = gas_usage.sum() stor_invest_dict = { - 'gas_usage': 1304112, - 'max_load': 0, - (('bus_electricity_', 'sink_electricity_demand'), 'flow'): 8239764, - (('bus_electricity_', 'sink_electricity_excess'), 'flow'): 22036732, - (('bus_electricity_', 'storage_electricity_battery'), 'flow'): 0, - (('pp_electricity_natural_gas', 'bus_electricity_'), 'flow'): 756385, - (('renewable_electricity_pv', 'bus_electricity_'), 'flow'): 744132, - (('renewable_electricity_wind', 'bus_electricity_'), - 'flow'): 28775978, - (('storage_electricity_battery', 'bus_electricity_',), - 'flow'): 0} + "gas_usage": 1304112, + "max_load": 0, + (("bus_electricity_", "sink_electricity_demand"), "flow"): 8239764, + (("bus_electricity_", "sink_electricity_excess"), "flow"): 22036732, + (("bus_electricity_", "storage_electricity_battery"), "flow"): 0, + (("pp_electricity_natural_gas", "bus_electricity_"), "flow"): 756385, + (("renewable_electricity_pv", "bus_electricity_"), "flow"): 744132, + (("renewable_electricity_wind", "bus_electricity_"), "flow"): 28775978, + ( + ( + "storage_electricity_battery", + "bus_electricity_", + ), + "flow", + ): 0, + } for key in stor_invest_dict.keys(): eq_(int(round(my_results[key])), int(round(stor_invest_dict[key]))) # Solver results - eq_(str(meta['solver']['Termination condition']), 'optimal') - eq_(meta['solver']['Error rc'], 0) - eq_(str(meta['solver']['Status']), 'ok') + eq_(str(meta["solver"]["Termination condition"]), "optimal") + eq_(meta["solver"]["Error rc"], 0) + eq_(str(meta["solver"]["Status"]), "ok") # Problem results - eq_(int(meta['problem']['Lower bound']), 37819254) - eq_(int(meta['problem']['Upper bound']), 37819254) - eq_(meta['problem']['Number of variables'], 281) - eq_(meta['problem']['Number of constraints'], 163) - eq_(meta['problem']['Number of nonzeros'], 116) - eq_(meta['problem']['Number of objectives'], 1) - eq_(str(meta['problem']['Sense']), 'minimize') + eq_(int(meta["problem"]["Lower bound"]), 37819254) + eq_(int(meta["problem"]["Upper bound"]), 37819254) + eq_(meta["problem"]["Number of variables"], 281) + eq_(meta["problem"]["Number of constraints"], 163) + eq_(meta["problem"]["Number of nonzeros"], 116) + eq_(meta["problem"]["Number of objectives"], 1) + eq_(str(meta["problem"]["Sense"]), "minimize") # Objective function - eq_(round(meta['objective']), 37819254) + eq_(round(meta["objective"]), 37819254) diff --git a/tests/test_scripts/test_solph/test_variable_chp/test_variable_chp.py b/tests/test_scripts/test_solph/test_variable_chp/test_variable_chp.py index 4c81dabfd..b7a07b4e5 100644 --- a/tests/test_scripts/test_solph/test_variable_chp/test_variable_chp.py +++ b/tests/test_scripts/test_solph/test_variable_chp/test_variable_chp.py @@ -23,11 +23,11 @@ from oemof.solph import views -def test_variable_chp(filename="variable_chp.csv", solver='cbc'): - logging.info('Initialize the energy system') +def test_variable_chp(filename="variable_chp.csv", solver="cbc"): + logging.info("Initialize the energy system") # create time index for 192 hours in May. - date_time_index = pd.date_range('5/5/2012', periods=5, freq='H') + date_time_index = pd.date_range("5/5/2012", periods=5, freq="H") energysystem = solph.EnergySystem(timeindex=date_time_index) Node.registry = energysystem @@ -39,97 +39,122 @@ def test_variable_chp(filename="variable_chp.csv", solver='cbc'): # Create oemof.solph objects ########################################################################## - logging.info('Create oemof.solph objects') + logging.info("Create oemof.solph objects") # create natural gas bus - bgas = solph.Bus(label=('natural', 'gas')) + bgas = solph.Bus(label=("natural", "gas")) # create commodity object for gas resource - solph.Source(label=('commodity', 'gas'), - outputs={bgas: solph.Flow(variable_costs=50)}) + solph.Source( + label=("commodity", "gas"), + outputs={bgas: solph.Flow(variable_costs=50)}, + ) # create two electricity buses and two heat buses - bel = solph.Bus(label=('electricity', 1)) - bel2 = solph.Bus(label=('electricity', 2)) - bth = solph.Bus(label=('heat', 1)) - bth2 = solph.Bus(label=('heat', 2)) + bel = solph.Bus(label=("electricity", 1)) + bel2 = solph.Bus(label=("electricity", 2)) + bth = solph.Bus(label=("heat", 1)) + bth2 = solph.Bus(label=("heat", 2)) # create excess components for the elec/heat bus to allow overproduction - solph.Sink(label=('excess', 'bth_2'), inputs={bth2: solph.Flow()}) - solph.Sink(label=('excess', 'bth_1'), inputs={bth: solph.Flow()}) - solph.Sink(label=('excess', 'bel_2'), inputs={bel2: solph.Flow()}) - solph.Sink(label=('excess', 'bel_1'), inputs={bel: solph.Flow()}) + solph.Sink(label=("excess", "bth_2"), inputs={bth2: solph.Flow()}) + solph.Sink(label=("excess", "bth_1"), inputs={bth: solph.Flow()}) + solph.Sink(label=("excess", "bel_2"), inputs={bel2: solph.Flow()}) + solph.Sink(label=("excess", "bel_1"), inputs={bel: solph.Flow()}) # create simple sink object for electrical demand for each electrical bus - solph.Sink(label=('demand', 'elec1'), inputs={bel: solph.Flow( - fix=data['demand_el'], nominal_value=1)}) - solph.Sink(label=('demand', 'elec2'), inputs={bel2: solph.Flow( - fix=data['demand_el'], nominal_value=1)}) + solph.Sink( + label=("demand", "elec1"), + inputs={bel: solph.Flow(fix=data["demand_el"], nominal_value=1)}, + ) + solph.Sink( + label=("demand", "elec2"), + inputs={bel2: solph.Flow(fix=data["demand_el"], nominal_value=1)}, + ) # create simple sink object for heat demand for each thermal bus - solph.Sink(label=('demand', 'therm1'), inputs={bth: solph.Flow( - fix=data['demand_th'], nominal_value=741000)}) - solph.Sink(label=('demand', 'therm2'), inputs={bth2: solph.Flow( - fix=data['demand_th'], nominal_value=741000)}) + solph.Sink( + label=("demand", "therm1"), + inputs={bth: solph.Flow(fix=data["demand_th"], nominal_value=741000)}, + ) + solph.Sink( + label=("demand", "therm2"), + inputs={bth2: solph.Flow(fix=data["demand_th"], nominal_value=741000)}, + ) # create a fixed transformer to distribute to the heat_2 and elec_2 buses solph.Transformer( - label=('fixed_chp', 'gas'), + label=("fixed_chp", "gas"), inputs={bgas: solph.Flow(nominal_value=10e10)}, outputs={bel2: solph.Flow(), bth2: solph.Flow()}, - conversion_factors={bel2: 0.3, bth2: 0.5}) + conversion_factors={bel2: 0.3, bth2: 0.5}, + ) # create a fixed transformer to distribute to the heat and elec buses solph.components.ExtractionTurbineCHP( - label=('variable_chp', 'gas'), + label=("variable_chp", "gas"), inputs={bgas: solph.Flow(nominal_value=10e10)}, outputs={bel: solph.Flow(), bth: solph.Flow()}, conversion_factors={bel: 0.3, bth: 0.5}, - conversion_factor_full_condensation={bel: 0.5} - ) + conversion_factor_full_condensation={bel: 0.5}, + ) ########################################################################## # Optimise the energy system and plot the results ########################################################################## - logging.info('Optimise the energy system') + logging.info("Optimise the energy system") om = solph.Model(energysystem) - logging.info('Solve the optimization problem') + logging.info("Solve the optimization problem") om.solve(solver=solver) optimisation_results = solph.processing.results(om) parameter = solph.processing.parameter_as_dict(energysystem) myresults = views.node(optimisation_results, "('natural', 'gas')") - sumresults = myresults['sequences'].sum(axis=0) - maxresults = myresults['sequences'].max(axis=0) + sumresults = myresults["sequences"].sum(axis=0) + maxresults = myresults["sequences"].max(axis=0) variable_chp_dict_sum = { - (("('natural', 'gas')", "('variable_chp', 'gas')"), 'flow'): 2823024, - (("('natural', 'gas')", "('fixed_chp', 'gas')"), 'flow'): 3710208, - (("('commodity', 'gas')", "('natural', 'gas')"), 'flow'): 6533232} + (("('natural', 'gas')", "('variable_chp', 'gas')"), "flow"): 2823024, + (("('natural', 'gas')", "('fixed_chp', 'gas')"), "flow"): 3710208, + (("('commodity', 'gas')", "('natural', 'gas')"), "flow"): 6533232, + } variable_chp_dict_max = { - (("('natural', 'gas')", "('variable_chp', 'gas')"), 'flow'): 630332, - (("('natural', 'gas')", "('fixed_chp', 'gas')"), 'flow'): 785934, - (("('commodity', 'gas')", "('natural', 'gas')"), 'flow'): 1416266} + (("('natural', 'gas')", "('variable_chp', 'gas')"), "flow"): 630332, + (("('natural', 'gas')", "('fixed_chp', 'gas')"), "flow"): 785934, + (("('commodity', 'gas')", "('natural', 'gas')"), "flow"): 1416266, + } for key in variable_chp_dict_max.keys(): logging.debug("Test the maximum value of {0}".format(key)) - eq_(int(round(maxresults[[key]])), - int(round(variable_chp_dict_max[key]))) + eq_( + int(round(maxresults[[key]])), + int(round(variable_chp_dict_max[key])), + ) for key in variable_chp_dict_sum.keys(): logging.debug("Test the summed up value of {0}".format(key)) - eq_(int(round(sumresults[[key]])), - int(round(variable_chp_dict_sum[key]))) + eq_( + int(round(sumresults[[key]])), + int(round(variable_chp_dict_sum[key])), + ) - eq_(parameter[(energysystem.groups["('fixed_chp', 'gas')"], None)] - ['scalars']['label'], "('fixed_chp', 'gas')") - eq_(parameter[(energysystem.groups["('fixed_chp', 'gas')"], None)] - ['scalars']["conversion_factors_('electricity', 2)"], 0.3) + eq_( + parameter[(energysystem.groups["('fixed_chp', 'gas')"], None)][ + "scalars" + ]["label"], + "('fixed_chp', 'gas')", + ) + eq_( + parameter[(energysystem.groups["('fixed_chp', 'gas')"], None)][ + "scalars" + ]["conversion_factors_('electricity', 2)"], + 0.3, + ) # objective function - eq_(round(solph.processing.meta_results(om)['objective']), 326661590) + eq_(round(solph.processing.meta_results(om)["objective"]), 326661590) diff --git a/tests/test_solph_network_classes.py b/tests/test_solph_network_classes.py index fa93c53ce..4a8c23faa 100644 --- a/tests/test_solph_network_classes.py +++ b/tests/test_solph_network_classes.py @@ -38,18 +38,21 @@ def test_default_conversion_factor(self): assert transf.conversion_factors[self.bus][2] == 1 def test_sequence_conversion_factor_from_scalar(self): - transf = solph.Transformer(inputs={self.bus: solph.Flow()}, - conversion_factors={self.bus: 2}) + transf = solph.Transformer( + inputs={self.bus: solph.Flow()}, conversion_factors={self.bus: 2} + ) assert transf.conversion_factors[self.bus][6] == 2 def test_sequence_conversion_factor_from_list_correct_length(self): - transf = solph.Transformer(inputs={self.bus: solph.Flow()}, - conversion_factors={self.bus: [2]}) + transf = solph.Transformer( + inputs={self.bus: solph.Flow()}, conversion_factors={self.bus: [2]} + ) assert len(transf.conversion_factors[self.bus]) == 1 def test_sequence_conversion_factor_from_list_wrong_length(self): - transf = solph.Transformer(inputs={self.bus: solph.Flow()}, - conversion_factors={self.bus: [2]}) + transf = solph.Transformer( + inputs={self.bus: solph.Flow()}, conversion_factors={self.bus: [2]} + ) with pytest.raises(IndexError): self.a = transf.conversion_factors[self.bus][6] @@ -101,9 +104,11 @@ def test_deprecated_actual_value(): def test_warning_fixed_still_used(): """If fixed attribute is still used, a warning is raised.""" - msg = ("The `fixed` attribute is deprecated.\nIf you have defined " - "the `fix` attribute the flow variable will be fixed.\n" - "The `fixed` attribute does not change anything.") + msg = ( + "The `fixed` attribute is deprecated.\nIf you have defined " + "the `fix` attribute the flow variable will be fixed.\n" + "The `fixed` attribute does not change anything." + ) with warnings.catch_warnings(record=True) as w: solph.Flow(fixed=True) assert len(w) != 0 diff --git a/tests/test_warnings.py b/tests/test_warnings.py index adcb5d5f3..437919dad 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -25,20 +25,20 @@ def warning_fixture(): def test_that_the_sink_warnings_actually_get_raised(warning_fixture): - """ Sink doesn't warn about potentially erroneous usage. - """ + """Sink doesn't warn about potentially erroneous usage.""" look_out = network.Bus() - msg = ("Attribute is missing in Node of ") + msg = ( + "Attribute is missing in Node of " + ) with warnings.catch_warnings(record=True) as w: - solph.Sink(label='test_sink', outputs={look_out: "A typo!"}) + solph.Sink(label="test_sink", outputs={look_out: "A typo!"}) assert len(w) == 1 assert msg in str(w[-1].message) def test_filtered_warning(warning_fixture): - """ Sink doesn't warn about potentially erroneous usage. - """ + """Sink doesn't warn about potentially erroneous usage.""" warnings.filterwarnings("ignore", category=SuspiciousUsageWarning) look_out = network.Bus() with warnings.catch_warnings(record=True) as w: @@ -47,23 +47,25 @@ def test_filtered_warning(warning_fixture): def test_that_the_source_warnings_actually_get_raised(warning_fixture): - """ Source doesn't warn about potentially erroneous usage. - """ + """Source doesn't warn about potentially erroneous usage.""" look_out = network.Bus() - msg = ("Attribute is missing in Node of .") + msg = ( + "Attribute is missing in Node of ." + ) with warnings.catch_warnings(record=True) as w: - solph.Source(label='test_source', inputs={look_out: "A typo!"}) + solph.Source(label="test_source", inputs={look_out: "A typo!"}) assert len(w) == 1 assert msg in str(w[-1].message) def test_that_the_solph_source_warnings_actually_get_raised(warning_fixture): - """ Source doesn't warn about potentially erroneous usage. - """ + """Source doesn't warn about potentially erroneous usage.""" look_out = network.Bus() - msg = ("Attribute is missing in Node of .") + msg = ( + "Attribute is missing in Node of ." + ) with warnings.catch_warnings(record=True) as w: solph.Source(label="solph_sink", inputs={look_out: "A typo!"}) assert len(w) == 1 @@ -71,20 +73,22 @@ def test_that_the_solph_source_warnings_actually_get_raised(warning_fixture): def test_that_the_transformer_warnings_actually_get_raised(warning_fixture): - """ Transformer doesn't warn about potentially erroneous usage. - """ + """Transformer doesn't warn about potentially erroneous usage.""" look_out = network.Bus() - msg = ("Attribute is missing in Node of .") + msg = ( + "Attribute is missing in Node of ." + ) with warnings.catch_warnings(record=True) as w: - solph.Transformer(label='no input', outputs={look_out: "No inputs!"}) + solph.Transformer(label="no input", outputs={look_out: "No inputs!"}) assert len(w) == 1 assert msg in str(w[-1].message) - msg = ("Attribute is missing in Node of .") + msg = ( + "Attribute is missing in Node of ." + ) with warnings.catch_warnings(record=True) as w: - solph.Transformer(label='no output', - inputs={look_out: "No outputs!"}) + solph.Transformer(label="no output", inputs={look_out: "No outputs!"}) assert len(w) == 1 assert msg in str(w[-1].message) @@ -92,11 +96,14 @@ def test_that_the_transformer_warnings_actually_get_raised(warning_fixture): def test_storage_without_outputs(warning_fixture): """ GenericStorage doesn't warn correctly about missing outputs.""" look_out = network.Bus() - msg = ("Attribute is missing in Node " - " of .") + msg = ( + "Attribute is missing in Node " + " of ." + ) with warnings.catch_warnings(record=True) as w: - solph.GenericStorage(label='storage without outputs', - inputs={look_out: "No outputs!"}) + solph.GenericStorage( + label="storage without outputs", inputs={look_out: "No outputs!"} + ) assert len(w) == 1 assert msg in str(w[-1].message) @@ -104,10 +111,13 @@ def test_storage_without_outputs(warning_fixture): def test_storage_without_inputs(warning_fixture): """ GenericStorage doesn't warn correctly about missing inputs.""" look_out = network.Bus() - msg = ("Attribute is missing in Node " - " of .") + msg = ( + "Attribute is missing in Node " + " of ." + ) with warnings.catch_warnings(record=True) as w: - solph.GenericStorage(label='storage without inputs', - outputs={look_out: "No inputs!"}) + solph.GenericStorage( + label="storage without inputs", outputs={look_out: "No inputs!"} + ) assert len(w) == 1 assert msg in str(w[-1].message) diff --git a/tox.ini b/tox.ini index 181085ea6..dfc182c0a 100644 --- a/tox.ini +++ b/tox.ini @@ -3,12 +3,18 @@ envlist = clean, check, docs, - py36, py37, py38, + py39 py3-nocov, report +[gh-actions] +python = + 3.7: py37 + 3.8: py38 + 3.9: py39 + [testenv] basepython = docs: {env:TOXPYTHON:python3} @@ -85,8 +91,8 @@ commands = coverage erase skip_install = true deps = coverage -[testenv:py36] -basepython = {env:TOXPYTHON:python3.6} +[testenv:py37] +basepython = {env:TOXPYTHON:python3.7} setenv = {[testenv]setenv} usedevelop = true @@ -96,8 +102,8 @@ deps = {[testenv]deps} pytest-cov -[testenv:py37] -basepython = {env:TOXPYTHON:python3.7} +[testenv:py38] +basepython = {env:TOXPYTHON:python3.8} setenv = {[testenv]setenv} usedevelop = true @@ -107,8 +113,8 @@ deps = {[testenv]deps} pytest-cov -[testenv:py38] -basepython = {env:TOXPYTHON:python3.8} +[testenv:py39] +basepython = {env:TOXPYTHON:python3.9} setenv = {[testenv]setenv} usedevelop = true