Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce nomopyomo #99

Merged
merged 117 commits into from Dec 19, 2019
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
cf787d7
integrate nomopyomo
FabianHofmann Oct 15, 2019
340cab5
fix environment
FabianHofmann Oct 15, 2019
e259977
opt_lm: fix dict update for python 2.7
FabianHofmann Oct 16, 2019
4522d92
travis.yml revert last change
FabianHofmann Oct 16, 2019
d6eb31d
environment: fix gurobi channel
FabianHofmann Oct 16, 2019
55d4a82
travis: play around, problem in 'before install' section
FabianHofmann Oct 16, 2019
c3c194f
opt_lm: move gurobipy import in function
FabianHofmann Oct 16, 2019
afa98b2
MANIFEST add varbiales.csv
FabianHofmann Oct 16, 2019
c0c2740
test: make lopf-tests more lightweight, include pyomo=False
FabianHofmann Oct 16, 2019
f0dffde
opt_lm: fix logging handling
FabianHofmann Oct 16, 2019
771dc94
opf_lm: fi disable logging too
FabianHofmann Oct 16, 2019
a50f43c
add solve.py: Start to restructure
FabianHofmann Oct 16, 2019
9409c78
distribute nomopyomo code oder modules
FabianHofmann Oct 17, 2019
2ecee3e
linopf fix tiny bug
FabianHofmann Oct 17, 2019
0ff03aa
travis include python2.7 again
FabianHofmann Oct 17, 2019
d6134ac
disable new code for python version < 3
FabianHofmann Oct 17, 2019
508a3bd
travis: disable python2.7 again
FabianHofmann Oct 17, 2019
53ac07b
linopf: complete docstrings
FabianHofmann Oct 18, 2019
aca70c6
linopt: add docstrings for run_and_read_
FabianHofmann Oct 18, 2019
3093ebd
linopf: cover case of lv limit but no extendable lines
FabianHofmann Oct 18, 2019
3a636ab
linopf: handle case of bus with no injection
FabianHofmann Oct 22, 2019
eab33d2
io: fix import export with new shadow prices
FabianHofmann Oct 24, 2019
d2c9c7e
io: revert exporting subnetwork frame and time-series (shadow price o…
FabianHofmann Oct 25, 2019
8ebe4cb
linopf: add argument keep_shadowprices, to simplify dual extracting
FabianHofmann Oct 25, 2019
b85f685
update docs
FabianHofmann Oct 27, 2019
39cfda4
update docs II
FabianHofmann Oct 27, 2019
03193db
update docs II
FabianHofmann Oct 27, 2019
dbcaedb
update docs IV
FabianHofmann Oct 27, 2019
0dfa5ce
update docs V
FabianHofmann Oct 27, 2019
7df314f
update docs VI
FabianHofmann Oct 27, 2019
e30f9d3
update docs VII
FabianHofmann Oct 27, 2019
67c4875
linopf: include transformer in KVL,
FabianHofmann Oct 28, 2019
cff7ec8
Update doc/optimal_power_flow.rst
FabianHofmann Oct 28, 2019
ef29cbf
Update doc/optimal_power_flow.rst
FabianHofmann Oct 28, 2019
bac787a
Update doc/optimal_power_flow.rst
FabianHofmann Oct 28, 2019
db5e6fb
Update pypsa/linopt.py
FabianHofmann Oct 28, 2019
c317f04
Update doc/optimal_power_flow.rst
FabianHofmann Oct 28, 2019
40533b8
Update doc/optimal_power_flow.rst
FabianHofmann Oct 28, 2019
7aaa1f0
Update doc/optimal_power_flow.rst
FabianHofmann Oct 28, 2019
be562a2
Update doc/optimal_power_flow.rst
FabianHofmann Oct 28, 2019
ef882ae
Update pypsa/descriptors.py
FabianHofmann Oct 28, 2019
0bc4286
Update pypsa/linopt.py
FabianHofmann Oct 28, 2019
831a2a1
Update pypsa/stats.py
FabianHofmann Oct 28, 2019
01706ac
doc: fix typo
FabianHofmann Oct 28, 2019
135d2ad
Merge branch 'nomopyomo' of github.com:PyPSA/pypsa into nomopyomo
FabianHofmann Oct 28, 2019
43c4e1d
Merge branch 'master' into nomopyomo
FabianHofmann Oct 31, 2019
a8d8ba1
linopf: fix string replacement
FabianHofmann Nov 4, 2019
8cfce1f
small corrections
FabianHofmann Nov 7, 2019
2a8c339
linopf.py: add warning for non support of unit commitment
FabianHofmann Nov 8, 2019
7a989cb
linopf: fix typo
FabianHofmann Nov 8, 2019
a4f3eb3
linopf.py: fill_value for buses without any components in KVL constraint
FabianHofmann Nov 8, 2019
1019b4a
linopf: Correct global constraints for stores
nworbmot Nov 8, 2019
8c834b0
Merge branch 'nomopyomo' of github.com:PyPSA/PyPSA into nomopyomo
nworbmot Nov 8, 2019
e82e5ac
linopf add linkports
FabianHofmann Nov 8, 2019
5a809c2
Merge branch 'nomopyomo' of github.com:PyPSA/pypsa into nomopyomo
FabianHofmann Nov 8, 2019
f058738
- fix functionality for muliline links
FabianHofmann Nov 8, 2019
d99f931
linopf.py: set loads_t.p from frame
FabianHofmann Nov 8, 2019
4be6bef
linopf use xcounter and ccounter locally
FabianHofmann Nov 11, 2019
aabca4f
travis.yml add windows
FabianHofmann Nov 11, 2019
5ee71d1
travis.yml test bash language
FabianHofmann Nov 11, 2019
1b0b796
travis.yml try again integrating windows
FabianHofmann Nov 11, 2019
cf5cb39
travis.yml remove second source conda.sh
FabianHofmann Nov 12, 2019
1756375
travis: exclude cbc from tests (only reproducing glpk solutions now)
FabianHofmann Nov 12, 2019
b299ba9
test/* split concatenated paths in os.join
FabianHofmann Nov 12, 2019
15cfeb1
travis.yml "make test" -> "py.test" for windos compatibility
FabianHofmann Nov 12, 2019
1f84cb0
linopf.py handle permission error for windows
FabianHofmann Nov 12, 2019
62f5264
linopf: try again closing file explicitly
FabianHofmann Nov 12, 2019
6acb6e8
linopf: not open files in binary mode for concatenation
FabianHofmann Nov 12, 2019
4a4badb
test: try with keep_files=True
FabianHofmann Nov 12, 2019
1e6d736
travis: test cbc installation only for linux and osx
FabianHofmann Nov 12, 2019
0990cfc
travis: install cbc after environment activation
FabianHofmann Nov 12, 2019
fcec8bd
travis channel specification for coincbc
FabianHofmann Nov 12, 2019
6453126
travis: try with if else statement
FabianHofmann Nov 12, 2019
83f63f8
travis: rewrite if statement
FabianHofmann Nov 12, 2019
b78e7e5
travis.yaml: try with adding cbc requirement in before_install
FabianHofmann Nov 12, 2019
16a814f
travis.yaml: split environment updates again
FabianHofmann Nov 12, 2019
3b7c74d
travis.yaml: try resolving package conflicts in conda install
FabianHofmann Nov 12, 2019
b726cae
test: reintegrate cbc solvers
FabianHofmann Nov 13, 2019
4dc1f51
travis.yaml: set full matrix of os and python versions
FabianHofmann Nov 13, 2019
5e6548d
test: correct alignment of solvers
FabianHofmann Nov 13, 2019
156f25b
travis: correct typo
FabianHofmann Nov 13, 2019
df08282
linopf.py: explicit closing of temp files via file descriptor (necess…
FabianHofmann Nov 13, 2019
67be12b
linopf.py: fix sign for marginal price
FabianHofmann Nov 13, 2019
841f74c
components.py: sort args of lopf
FabianHofmann Nov 13, 2019
a553d68
linopt.py: ensure integer length
FabianHofmann Nov 13, 2019
98f53ff
Merge branch 'master' into nomopyomo
FabianHofmann Nov 13, 2019
e8be54b
linopf.py: add objective constant to objective function (no post-proc…
FabianHofmann Nov 14, 2019
3fd1a83
linopf/linopt: detache variables and constraints from components
FabianHofmann Nov 16, 2019
d0f096f
linopt:
FabianHofmann Nov 16, 2019
41a4762
linopf: apply define_variables and define_constraints in code
FabianHofmann Nov 16, 2019
882c030
linopt: add aligned_with_static_component function
FabianHofmann Nov 16, 2019
4f4769d
linopt: resolve case for defining variables and constraints with pure
FabianHofmann Nov 17, 2019
2e9eb10
linopf: better case differentiation for mapping duals
FabianHofmann Nov 17, 2019
07f3990
linopt: make as_pandas=True default in linexpr()
FabianHofmann Nov 17, 2019
2e3201d
update doc
FabianHofmann Nov 19, 2019
90fdeb7
* introduce get_dual and get_sol function for easy handling of dual p…
FabianHofmann Nov 20, 2019
a57039d
add example, fix static 'opt' value
FabianHofmann Nov 20, 2019
1e23865
linopt/linopf: correct objective sign
FabianHofmann Nov 20, 2019
c0aaf8b
linopt: rewrite _str_array to more performant
FabianHofmann Nov 20, 2019
842ee8b
linopt set output type in _v_to_float_str
FabianHofmann Nov 20, 2019
73d8e5c
add simplified unit commitment
FabianHofmann Nov 21, 2019
5bdbcd2
linopf correct warning string
FabianHofmann Nov 21, 2019
c568ec8
linopf: omit ramp constraints for committables if not any there
FabianHofmann Nov 21, 2019
0358473
doc update
FabianHofmann Nov 22, 2019
056af01
doc update II
FabianHofmann Nov 22, 2019
a3ef8a6
add solver_dir argument for specifying io directory for solving (defa…
FabianHofmann Nov 25, 2019
69c9880
linopf: again, fix dual sign (a general rule is still unlclear)
FabianHofmann Nov 26, 2019
f0e533b
store integer references instead of strings
FabianHofmann Nov 26, 2019
cbe6e81
clean lp file writing a bit
FabianHofmann Nov 26, 2019
4991457
linopt: fix reading solution for glpk
FabianHofmann Nov 26, 2019
ceb5406
test: assert optimality
FabianHofmann Nov 26, 2019
9112473
set status to 'ok' for optimal solutions
FabianHofmann Nov 27, 2019
b0f1d3a
linopt: Stop termination_condition being reset after gurobi solves
nworbmot Dec 2, 2019
7026e18
fix adding multiple global constraints
FabianHofmann Dec 11, 2019
70ecac5
ensure carrier is defined in ilopf
FabianHofmann Dec 16, 2019
8f443cd
fix discussed error for multiple static global constraints
FabianHofmann Dec 16, 2019
b381048
Merge branch 'master' into nomopyomo
nworbmot Dec 19, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
Expand Up @@ -6,8 +6,8 @@ sudo: false # Use container-based infrastructure

matrix:
include:
- env:
- PYTHON_VERSION="2.7"
# - env:
# - PYTHON_VERSION="2.7"
- env:
- PYTHON_VERSION="3.6"
- env:
Expand All @@ -23,7 +23,7 @@ before_install:
- export PATH="$HOME/miniconda/bin:$PATH"
- hash -r
- conda config --set always_yes yes --set changeps1 no
- conda update -q conda
# - conda update -q conda
# Useful for debugging any issues with conda
- conda info -a
- source $HOME/miniconda/etc/profile.d/conda.sh
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
@@ -1,5 +1,6 @@
include pypsa/component_attrs/*.csv
include pypsa/standard_types/*.csv
include pypsa/components.csv
include pypsa/variables.csv
include README.rst LICENSE.txt
include requirements.yml
247 changes: 136 additions & 111 deletions doc/optimal_power_flow.rst

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions environment.yaml
Expand Up @@ -14,3 +14,4 @@ dependencies:
- cartopy>=0.16
- coincbc
- glpk
- gurobi::gurobi
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to include gurobi here as a dependency? I currently tend to say no. Any counterarguments?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gurobipy is needed to solve the lp problem without pyomo and as for as I know, does not come automatically with pyomo (which is also in the dependency)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jep, that's true. gurobi doesn't come with pyomo. But as I understand the script it's possible to chose between cbc and gurobi, isn't it?
I would suggest to remove gurobi as a dependency, as it's a commercial solver and the users of pypsa should decide on their own to buy and install gurobi or not.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is, supported for now are

  • glpk
  • cbc
  • gurobi

I totally agree on the point of letting the user decide which solver to use. But is does not contradict. Installing gurobipy means only installing the interface, not the solver itself. This counts for the other solvers as well, none of them is installed pypsa. Like this the code can guarantee, that as soon as the solver is installed, the optimization runs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but if you guys don't agree, I don't mind taking it out :)

10 changes: 9 additions & 1 deletion pypsa/__init__.py
Expand Up @@ -26,7 +26,15 @@
from __future__ import absolute_import

from . import components, descriptors
from . import pf, opf, plot, networkclustering, io, contingency, geo
from . import pf, opf, opt, plot, networkclustering, io, contingency, geo, stats

import sys

#do this as long as python 2.7 should be supported
if sys.version_info.major >= 3:
from . import linopf, linopt



from .components import Network, SubNetwork

Expand Down
5 changes: 5 additions & 0 deletions pypsa/component_attrs/generators.csv
Expand Up @@ -30,3 +30,8 @@ p,series,MW,0.,active power at bus (positive if net generation),Output
q,series,MVar,0.,reactive power (positive if net generation),Output
p_nom_opt,float,MW,0.,Optimised nominal power.,Output
status,series,n/a,1,"Status (1 is on, 0 is off). Only outputted if committable is True.",Output
mu_upper,series,currency/MWh,0.,Shadow price of upper p_nom limit,Output
mu_lower,series,currency/MWh,0.,Shadow price of lower p_nom limit,Output
mu_p_set,series,currency/MWh,0.,Shadow price of fixed power generation p_set,Output
mu_ramp_limit_up,series,currency/MWh,0.,Shadow price of upper ramp up limit,Output
mu_ramp_limit_down,series,currency/MWh,0.,Shadow price of lower ramp down limit,Output
3 changes: 2 additions & 1 deletion pypsa/component_attrs/links.csv
Expand Up @@ -19,4 +19,5 @@ p0,series,MW,0.,Active power at bus0 (positive if branch is withdrawing power fr
p1,series,MW,0.,Active power at bus1 (positive if branch is withdrawing power from bus1).,Output
p_nom_opt,float,MVA,0.,Optimised capacity for active power.,Output
mu_lower,series,currency/MVA,0.,Shadow price of lower p_nom limit -F \leq f. Always non-negative.,Output
mu_upper,series,currency/MVA,0.,Shadow price of upper p_nom limit f \leq F. Always non-negative.,Output
mu_upper,series,currency/MVA,0.,Shadow price of upper p_nom limit f \leq F. Always non-negative.,Output
mu_p_set,series,currency/MWh,0.,Shadow price of fixed power transmission p_set,Output
5 changes: 5 additions & 0 deletions pypsa/component_attrs/storage_units.csv
Expand Up @@ -24,7 +24,12 @@ efficiency_dispatch,float,per unit,1.,Efficiency of storage on the way out of th
standing_loss,float,per unit,0.,Losses per hour to state of charge.,Input (optional)
inflow,static or series,MW,0.,"Inflow to the state of charge, e.g. due to river inflow in hydro reservoir.",Input (optional)
p,series,MW,0.,active power at bus (positive if net generation),Output
p_dispatch,series,MW,0.,active power dispatch at bus,Output
p_store,series,MW,0.,active power charging at bus,Output
q,series,MVar,0.,reactive power (positive if net generation),Output
state_of_charge,series,MWh,NaN,State of charge as calculated by the OPF.,Output
spill,series,MW,0.,Spillage for each snapshot.,Output
p_nom_opt,float,MW,0.,Optimised nominal power.,Output
mu_upper,series,currency/MWh,0.,Shadow price of upper p_nom limit,Output
mu_lower,series,currency/MWh,0.,Shadow price of lower p_nom limit,Output
mu_state_of_charge_set,series,currency/MWh,0.,Shadow price of fixed state of charge state_of_charge_set,Output
5 changes: 4 additions & 1 deletion pypsa/component_attrs/stores.csv
Expand Up @@ -19,4 +19,7 @@ standing_loss,float,per unit,0.,Losses per hour to energy.,Input (optional)
p,series,MW,0.,active power at bus (positive if net generation),Output
q,series,MVar,0.,reactive power (positive if net generation),Output
e,series,MWh,0.,Energy as calculated by the OPF.,Output
e_nom_opt,float,MW,0.,Optimised nominal energy capacity outputed by OPF.,Output
e_nom_opt,float,MW,0.,Optimised nominal energy capacity outputed by OPF.,Output
mu_upper,series,currency/MWh,0.,Shadow price of upper e_nom limit,Output
mu_lower,series,currency/MWh,0.,Shadow price of lower e_nom limit,Output
mu_e_set,series,currency/MWh,0.,Shadow price of fixed energy level e_set,Output
111 changes: 102 additions & 9 deletions pypsa/components.py
Expand Up @@ -21,24 +21,18 @@

# make the code as Python 3 compatible as possible
from __future__ import division, absolute_import
import six
from six import iteritems, itervalues, iterkeys
from six.moves import map
from weakref import ref


__author__ = "Tom Brown (FIAS), Jonas Hoersch (FIAS), David Schlachtberger (FIAS)"
__copyright__ = "Copyright 2015-2017 Tom Brown (FIAS), Jonas Hoersch (FIAS), David Schlachtberger (FIAS), GNU GPL 3"

import networkx as nx

import numpy as np
import pandas as pd
import scipy as sp, scipy.sparse
from scipy.sparse import csgraph
from itertools import chain
from collections import namedtuple
from operator import itemgetter
import os

Comment on lines 30 to 37
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow that's a lot of unneeded imports. Didn't know.


Expand Down Expand Up @@ -71,10 +65,11 @@

from .graph import graph, incidence_matrix, adjacency_matrix

import inspect

import sys

if sys.version_info.major >= 3:
from .linopf import network_lopf as network_lopf_lowmem

import logging
logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -198,7 +193,7 @@ class Network(Basic):

pf = network_pf

lopf = network_lopf
# lopf = network_lopf
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete?


opf = network_opf

Expand Down Expand Up @@ -407,6 +402,102 @@ def set_snapshots(self,snapshots):

#NB: No need to rebind pnl to self, since haven't changed it

def lopf(self, snapshots=None, solver_name="glpk", extra_functionality=None,
solver_logfile=None, solver_options={}, keep_files=False,
formulation="kirchhoff", extra_postprocessing=None, pyomo=True,
**kwargs):
"""
Linear optimal power flow for a group of snapshots.

Parameters
----------
snapshots : list or index slice
A list of snapshots to optimise, must be a subset of
network.snapshots, defaults to network.snapshots
solver_name : string
Must be a solver name that pyomo recognises and that is
installed, e.g. "glpk", "gurobi"
pyomo : bool, default True
Whether to use pyomo for building and solving the model, setting
this to False saves a lot of memory and time.
solver_logfile : None|string
If not None, sets the logfile option of the solver.
solver_options : dictionary
A dictionary with additional options that get passed to the solver.
(e.g. {'threads':2} tells gurobi to use only 2 cpus)
keep_files : bool, default False
Keep the files that pyomo constructs from OPF problem
construction, e.g. .lp file - useful for debugging
formulation : string
Formulation of the linear power flow equations to use; must be
one of ["angles","cycles","kirchhoff","ptdf"]
extra_functionality : callable function
This function must take two arguments
`extra_functionality(network,snapshots)` and is called after
the model building is complete, but before it is sent to the
solver. It allows the user to
add/change constraints and add/change the objective function.

Other Parameters
----------------

ptdf_tolerance : float
Only when pyomo is True.
Value below which PTDF entries are ignored
free_memory : set, default {'pyomo'}
Only when pyomo is True.
Any subset of {'pypsa', 'pyomo'}. Allows to stash `pypsa` time-series
data away while the solver runs (as a pickle to disk) and/or free
`pyomo` data after the solution has been extracted.
solver_io : string, default None
Only when pyomo is True.
Solver Input-Output option, e.g. "python" to use "gurobipy" for
solver_name="gurobi"
skip_pre : bool, default False
Only when pyomo is True.
Skip the preliminary steps of computing topology, calculating
dependent values and finding bus controls.
extra_postprocessing : callable function
This function must take three arguments
`extra_postprocessing(network,snapshots,duals)` and is called after
the model has solved and the results are extracted. It allows the user
to extract further information about the solution, such as additional
shadow prices.
warmstart : bool or string, default False
Only when pyomo is False.
Use this to warmstart the optimization. Pass a string which gives
the path to the basis file. If set to True, a path to
a basis file must be given in network.basis_fn.
store_basis : bool, default True
Only when pyomo is False.
Whether to store the basis of the optimization results. If True,
the path to the basis file is saved in network.basis_fn. Note that
a basis can only be stored if simplex, dual-simplex, or barrier
*with* crossover is used for solving.
keep_references : bool, default False
Only when pyomo is False.
Keep the references of variable and constraint names withing the
network, e.g. n.generators_t.p_varref - useful for constructing
extra_functionality or debugging
keep_shadowprices : bool or list of component names, default None
Only when pyomo is False.
Keep shadow prices for all constraints, if set to True.
These are stored at e.g. n.generators_t.mu_upper for upper limit
of p_nom. If a list of component names is passed, shadow
prices of variables attached to those are extracted. If set to None,
components default to ['Bus', 'Line', 'GlobalConstraint']

"""
args = {'snapshots': snapshots, 'keep_files': keep_files,
'solver_options': solver_options, 'formulation': formulation,
'extra_functionality': extra_functionality,
'solver_name': solver_name, 'solver_logfile': solver_logfile}
args.update(kwargs)
if pyomo:
return network_lopf(self, **args)
else:
return network_lopf_lowmem(self, **args)



def add(self, class_name, name, **kwargs):
Expand Down Expand Up @@ -834,6 +925,8 @@ def determine_network_topology(self):

for sub in self.sub_networks.obj:
find_cycles(sub)
sub.find_bus_controls()


def iterate_components(self, components=None, skip_empty=True):
if components is None:
Expand Down
76 changes: 71 additions & 5 deletions pypsa/descriptors.py
Expand Up @@ -22,19 +22,15 @@
# make the code as Python 3 compatible as possible
from __future__ import division
from __future__ import absolute_import
from six import iteritems, string_types
from six import iteritems


__author__ = "Tom Brown (FIAS), Jonas Hoersch (FIAS)"
__copyright__ = "Copyright 2015-2017 Tom Brown (FIAS), Jonas Hoersch (FIAS), GNU GPL 3"





#weak references are necessary to make sure the key-value pair are
#destroyed if the key object goes out of scope
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete if relevant imports are removed?

from weakref import WeakKeyDictionary

from collections import OrderedDict
from itertools import repeat
Expand Down Expand Up @@ -303,3 +299,73 @@ def zsum(s, *args, **kwargs):
Meant to be set as pd.Series.zsum = zsum.
"""
return 0 if s.empty else s.sum(*args, **kwargs)

#Perhaps this should rather go into components.py
nominal_attrs = {'Generator': 'p_nom',
'Line': 's_nom',
'Transformer': 's_nom',
'Link': 'p_nom',
'Store': 'e_nom',
'StorageUnit': 'p_nom'}

def expand_series(ser, columns):
"""
Helper function to quickly expand a series to a dataframe with according
column axis and every single column being the equal to the given series.
"""
return ser.to_frame(columns[0]).reindex(columns=columns).ffill(axis=1)


def get_extendable_i(n, c):
"""
Getter function. Get the index of extendable elements of a given component.
"""
return n.df(c)[lambda ds:
ds[nominal_attrs[c] + '_extendable']].index

def get_non_extendable_i(n, c):
"""
Getter function. Get the index of non-extendable elements of a given
component.
"""
return n.df(c)[lambda ds:
~ds[nominal_attrs[c] + '_extendable']].index

def get_bounds_pu(n, c, sns, index=slice(None), attr=None):
"""
Getter function to retrieve the per unit bounds of a given compoent for
given snapshots and possible subset of elements (e.g. non-extendables).
Depending on the attr you can further specify the bounds of the variable
you are looking at, e.g. p_store for storage units.

Parameters
----------
n : pypsa.Network
c : string
Component name, e.g. "Generator", "Line".
sns : pandas.Index/pandas.DateTimeIndex
set of snapshots for the bounds
index : pd.Index, default None
Subset of the component elements. If None (default) bounds of all
elements are returned.
attr : string, default None
attribute name for the bounds, e.g. "p", "s", "p_store"

"""
min_pu_str = nominal_attrs[c].replace('nom', 'min_pu')
max_pu_str = nominal_attrs[c].replace('nom', 'max_pu')

max_pu = get_switchable_as_dense(n, c, max_pu_str, sns)
if c in n.passive_branch_components:
min_pu = - max_pu
elif c == 'StorageUnit':
min_pu = pd.DataFrame(0, max_pu.index, max_pu.columns)
if attr == 'p_store':
max_pu = - get_switchable_as_dense(n, c, min_pu_str, sns)
if attr == 'state_of_charge':
max_pu = expand_series(n.df(c).max_hours, sns).T
min_pu = pd.DataFrame(0, *max_pu.axes)
else:
min_pu = get_switchable_as_dense(n, c, min_pu_str, sns)
return min_pu[index], max_pu[index]