Note
You can download this example as a Jupyter notebook or start it in interactive mode.
Battery Electric Vehicle Charging
In this example a battery electric vehicle (BEV) is driven 100 km in the morning and 100 km in the evening, to simulate commuting, and charged during the day by a solar panel at the driver’s place of work. The size of the panel is computed by the optimisation.
The BEV has a battery of size 100 kWh and an electricity consumption of 0.18 kWh/km.
NB: this example will use units of kW and kWh, unlike the PyPSA defaults
[1]:
import pypsa
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
[2]:
# use 24 hour period for consideration
index = pd.date_range("2016-01-01 00:00","2016-01-01 23:00", freq="H")
# consumption pattern of BEV
bev_usage = pd.Series([0.]*7 + [9.]*2 + [0.]*8 + [9.]*2 + [0.]*5, index)
# solar PV panel generation per unit of capacity
pv_pu = pd.Series([0.]*7 + [0.2,0.4,0.6,0.75,0.85,0.9,0.85,0.75,0.6,0.4,0.2,0.1] + [0.]*5, index)
# availability of charging - i.e. only when parked at office
charger_p_max_pu = pd.Series(0, index=index)
charger_p_max_pu["2016-01-01 09:00":"2016-01-01 16:00"] = 1.
[3]:
df = pd.concat({'BEV': bev_usage, 'PV': pv_pu, 'Charger': charger_p_max_pu}, axis=1)
df.plot.area(subplots=True, figsize=(10,7))
plt.tight_layout()
Initialize the network
[4]:
network = pypsa.Network()
network.set_snapshots(index)
network.add("Bus",
"place of work",
carrier="AC")
network.add("Bus",
"battery",
carrier="Li-ion")
network.add("Generator",
"PV panel",
bus="place of work",
p_nom_extendable=True,
p_max_pu=pv_pu,
capital_cost=1000.)
network.add("Load",
"driving",
bus="battery",
p_set=bev_usage)
network.add("Link",
"charger",
bus0="place of work",
bus1="battery",
p_nom=120, #super-charger with 120 kW
p_max_pu=charger_p_max_pu,
efficiency=0.9)
network.add("Store",
"battery storage",
bus="battery",
e_cyclic=True,
e_nom=100.)
[5]:
network.lopf()
print("Objective:",network.objective)
INFO:pypsa.opf:Performed preliminary steps
INFO:pypsa.opf:Building pyomo model using `kirchhoff` formulation
INFO:pypsa.opf:Solving model using glpk
WARNING:pyomo.solvers:Could not locate the 'glpsol' executable, which is required for solver 'glpk'
---------------------------------------------------------------------------
ApplicationError Traceback (most recent call last)
/tmp/ipykernel_207/1226684916.py in <module>
----> 1 network.lopf()
2 print("Objective:",network.objective)
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/components.py in lopf(self, snapshots, pyomo, solver_name, solver_options, solver_logfile, formulation, keep_files, extra_functionality, multi_investment_periods, **kwargs)
646
647 if pyomo:
--> 648 return network_lopf(self, **args)
649 else:
650 return network_lopf_lowmem(self, **args)
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/opf.py in network_lopf(network, snapshots, solver_name, solver_io, skip_pre, extra_functionality, multi_investment_periods, solver_logfile, solver_options, keep_files, formulation, ptdf_tolerance, free_memory, extra_postprocessing)
1663 solver_logfile=solver_logfile, solver_options=solver_options,
1664 keep_files=keep_files, free_memory=free_memory,
-> 1665 extra_postprocessing=extra_postprocessing)
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/opf.py in network_lopf_solve(network, snapshots, formulation, solver_options, solver_logfile, keep_files, free_memory, extra_postprocessing)
1563 network.results = network.opt.solve(*args, suffixes=["dual"], keepfiles=keep_files, logfile=solver_logfile, options=solver_options)
1564 else:
-> 1565 network.results = network.opt.solve(*args, suffixes=["dual"], keepfiles=keep_files, logfile=solver_logfile, options=solver_options)
1566
1567 if logger.isEnabledFor(logging.INFO):
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pyomo/opt/base/solvers.py in solve(self, *args, **kwds)
510 """ Solve the problem """
511
--> 512 self.available(exception_flag=True)
513 #
514 # If the inputs are models, then validate that they have been
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pyomo/opt/solver/shellcmd.py in available(self, exception_flag)
123 if exception_flag:
124 msg = "No executable found for solver '%s'"
--> 125 raise ApplicationError(msg % self.name)
126 return False
127 return True
ApplicationError: No executable found for solver 'glpk'
The optimal panel size in kW is
[6]:
network.generators.p_nom_opt["PV panel"]
[6]:
0.0
[7]:
network.generators_t.p.plot.area(figsize = (9,4))
plt.tight_layout()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_207/3599074643.py in <module>
----> 1 network.generators_t.p.plot.area(figsize = (9,4))
2 plt.tight_layout()
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_core.py in area(self, x, y, **kwargs)
1494 >>> ax = df.plot.area(x='day')
1495 """
-> 1496 return self(kind="area", x=x, y=y, **kwargs)
1497
1498 def pie(self, **kwargs):
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs)
970 data.columns = label_name
971
--> 972 return plot_backend.plot(data, kind=kind, **kwargs)
973
974 __call__.__doc__ = __doc__
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs)
69 kwargs["ax"] = getattr(ax, "left_ax", ax)
70 plot_obj = PLOT_CLASSES[kind](data, **kwargs)
---> 71 plot_obj.generate()
72 plot_obj.draw()
73 return plot_obj.result
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in generate(self)
284 def generate(self):
285 self._args_adjust()
--> 286 self._compute_plot_data()
287 self._setup_subplots()
288 self._make_plot()
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _compute_plot_data(self)
451 # no non-numeric frames or series allowed
452 if is_empty:
--> 453 raise TypeError("no numeric data to plot")
454
455 self.data = numeric_data.apply(self._convert_to_ndarray)
TypeError: no numeric data to plot
[8]:
df = pd.DataFrame({attr: network.stores_t[attr]["battery storage"] for attr in ["p","e"]})
df.plot(grid=True, figsize = (10,5))
plt.legend(labels=['Energy output', 'State of charge'])
plt.tight_layout()
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
3360 try:
-> 3361 return self._engine.get_loc(casted_key)
3362 except KeyError as err:
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: 'battery storage'
The above exception was the direct cause of the following exception:
KeyError Traceback (most recent call last)
/tmp/ipykernel_207/1517764456.py in <module>
----> 1 df = pd.DataFrame({attr: network.stores_t[attr]["battery storage"] for attr in ["p","e"]})
2 df.plot(grid=True, figsize = (10,5))
3 plt.legend(labels=['Energy output', 'State of charge'])
4 plt.tight_layout()
/tmp/ipykernel_207/1517764456.py in <dictcomp>(.0)
----> 1 df = pd.DataFrame({attr: network.stores_t[attr]["battery storage"] for attr in ["p","e"]})
2 df.plot(grid=True, figsize = (10,5))
3 plt.legend(labels=['Energy output', 'State of charge'])
4 plt.tight_layout()
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/frame.py in __getitem__(self, key)
3456 if self.columns.nlevels > 1:
3457 return self._getitem_multilevel(key)
-> 3458 indexer = self.columns.get_loc(key)
3459 if is_integer(indexer):
3460 indexer = [indexer]
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
3361 return self._engine.get_loc(casted_key)
3362 except KeyError as err:
-> 3363 raise KeyError(key) from err
3364
3365 if is_scalar(key) and isna(key) and not self.hasnans:
KeyError: 'battery storage'
The losses in kWh per pay are:
[9]:
network.generators_t.p.loc[:,"PV panel"].sum() - network.loads_t.p.loc[:,"driving"].sum()
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
3360 try:
-> 3361 return self._engine.get_loc(casted_key)
3362 except KeyError as err:
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: 'PV panel'
The above exception was the direct cause of the following exception:
KeyError Traceback (most recent call last)
/tmp/ipykernel_207/3394708287.py in <module>
----> 1 network.generators_t.p.loc[:,"PV panel"].sum() - network.loads_t.p.loc[:,"driving"].sum()
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/indexing.py in __getitem__(self, key)
923 with suppress(KeyError, IndexError):
924 return self.obj._get_value(*key, takeable=self._takeable)
--> 925 return self._getitem_tuple(key)
926 else:
927 # we by definition only have the 0th axis
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/indexing.py in _getitem_tuple(self, tup)
1098 def _getitem_tuple(self, tup: tuple):
1099 with suppress(IndexingError):
-> 1100 return self._getitem_lowerdim(tup)
1101
1102 # no multi-index, so validate all of the indexers
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/indexing.py in _getitem_lowerdim(self, tup)
836 # We don't need to check for tuples here because those are
837 # caught by the _is_nested_tuple_indexer check above.
--> 838 section = self._getitem_axis(key, axis=i)
839
840 # We should never have a scalar section here, because
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/indexing.py in _getitem_axis(self, key, axis)
1162 # fall thru to straight lookup
1163 self._validate_key(key, axis)
-> 1164 return self._get_label(key, axis=axis)
1165
1166 def _get_slice_axis(self, slice_obj: slice, axis: int):
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/indexing.py in _get_label(self, label, axis)
1111 def _get_label(self, label, axis: int):
1112 # GH#5667 this will fail if the label is not present in the axis.
-> 1113 return self.obj.xs(label, axis=axis)
1114
1115 def _handle_lowerdim_multi_index_axis0(self, tup: tuple):
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/generic.py in xs(self, key, axis, level, drop_level)
3759 if axis == 1:
3760 if drop_level:
-> 3761 return self[key]
3762 index = self.columns
3763 else:
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/frame.py in __getitem__(self, key)
3456 if self.columns.nlevels > 1:
3457 return self._getitem_multilevel(key)
-> 3458 indexer = self.columns.get_loc(key)
3459 if is_integer(indexer):
3460 indexer = [indexer]
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
3361 return self._engine.get_loc(casted_key)
3362 except KeyError as err:
-> 3363 raise KeyError(key) from err
3364
3365 if is_scalar(key) and isna(key) and not self.hasnans:
KeyError: 'PV panel'
[10]:
network.links_t.p0.plot.area(figsize = (9,5))
plt.tight_layout()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_207/1261738793.py in <module>
----> 1 network.links_t.p0.plot.area(figsize = (9,5))
2 plt.tight_layout()
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_core.py in area(self, x, y, **kwargs)
1494 >>> ax = df.plot.area(x='day')
1495 """
-> 1496 return self(kind="area", x=x, y=y, **kwargs)
1497
1498 def pie(self, **kwargs):
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs)
970 data.columns = label_name
971
--> 972 return plot_backend.plot(data, kind=kind, **kwargs)
973
974 __call__.__doc__ = __doc__
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs)
69 kwargs["ax"] = getattr(ax, "left_ax", ax)
70 plot_obj = PLOT_CLASSES[kind](data, **kwargs)
---> 71 plot_obj.generate()
72 plot_obj.draw()
73 return plot_obj.result
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in generate(self)
284 def generate(self):
285 self._args_adjust()
--> 286 self._compute_plot_data()
287 self._setup_subplots()
288 self._make_plot()
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _compute_plot_data(self)
451 # no non-numeric frames or series allowed
452 if is_empty:
--> 453 raise TypeError("no numeric data to plot")
454
455 self.data = numeric_data.apply(self._convert_to_ndarray)
TypeError: no numeric data to plot