edit2
ShahlaHuseynova 2 years ago
parent ac3c883bef
commit 4f7cb1b9c3

@ -1,616 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 66,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The autoreload extension is already loaded. To reload it, use:\n",
" %reload_ext autoreload\n"
]
}
],
"source": [
"from pyrecoy.assets import HotWaterStorage, HeatBuffer\\\n",
"\n",
"%load_ext autoreload\n",
"%autoreload 2"
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"HotWaterStorage(name=HWS, max_power=10, min_power=-10, roundtrip_eff=1, capacity_per_volume=0.05, volume = 1000),lifetime=25, temperature = 368, min_storagelevel = 5)"
]
},
"execution_count": 67,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hws = HotWaterStorage(\n",
" name='HWS',\n",
" max_power=10,\n",
" min_power=-10,\n",
" roundtrip_eff=1,\n",
" capacity_per_volume = 50 * 1e-3,\n",
" volume = 1000,\n",
" lifetime = 25,\n",
" temperature = 368, #K\n",
" min_storagelevel = 5,\n",
")\n",
"\n",
"hws"
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {},
"outputs": [],
"source": [
"hws.storagelevel\n",
"hws.set_freq('15T')"
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"7.5"
]
},
"execution_count": 69,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hws.charge(10)\n",
"hws.storagelevel"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"32.5"
]
},
"execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hws.charge(100)\n",
"hws.storagelevel"
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"47.5"
]
},
"execution_count": 71,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hws.charge(100)\n",
"hws.storagelevel"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"22.5"
]
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hws.discharge(100)\n",
"hws.storagelevel"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"5.0"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hws.discharge(100)\n",
"hws.storagelevel"
]
},
{
"cell_type": "code",
"execution_count": 104,
"metadata": {},
"outputs": [],
"source": [
"hb = HeatBuffer(\n",
" name='HB',\n",
" rated_power=10, \n",
" capacity_per_volume = 50 * 1e-3,\n",
" volume=1000, \n",
" temperature=368,\n",
" min_storagelevel = 5,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 105,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"50.0"
]
},
"execution_count": 105,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.capacity"
]
},
{
"cell_type": "code",
"execution_count": 106,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"5.0"
]
},
"execution_count": 106,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.min_chargelevel"
]
},
{
"cell_type": "code",
"execution_count": 107,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"47.5"
]
},
"execution_count": 107,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.max_chargelevel"
]
},
{
"cell_type": "code",
"execution_count": 109,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"HeatBuffer(name=HB, rated_power=10, capacity=50.0, temperature = 368, min_storagelevel = 5.0)"
]
},
"execution_count": 109,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 79,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"5.0"
]
},
"execution_count": 79,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.set_freq('15T')\n",
"hb.chargelevel"
]
},
{
"cell_type": "code",
"execution_count": 80,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"7.5"
]
},
"execution_count": 80,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.charge(10)\n",
"hb.chargelevel"
]
},
{
"cell_type": "code",
"execution_count": 81,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"-10.0"
]
},
"execution_count": 81,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.charge(100)"
]
},
{
"cell_type": "code",
"execution_count": 82,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"10.0"
]
},
"execution_count": 82,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.chargelevel"
]
},
{
"cell_type": "code",
"execution_count": 83,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"-10.0"
]
},
"execution_count": 83,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.charge(100)"
]
},
{
"cell_type": "code",
"execution_count": 84,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"12.5"
]
},
"execution_count": 84,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.chargelevel"
]
},
{
"cell_type": "code",
"execution_count": 85,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"10.0"
]
},
"execution_count": 85,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.discharge(100)"
]
},
{
"cell_type": "code",
"execution_count": 86,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"10.0"
]
},
"execution_count": 86,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.chargelevel"
]
},
{
"cell_type": "code",
"execution_count": 87,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"10.0"
]
},
"execution_count": 87,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.discharge(100)"
]
},
{
"cell_type": "code",
"execution_count": 88,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"7.5"
]
},
"execution_count": 88,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.chargelevel"
]
},
{
"cell_type": "code",
"execution_count": 89,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"5.0"
]
},
"execution_count": 89,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.discharge(5)"
]
},
{
"cell_type": "code",
"execution_count": 90,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6.25"
]
},
"execution_count": 90,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.chargelevel"
]
},
{
"cell_type": "code",
"execution_count": 91,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"5.0"
]
},
"execution_count": 91,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.discharge(10)"
]
},
{
"cell_type": "code",
"execution_count": 92,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"5.0"
]
},
"execution_count": 92,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hb.chargelevel"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def set_financials(\n",
" self,\n",
" capex_per_MW,\n",
" capex_per_MWh,\n",
" opex,\n",
" devex,\n",
" lifetime=None,\n",
" depreciate=True,\n",
" salvage_value=0,\n",
"):\n",
" total_capex = (\n",
" capex_per_MW * self.max_power + capex_per_MWh * self.max_storage_capacity\n",
" )\n",
" super().set_financials(\n",
" total_capex, opex, devex, lifetime=None, depreciate=True, salvage_value=0\n",
" )\n",
"\n",
"def __repr__(self):\n",
" return (\n",
" f\"{self.__class__.__name__}(name={self.name}, max_power={self.max_power}, \"\n",
" f\"min_power={self.min_power}, roundtrip_eff={self.roundtrip_eff}, capacity_per_volume={self.capacity_per_volume}, volume = {self.volume}),\"\n",
" f\"lifetime={self.lifetime}, temperature = {self.temperature}, min_storagelevel = {self.min_storagelevel})\"\n",
" )"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.9"
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"state": {},
"version_major": 2,
"version_minor": 0
}
}
},
"nbformat": 4,
"nbformat_minor": 4
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

@ -1,58 +0,0 @@
{
"2020": {
"electricity": {
"1": {
"EB": 0.09770,
"ODE": 0.0273
},
"2": {
"EB": 0.05083,
"ODE": 0.0375
},
"3": {
"EB": 0.01353,
"ODE": 0.0205
},
"4": {
"EB": 0.00055,
"ODE": 0.0004
}
},
"gas": {
"1": {
"EB": 0.33307,
"ODE": 0.0775
},
"2": {
"EB": 0.06444,
"ODE": 0.0214
},
"3": {
"EB": 0.02348,
"ODE": 0.0212
},
"4": {
"EB": 0.01261,
"ODE": 0.0212
}
},
"gas_horticulture": {
"1": {
"EB": 0.05348,
"ODE": 0.0124
},
"2": {
"EB": 0.02432,
"ODE": 0.0081
},
"3": {
"EB": 0.02348,
"ODE": 0.0212
},
"4": {
"EB": 0.01261,
"ODE": 0.0212
}
}
}
}

@ -1,414 +0,0 @@
import warnings
from functools import partial, lru_cache
from numbers import Number
from itertools import count
import numpy as np
from numpy.polynomial import Polynomial
from scipy.optimize import minimize_scalar
# from .converters import *
class Asset:
"""Generic class for producing/consuming assets. Specific asset classes can
inherit from this class.
Parameters:
-----------
max_power : int/float
Maximum asset power in MW electric
min_power : int/float
Minimium asset load in MW electric
Usage:
------
Use the set_load and get_load methods to set and get asset status in MW.
Convention is negative values for inputs (consumption) and positive
values for outputs (production).
"""
_freq_to_multiplier = {"H": 1, "15T": (1 / 4), "1T": (1 / 60)}
_ids = count(0)
def __init__(self, name, max_power, min_power):
if min_power > max_power:
raise ValueError("'min_power' can not be larger than 'max_power'.")
self.name = name
self.id = next(self._ids)
self.max_power = max_power
self.min_power = min_power
self.modes = {"max": max_power, "min": min_power}
def __repr__(self):
return f"{self.__class__.__name__}(self, max_power={self.max_power}, min_power={self.min_power})"
def set_load(self, load):
"""Set Asset load in MW.
Convention is negative value for consumption and positive value
for production. Subclasses might use a different convention if
this seems more intiutive.
Returns the load that is set in MW.
"""
if load < self.min_power or load > self.max_power:
warnings.warn(
f"Chosen Asset load for {self.name} is out of range. "
f"Should be between {self.min_power} and {self.max_power}. "
f"Function will return boundary load level for now."
)
load = min(max(load, self.min_power), self.max_power)
return load
def set_mode(self, mode):
""" """
load = self.modes[mode]
return self.set_load(load)
def MW_to_MWh(self, MW):
"""Performs conversion from MW to MWh using the time_factor variable."""
return MW * self.time_factor
def MWh_to_MW(self, MWh):
"""Performs conversion from MWh to MW using the time_factor variable."""
return MWh / self.time_factor
def set_freq(self, freq):
"""
Function that aligns time frequency between Model and Asset.
Can be '1T', '15T' or 'H'
The time_factor variable is used in subclasses to perform MW to MWh conversions.
"""
self.freq = freq
self.time_factor = Asset._freq_to_multiplier[freq]
def set_financials(self, capex, opex, devex, lifetime=None, depreciate=True, salvage_value=0):
"""Set financial data of the asset."""
self.capex = capex
self.opex = opex
self.devex = devex
self.lifetime = lifetime
self.depreciate = depreciate
self.salvage_value = salvage_value
class WaterStorage(Asset):
def __init__(
self,
name,
max_power,
min_power,
roundtrip_eff,
capacity_per_volume,
volume,
lifetime,
temperature,
min_storagelevel,
initial_storagelevel=None
):
if min_power > max_power:
raise ValueError("'min_power' can not be larger than 'max_power'.")
super().__init__(name, max_power, min_power)
self.roundtrip_eff = roundtrip_eff
self.volume = volume
self.lifetime = lifetime
self.temperature = temperature
self.capacity_per_volume = capacity_per_volume #MWh/m3
self.max_storage_capacity = self.volume * self.capacity_per_volume #MWh
self.max_storagelevel = self.max_storage_capacity * 0.95
self.min_storagelevel = min_storagelevel
if initial_storagelevel:
self.storagelevel = initial_storagelevel
else:
self.storagelevel = self.min_storagelevel
def set_storagelevel(self, storagelevel):
"""Set the storagelevel in MWh."""
if storagelevel < self.min_storagelevel or storagelevel > self.max_storagelevel:
raise ValueError(
f"Tried to set Storage Level to {storagelevel}. "
f"Storage Level must be a value between "
f"{self.min_storagelevel} and {self.max_storagelevel} (in MWh)"
)
self.storagelevel = storagelevel
@property
def charging_power_limit(self):
max_charging_energy = self.max_storagelevel - self.storagelevel
max_charging_power = min(self.MWh_to_MW(max_charging_energy), -self.min_power)
return max_charging_power
@property
def discharging_power_limit(self):
max_discharging_energy = self.storagelevel - self.min_storagelevel
max_discharging_power = min(self.MWh_to_MW(max_discharging_energy), self.max_power)
return max_discharging_power
def charge(self, power):
energy = self.MW_to_MWh(power)
max_charging = self.max_storagelevel - self.storagelevel
bounded_energy = min (energy, max_charging)
# print('bounded_energy', bounded_energy)
new_cl = self.storagelevel + bounded_energy
# print('new_cl', new_cl)
self.set_storagelevel(new_cl)
power = self.MWh_to_MW(bounded_energy)
return power
def discharge(self, power):
energy = self.MW_to_MWh(power)
max_discharging = self.storagelevel - self.min_storagelevel
bounded_energy = min(energy, max_discharging)
# print('bounded_energy', bounded_energy)
new_cl = self.storagelevel - bounded_energy
# print('new_cl', new_cl)
self.set_storagelevel(new_cl)
power = self.MWh_to_MW(bounded_energy)
return power
def get_soc(self, storagelevel, max_storage_capacity):
"""Get the SoC in % (decimal value)"""
return self.storagelevel / self.max_storage_capacity
def set_financials(self,
capex_per_MW,
capex_per_MWh,
opex,
devex,
lifetime=None,
depreciate=True, salvage_value=0):
total_capex = (
capex_per_MW * self.max_power + capex_per_MWh * self.max_storage_capacity
)
super().set_financials(
total_capex,
opex,
devex,
lifetime=None,
depreciate=True,
salvage_value=0
)
def __repr__(self):
return (
f"{self.__class__.__name__}(name={self.name}, max_power={self.max_power}, "
f"min_power={self.min_power}, roundtrip_eff={self.roundtrip_eff}, capacity_per_volume={self.capacity_per_volume}, volume = {self.volume}),"
f"lifetime={self.lifetime}, temperature = {self.temperature}, min_storagelevel = {self.min_storagelevel})"
)
class Heatpump(Asset):
"""Subclass for a Heatpump.
Use cop parameter to set fixed COP (float/int) or COP curve (func).
COP curve should take load in MWhe and return COP.
Parameters:
-----------
max_th_power : numeric
Maximum thermal output in MW (positive value)
cop_curve : numeric or list or function
3 ways to set the COP of the Heatpump:
(1) Fixed COP based on [numeric] value.
(2) Polynomial with coefficients based on [list] input.
Input coeficients in format [c0, c1, c2, ..., c(n)],
will generate Polynomial p(x) = c0 + c1*x + c2*x^2 ... cn*x^n,
where x = % thermal load (in % of thermal capacity) as decimal value.
Example:
cop=[1, 2, 3, 4] will result in following COP curve:
p(x) = 1 + 2x + 3x**2 + 4x**3,
(3) [function] in format func(*args, **kwargs)
Function should return a Polynomial that takes 'load_perc' as parameter.
min_th_power : numeric
Minimum thermal output in MW (positive value)
Notes:
------
Sign convention:
Thermal power outputs have positive values
Electric power inputs have negative values
"""
def __init__(
self,
name,
max_th_power,
cop_curve,
min_th_power=0,
):
if max_th_power < 0 or min_th_power < 0:
raise ValueError("Thermal power can not have negative values.")
if min_th_power > max_th_power:
raise ValueError("'min_th_power' can not be larger than 'max_th_power'.")
self.name = name
self.max_th_power = max_th_power
self.min_th_power = min_th_power
self.cop_curve = self._set_cop_curve(cop_curve=cop_curve)
def __repr__(self):
return (
f"{self.__class__.__name__}(name='{self.name}', max_thermal_power={self.max_th_power}, "
f"cop_curve={self.cop_curve}, min_th_power={self.min_th_power})"
)
# Is turning everything into a Polynomial the best solution here?
@staticmethod
@lru_cache(maxsize=None)
def _set_cop_curve(self, cop_curve):
"""Generate COP curve function based on different inputtypes.
Returns a function that takes *args **kwargs and returns a Polynomial.
"""
if isinstance(cop_curve, list):
def func(*args, **kwargs):
return Polynomial(cop_curve)
return func
return cop_curve
@lru_cache(maxsize=None)
def get_cop(self, heat_output, Tsink=None, Tsource=None):
"""Get COP corresponding to certain load.
Parameters:
-----------
heat_output : numeric
Thermal load in MW
Tsink : numeric
Sink temperature in degrees celcius
Tsource : numeric
Source temperature in degrees celcius
Notes:
------
Sign convention:
Positive values for thermal load
Negative values for electric load
"""
load_perc = heat_output / self.max_th_power
cop_curve = self.cop_curve
if not callable(cop_curve):
return cop_curve
else:
return cop_curve(Tsink=Tsink, Tsource=Tsource)(load_perc)
def th_to_el_power(self, heat_output, Tsink=None, Tsource=None):
if not self.min_th_power <= heat_output <= self.max_th_power:
warnings.warn(
f"Chosen heat output is out of range [{self.min_th_power} - {self.max_th_power}]. "
"Heat output is being limited to the closest boundary."
)
heat_output = min(max(heat_output, self.min_th_power), self.max_th_power)
cop = self.get_cop(heat_output=heat_output, Tsink=Tsink, Tsource=Tsource)
return -heat_output / cop
def set_load(self, *args, **kwargs):
raise NotImplementedError(
"Directly setting the electric load of the heatpump is not possible (yet). "
"Functionality will be implemented if there is a specific usecase for it."
)
@lru_cache(maxsize=None)
def set_heat_output(self, heat_output, Tsink=None, Tsource=None):
"""Set heat output in MWth, returns load of heatpump as tuple (MWe, MWth)"""
if not self.min_th_power <= heat_output <= self.max_th_power:
warnings.warn(
f"Chosen heat output is out of range [{self.min_th_power} - {self.max_th_power}]. "
"Heat output is being limited to the closest boundary."
)
heat_output = min(max(heat_output, self.min_th_power), self.max_th_power)
if Tsink is not None and Tsource is not None and Tsink <= Tsource:
raise ValueError(f"Tsource '{Tsource}' can not be higher than '{Tsink}'.")
cop = self.get_cop(heat_output=heat_output, Tsink=Tsink, Tsource=Tsource)
e_load = -heat_output / cop
return e_load, heat_output
def _cost_function(self, x, c1, c2, c3, Tsink=None, Tsource=None):
"""Objective function for set_opt_load function.
x = heatpump thermal load in MW
c1 = electricity_cost
c2 = alt_heat_price
c3 = demand
"""
return (
x / self.get_cop(heat_output=x, Tsink=Tsink, Tsource=Tsource) * c1
+ (c3 - x) * c2
)
@lru_cache(maxsize=None)
def set_opt_load(
self,
electricity_cost,
alt_heat_price,
demand,
Tsink=None,
Tsource=None,
tolerance=0.01,
):
"""Set optimal load of Heatpump with minimal total heat costs.
Function uses np.minimize_scalar to minimize cost function.
Parameters:
-----------
electricity_cost:
Cost of input electricity in /MWh(e)
alt_heat_price:
Price of heat from alternative source in /MWh(th)
demand:
Heat demand in MW(th)
Returns:
--------
Optimal load of heatpump as tuple (MWe, MWth)
"""
c1 = electricity_cost
c2 = alt_heat_price
c3 = demand
cop_curve = self.cop_curve
if isinstance(cop_curve, Number):
if c1 / cop_curve <= c2:
return self.max_th_power
else:
return self.min_th_power
obj_func = partial(
self._cost_function, c1=c1, c2=c2, c3=c3, Tsink=Tsink, Tsource=Tsource
)
low_bound = 0
up_bound = min(c3, self.max_th_power)
opt_th_load = minimize_scalar(
obj_func,
bounds=(low_bound, up_bound),
method="bounded",
options={"xatol": tolerance},
).x
opt_e_load, opt_th_load = self.set_heat_output(
opt_th_load, Tsink=Tsink, Tsource=Tsource
)
return opt_e_load, opt_th_load

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save