thermobuilpy 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- thermobuilpy-0.0.2/LICENSE.txt +19 -0
- thermobuilpy-0.0.2/PKG-INFO +20 -0
- thermobuilpy-0.0.2/README.md +3 -0
- thermobuilpy-0.0.2/setup.cfg +4 -0
- thermobuilpy-0.0.2/setup.py +24 -0
- thermobuilpy-0.0.2/src/ThermoBuilPy/__init__.py +5 -0
- thermobuilpy-0.0.2/src/ThermoBuilPy/component.py +70 -0
- thermobuilpy-0.0.2/src/ThermoBuilPy/connection.py +183 -0
- thermobuilpy-0.0.2/src/ThermoBuilPy/storage.py +133 -0
- thermobuilpy-0.0.2/src/ThermoBuilPy/stratifiedStorage.py +110 -0
- thermobuilpy-0.0.2/src/ThermoBuilPy/thermalSystem.py +356 -0
- thermobuilpy-0.0.2/src/ThermoBuilPy.egg-info/PKG-INFO +20 -0
- thermobuilpy-0.0.2/src/ThermoBuilPy.egg-info/SOURCES.txt +19 -0
- thermobuilpy-0.0.2/src/ThermoBuilPy.egg-info/dependency_links.txt +1 -0
- thermobuilpy-0.0.2/src/ThermoBuilPy.egg-info/requires.txt +2 -0
- thermobuilpy-0.0.2/src/ThermoBuilPy.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2025 Hannes Hanse
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: thermobuilpy
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: ToDo
|
|
5
|
+
Home-page: https://github.com/hanneshanse/MilPython
|
|
6
|
+
Author: Hannes Hanse
|
|
7
|
+
Author-email: hannes.hanse@tu-clausthal.de
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: milp,time series,optimization,gurobi,gurobipy
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.txt
|
|
15
|
+
Requires-Dist: numpy
|
|
16
|
+
Requires-Dist: matplotlib
|
|
17
|
+
|
|
18
|
+
# ThermoBuilPy
|
|
19
|
+
MilPython is a python Framework ... TODO
|
|
20
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
this_directory = Path(__file__).parent
|
|
4
|
+
long_description = (this_directory / "README.md").read_text()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name='thermobuilpy',
|
|
8
|
+
version='0.0.2',
|
|
9
|
+
description='ToDo',
|
|
10
|
+
license='MIT',
|
|
11
|
+
long_description=long_description,
|
|
12
|
+
long_description_content_type='text/markdown',
|
|
13
|
+
author='Hannes Hanse',
|
|
14
|
+
author_email='hannes.hanse@tu-clausthal.de',
|
|
15
|
+
keywords=['milp','time series','optimization','gurobi','gurobipy'],
|
|
16
|
+
readme = "README.md",
|
|
17
|
+
url='https://github.com/hanneshanse/MilPython',
|
|
18
|
+
install_requires=["numpy","matplotlib"],
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
]
|
|
24
|
+
)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from .connection import Connection,Conduction,ForcedConvection,FreeConvection,GeneralHeatTransfer
|
|
2
|
+
from .storage import ThermalStorage,ExtStorage,Storage
|
|
3
|
+
from .stratifiedStorage import StratifiedStorage
|
|
4
|
+
from .thermalSystem import ThermalSystem,SimulationMethod
|
|
5
|
+
from .component import Component,Components
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
def get_nested_attr(obj, attr_path):
|
|
5
|
+
"""Greift rekursiv auf verschachtelte Attribute zu, z.B. 'storage1.name'"""
|
|
6
|
+
attrs = attr_path.split(".")
|
|
7
|
+
for attr in attrs:
|
|
8
|
+
obj = getattr(obj, attr)
|
|
9
|
+
return obj
|
|
10
|
+
|
|
11
|
+
class Component(ABC):
|
|
12
|
+
_series_map = {}
|
|
13
|
+
_counter = 0 # Klassenattribut für Zähler
|
|
14
|
+
|
|
15
|
+
def __init__(self, name=None):
|
|
16
|
+
cls = type(self) # Unterklasse
|
|
17
|
+
if not hasattr(cls, "_counter"):
|
|
18
|
+
cls._counter = 0 # Initialisierung für die Unterklasse
|
|
19
|
+
cls._counter += 1
|
|
20
|
+
|
|
21
|
+
if name is None:
|
|
22
|
+
# automatischer Name: Klassenname + Zähler
|
|
23
|
+
self.name = f"{cls.__name__}{cls._counter}"
|
|
24
|
+
else:
|
|
25
|
+
self.name = name
|
|
26
|
+
|
|
27
|
+
def toPDSeries(self) -> pd.Series:
|
|
28
|
+
# data = {col: getattr(self, attr) for col, attr in self._series_map.items()}
|
|
29
|
+
# return pd.Series(data)
|
|
30
|
+
data = {}
|
|
31
|
+
for col_name, attr_name in self._series_map.items():
|
|
32
|
+
if callable(attr_name):
|
|
33
|
+
# Methode / Lambda aufrufen
|
|
34
|
+
value = attr_name(self)
|
|
35
|
+
else:
|
|
36
|
+
value = get_nested_attr(self, attr_name)
|
|
37
|
+
|
|
38
|
+
# Wenn der Wert eine Liste von Objekten ist, die auch ein Attribut 'name' haben
|
|
39
|
+
if isinstance(value, list) and value and all(hasattr(v, "name") for v in value):
|
|
40
|
+
# Wir speichern die Namen der Objekte als Liste
|
|
41
|
+
data[col_name] = [v.name for v in value]
|
|
42
|
+
else:
|
|
43
|
+
data[col_name] = value
|
|
44
|
+
return pd.Series(data)
|
|
45
|
+
# @abstractmethod
|
|
46
|
+
# def updateFromPDSeries(self, pdSeries):
|
|
47
|
+
# pass
|
|
48
|
+
|
|
49
|
+
class Components:
|
|
50
|
+
def __init__(self,name,items=None):
|
|
51
|
+
self.name=name
|
|
52
|
+
self.items:list[Component]=items or []
|
|
53
|
+
pass
|
|
54
|
+
def __iter__(self):
|
|
55
|
+
return iter(self.items)
|
|
56
|
+
def __len__(self):
|
|
57
|
+
return len(self.items)
|
|
58
|
+
def add_component(self, item:Component):
|
|
59
|
+
if item is not None:
|
|
60
|
+
self.items.append(item)
|
|
61
|
+
def add_components(self, items:list[Component]):
|
|
62
|
+
if items is not None:
|
|
63
|
+
self.items.extend(items)
|
|
64
|
+
def set_simulation_parameter(self,method,stepsize):
|
|
65
|
+
for item in self.items:
|
|
66
|
+
item.set_simulation_parameter(method,stepsize)
|
|
67
|
+
|
|
68
|
+
def toDataFrame(self) -> pd.DataFrame:
|
|
69
|
+
series_list = [c.toPDSeries() for c in self.items]
|
|
70
|
+
return pd.DataFrame(series_list)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from .component import Component
|
|
2
|
+
from .storage import Storage,ThermalStorage,ExtStorage
|
|
3
|
+
import numpy as np
|
|
4
|
+
CP_Water = 4.186/3.6 # Specific heat capacity of water in Wh/kg (?)#TODO
|
|
5
|
+
class Connection(Component):
|
|
6
|
+
def get_heatflow_res(self):
|
|
7
|
+
raise NotImplementedError("This method should be implemented in subclasses.")
|
|
8
|
+
def set_simulation_parameter(self,method,stepsize):
|
|
9
|
+
self.simulation_method = method
|
|
10
|
+
self.stepsize = stepsize
|
|
11
|
+
|
|
12
|
+
class Conduction(Connection):
|
|
13
|
+
_series_map = {
|
|
14
|
+
'Name': 'name',
|
|
15
|
+
'Storage1': 'storage1.name',
|
|
16
|
+
'Storage2': 'storage2.name',
|
|
17
|
+
'Coeff': 'coeff'}
|
|
18
|
+
def __init__(self, storage1, storage2,coeff,name=None):
|
|
19
|
+
Component.__init__(self,name)
|
|
20
|
+
self.coeff=coeff
|
|
21
|
+
self.storage1:Storage = storage1
|
|
22
|
+
self.storage2:Storage = storage2
|
|
23
|
+
self.storages = [storage1, storage2]
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def newConduction(cls,storage1:Storage,storage2,coeff:float,name:str=None):
|
|
27
|
+
cls=cls(storage1,storage2,coeff,name)
|
|
28
|
+
return cls
|
|
29
|
+
|
|
30
|
+
def get_heatflow_res(self):
|
|
31
|
+
from .thermalSystem import SimulationMethod
|
|
32
|
+
t1_res = self.storage1.get_temp_res()
|
|
33
|
+
t2_res = self.storage2.get_temp_res()
|
|
34
|
+
match self.simulation_method:
|
|
35
|
+
case SimulationMethod.EXPLICIT_EULER:
|
|
36
|
+
return self.coeff * (t1_res[:-1] - t2_res[:-1])
|
|
37
|
+
case SimulationMethod.IMPLICIT_EULER:
|
|
38
|
+
return self.coeff * (t1_res[1:] - t2_res[1:])
|
|
39
|
+
case SimulationMethod.CRANK_NICOLSON:
|
|
40
|
+
expl=self.coeff * (t1_res[:-1] - t2_res[:-1])
|
|
41
|
+
impl=self.coeff * (t1_res[1:] - t2_res[1:])
|
|
42
|
+
return 0.5 * (expl + impl)
|
|
43
|
+
|
|
44
|
+
class GeneralHeatTransfer(Connection):
|
|
45
|
+
'''
|
|
46
|
+
! This is only for cases where the other methods don't work.
|
|
47
|
+
It only sets the equations for the targetStorage.
|
|
48
|
+
If you want to set up a connection between multiple storages and can't use the other methods, you have to set up the connection for all storages individually.
|
|
49
|
+
'''
|
|
50
|
+
_series_map = {
|
|
51
|
+
'Name': 'name',
|
|
52
|
+
'TargetStorage': 'targestStorage.name',
|
|
53
|
+
'StorageList': 'storageList',
|
|
54
|
+
'CoeffList': 'coeffList',
|
|
55
|
+
'b': 'b'
|
|
56
|
+
}
|
|
57
|
+
def __init__(self,name):
|
|
58
|
+
Component.__init__(self,name)
|
|
59
|
+
self.targestStorage:ThermalStorage = None
|
|
60
|
+
self.storageList:list[Storage] = None
|
|
61
|
+
self.coeffList:list[float] = None
|
|
62
|
+
self.b:float=None
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def newGeneralHeatTransfer(cls, targetStorage:ThermalStorage,storageList:list[Storage],coeffList:list[float],b:float=0, name=None):#TODO was ist mit extStorages
|
|
66
|
+
con = cls(name)
|
|
67
|
+
con.targestStorage = targetStorage
|
|
68
|
+
con.storageList= storageList
|
|
69
|
+
con.coeffList = coeffList
|
|
70
|
+
con.b = b
|
|
71
|
+
return con
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class FreeConvection(Connection):
|
|
75
|
+
'''
|
|
76
|
+
Free convection in stratified storages between layer a and b where a is below b if temp(a) > temp(b) with given mass flow
|
|
77
|
+
'''
|
|
78
|
+
_series_map = {
|
|
79
|
+
'Name': 'name',
|
|
80
|
+
'Storage1': 'storage1.name',
|
|
81
|
+
'Storage2': 'storage2.name',
|
|
82
|
+
'MassFlow': lambda self: self.get_mFlow(),
|
|
83
|
+
'CpFluid': 'cpFluid',
|
|
84
|
+
'Tolerance': 'tolerance'
|
|
85
|
+
}
|
|
86
|
+
def __init__(self,name):
|
|
87
|
+
Component.__init__(self,name)
|
|
88
|
+
self.storage1:ThermalStorage = None
|
|
89
|
+
self.storage2:ThermalStorage = None
|
|
90
|
+
self.__massFlow:float = None
|
|
91
|
+
self.__massFlow_res = []
|
|
92
|
+
self.tolerance:float = None
|
|
93
|
+
self.cpFluid = None
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def newFreeConvection(cls, storage1:ThermalStorage, storage2:ThermalStorage, massFlow:float, cpFluid=CP_Water, tolerance=0.1,name=None):
|
|
97
|
+
con = cls(name)
|
|
98
|
+
con.storage1 = storage1
|
|
99
|
+
con.storage2 = storage2
|
|
100
|
+
con.__massFlow = massFlow
|
|
101
|
+
con.cpFluid = cpFluid
|
|
102
|
+
con.tolerance = tolerance
|
|
103
|
+
return con
|
|
104
|
+
|
|
105
|
+
def set_mFlow(self, mFlow):
|
|
106
|
+
if mFlow<0:
|
|
107
|
+
raise ValueError("mFlow must be non-negative. For flows in opposite direction set up a seperate ForcedFlow.")
|
|
108
|
+
self.__massFlow = mFlow
|
|
109
|
+
def get_mFlow(self):
|
|
110
|
+
return self.__massFlow
|
|
111
|
+
def save_massFlow(self):
|
|
112
|
+
self.__massFlow_res.append(self.__massFlow)
|
|
113
|
+
def get_massFlow_res(self):
|
|
114
|
+
return self.__massFlow_res
|
|
115
|
+
|
|
116
|
+
class ForcedConvection(Connection):
|
|
117
|
+
_series_map = {
|
|
118
|
+
'Name': 'name',
|
|
119
|
+
'StorageList': 'storageList',
|
|
120
|
+
'MassFlow': lambda self: self.get_mFlow(),
|
|
121
|
+
'CpFluid': 'cpFluid'
|
|
122
|
+
}
|
|
123
|
+
def __init__(self,name):
|
|
124
|
+
Component.__init__(self,name)
|
|
125
|
+
self.storageList = None
|
|
126
|
+
self.__massFlow = None
|
|
127
|
+
self.cpFluid = None
|
|
128
|
+
self.__massFlow_res = []
|
|
129
|
+
|
|
130
|
+
def set_mFlow(self, mFlow):
|
|
131
|
+
if mFlow<0:
|
|
132
|
+
raise ValueError("mFlow must be non-negative. For flows in opposite direction set up a seperate ForcedFlow.")
|
|
133
|
+
self.__massFlow = mFlow
|
|
134
|
+
|
|
135
|
+
def get_mFlow(self):
|
|
136
|
+
return self.__massFlow
|
|
137
|
+
|
|
138
|
+
def save_massFlow(self):
|
|
139
|
+
self.__massFlow_res.append(self.__massFlow)
|
|
140
|
+
def get_massFlow_res(self):
|
|
141
|
+
return np.array(self.__massFlow_res)
|
|
142
|
+
|
|
143
|
+
def get_heatflow_res(self):
|
|
144
|
+
from .thermalSystem import SimulationMethod
|
|
145
|
+
temp_res_list = [storage.get_temp_res() for storage in self.storageList]
|
|
146
|
+
match self.simulation_method:
|
|
147
|
+
case SimulationMethod.EXPLICIT_EULER:
|
|
148
|
+
q_lst=[]
|
|
149
|
+
for i in range(len(self.storageList)-1):
|
|
150
|
+
t1_res = temp_res_list[i][:-1]
|
|
151
|
+
q = self.get_massFlow_res()[:-1] * self.cpFluid * t1_res
|
|
152
|
+
q_lst.append(q)
|
|
153
|
+
return q_lst
|
|
154
|
+
case SimulationMethod.IMPLICIT_EULER:
|
|
155
|
+
q_lst=[]
|
|
156
|
+
for i in range(len(self.storageList)-1):
|
|
157
|
+
t1_res = temp_res_list[i][1:]
|
|
158
|
+
q = self.get_massFlow_res()[1:] * self.cpFluid * t1_res
|
|
159
|
+
q_lst.append(q)
|
|
160
|
+
return q_lst
|
|
161
|
+
case SimulationMethod.CRANK_NICOLSON:
|
|
162
|
+
q_lst=[]
|
|
163
|
+
for i in range(len(self.storageList)-1):
|
|
164
|
+
t1_res_expl = temp_res_list[i][:-1]
|
|
165
|
+
q_expl = self.get_massFlow_res()[:-1] * self.cpFluid * t1_res_expl
|
|
166
|
+
t1_res_impl = temp_res_list[i][1:]
|
|
167
|
+
q_impl = self.get_massFlow_res()[1:] * self.cpFluid * t1_res_impl
|
|
168
|
+
q_lst.append((q_expl+q_impl)/2)
|
|
169
|
+
return q_lst
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def newForcedConvection(cls,storageList:list[Storage], massFlow, name=None, cpFluid=CP_Water):
|
|
173
|
+
flow = cls(name)
|
|
174
|
+
flow.cpFluid = cpFluid
|
|
175
|
+
storageList = [x for sub in storageList for x in (sub if isinstance(sub, list) else [sub])]
|
|
176
|
+
if not isinstance(storageList, list):
|
|
177
|
+
raise TypeError("storageList must be a list of ThermalStorage objects")
|
|
178
|
+
if not all(isinstance(storage, Storage) for storage in storageList):
|
|
179
|
+
raise TypeError("All elements in storageList must be instances of Storage")
|
|
180
|
+
flow.storageList = storageList
|
|
181
|
+
flow.__massFlow = massFlow
|
|
182
|
+
return flow
|
|
183
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from .component import Component
|
|
5
|
+
CP_Water = 4.186/3.6 # Specific heat capacity of water in Wh/kg (?)#TODO
|
|
6
|
+
|
|
7
|
+
def temp_to_Q(temp,cap):
|
|
8
|
+
return temp * cap
|
|
9
|
+
|
|
10
|
+
class Storage(Component):
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def get_temp(self):
|
|
13
|
+
pass
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def get_temp_res(self):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
class ThermalStorage(Storage):
|
|
19
|
+
_series_map = {
|
|
20
|
+
'Name': 'name',
|
|
21
|
+
'Capacity': 'cap',
|
|
22
|
+
'Temperature': lambda self: self.get_temp(),
|
|
23
|
+
}
|
|
24
|
+
def __init__(self,name):
|
|
25
|
+
Component.__init__(self,name)
|
|
26
|
+
self.col=None
|
|
27
|
+
self.cap=None
|
|
28
|
+
self.temp=None
|
|
29
|
+
self.__Q=None
|
|
30
|
+
self.mass=None # Volume, if applicable
|
|
31
|
+
@classmethod
|
|
32
|
+
def fromPDSeries(cls, pdSeries):
|
|
33
|
+
return cls.newStorage(
|
|
34
|
+
cap=pdSeries['Capacity'],
|
|
35
|
+
temp=pdSeries['Temperature'],
|
|
36
|
+
name=pdSeries['Name'])
|
|
37
|
+
@classmethod
|
|
38
|
+
def newStorage(cls,cap,temp,name=None):
|
|
39
|
+
storage = cls(name)
|
|
40
|
+
storage.cap = cap
|
|
41
|
+
storage.__Q = temp_to_Q(temp, cap)
|
|
42
|
+
return storage
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def newStorageByMass(cls,mass,cp,temp,name=None):
|
|
46
|
+
storage = cls(name)
|
|
47
|
+
storage.cap = mass * cp
|
|
48
|
+
storage.mass=mass
|
|
49
|
+
storage.__Q = temp_to_Q(temp, storage.cap)
|
|
50
|
+
return storage
|
|
51
|
+
|
|
52
|
+
def sim_prep(self, num_steps=None):
|
|
53
|
+
if num_steps:
|
|
54
|
+
self.__Q_res = np.zeros(num_steps+1)
|
|
55
|
+
else:
|
|
56
|
+
self.__Q_res = []
|
|
57
|
+
|
|
58
|
+
def set_col(self,col:int):
|
|
59
|
+
self.col = col
|
|
60
|
+
|
|
61
|
+
def set_Q(self, Q, t):
|
|
62
|
+
self.__Q = Q
|
|
63
|
+
if isinstance(self.__Q_res,np.ndarray):
|
|
64
|
+
self.__Q_res[t] = Q
|
|
65
|
+
else:
|
|
66
|
+
self.__Q_res.append(Q)
|
|
67
|
+
|
|
68
|
+
def set_temp(self,temp):
|
|
69
|
+
self.__Q = temp_to_Q(temp, self.cap)
|
|
70
|
+
|
|
71
|
+
def get_Q(self):
|
|
72
|
+
return self.__Q
|
|
73
|
+
|
|
74
|
+
def get_temp(self):
|
|
75
|
+
return self.__Q / self.cap
|
|
76
|
+
|
|
77
|
+
def get_temp_res(self):
|
|
78
|
+
return np.array(self.__Q_res) / self.cap
|
|
79
|
+
|
|
80
|
+
def get_Q_res(self):
|
|
81
|
+
try:
|
|
82
|
+
return self.__Q_res
|
|
83
|
+
except:
|
|
84
|
+
raise Exception('First run the simulation, then get get results.')
|
|
85
|
+
|
|
86
|
+
def plot_temp_res(self,ax,color=None):
|
|
87
|
+
temp_res = self.get_temp_res()
|
|
88
|
+
if color:
|
|
89
|
+
ax.plot(temp_res,label=self.name,color=color)
|
|
90
|
+
else:
|
|
91
|
+
ax.plot(temp_res,label=self.name)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ExtStorage(Storage):
|
|
95
|
+
_series_map = {
|
|
96
|
+
'Name': 'name',
|
|
97
|
+
'Temperature': lambda self: self.get_temp(),
|
|
98
|
+
}
|
|
99
|
+
def __init__(self,name,temp):
|
|
100
|
+
Component.__init__(self,name)
|
|
101
|
+
self.__temp = temp
|
|
102
|
+
self.__temp_res = []
|
|
103
|
+
@classmethod
|
|
104
|
+
def newExtStorage(cls,name=None,temp=0):
|
|
105
|
+
storage = cls(name,temp)
|
|
106
|
+
return storage
|
|
107
|
+
|
|
108
|
+
def sim_prep(self, num_steps=None):
|
|
109
|
+
self.Q_sum = 0
|
|
110
|
+
if num_steps:
|
|
111
|
+
self.__q_res = np.zeros(num_steps+1)
|
|
112
|
+
else:
|
|
113
|
+
self.__q_res = []
|
|
114
|
+
|
|
115
|
+
def add_q(self, q, t=None):
|
|
116
|
+
self.Q_sum += q
|
|
117
|
+
if t:
|
|
118
|
+
self.__q_res[t] = q
|
|
119
|
+
def set_temp(self,temp):
|
|
120
|
+
self.__temp = temp
|
|
121
|
+
def get_temp(self):
|
|
122
|
+
return self.__temp
|
|
123
|
+
def get_q_res(self):
|
|
124
|
+
return self.__q_res
|
|
125
|
+
def save_temp(self):
|
|
126
|
+
self.__temp_res.append(self.__temp)
|
|
127
|
+
def get_temp_res(self):
|
|
128
|
+
return np.array(self.__temp_res)
|
|
129
|
+
@classmethod
|
|
130
|
+
def fromPDSeries(cls, pdSeries):
|
|
131
|
+
return cls.newExtStorage(
|
|
132
|
+
temp=pdSeries['Temperature'],
|
|
133
|
+
name=pdSeries['Name'])
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from .storage import ThermalStorage
|
|
2
|
+
from .connection import Conduction,FreeConvection
|
|
3
|
+
from .component import Component
|
|
4
|
+
CP_Water = 4.186/3.6 # Specific heat capacity of water in Wh/kg (?)#TODO
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
class StratifiedStorage(Component):
|
|
7
|
+
_series_map = {
|
|
8
|
+
'Name': 'name',
|
|
9
|
+
'Layers': 'layers',
|
|
10
|
+
'Conductions': 'conductions',
|
|
11
|
+
'FreeConvections': 'freeConvections'
|
|
12
|
+
}
|
|
13
|
+
def __init__(self,name):
|
|
14
|
+
Component.__init__(self,name)
|
|
15
|
+
self.layers:list[ThermalStorage] = []
|
|
16
|
+
self.conductions:list[Conduction] = []
|
|
17
|
+
self.freeConvections:list[FreeConvection] = []
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def newStratifiedStorageByMasses(cls, layerMasses:list,layerTemperatures:list,conductionCoeff:list[float]|float,freeConvectionMassFlow:list[float]|float=0,cpFluid=CP_Water, name=None): #TODO name: Stratified or MultiLayer?
|
|
21
|
+
storage = cls(name)
|
|
22
|
+
if not isinstance(conductionCoeff,list):
|
|
23
|
+
conductionCoeff = [conductionCoeff] * (len(layerMasses) - 1)
|
|
24
|
+
if not isinstance(freeConvectionMassFlow,list):
|
|
25
|
+
freeConvectionMassFlow = [freeConvectionMassFlow] * (len(layerMasses) - 1)
|
|
26
|
+
if not len(layerMasses) == len(layerTemperatures) == len(conductionCoeff)+1:
|
|
27
|
+
raise ValueError("layerVolumes and layerTemperatures must have the same length.")
|
|
28
|
+
|
|
29
|
+
for i in range(len(layerMasses)):
|
|
30
|
+
storage.layers.append(ThermalStorage.newStorageByMass(layerMasses[i], cpFluid, layerTemperatures[i]))
|
|
31
|
+
for i, cond_coeff in enumerate(conductionCoeff):
|
|
32
|
+
storage.conductions.append(
|
|
33
|
+
Conduction(
|
|
34
|
+
storage1=storage.layers[i],
|
|
35
|
+
storage2=storage.layers[i+1],
|
|
36
|
+
coeff=cond_coeff
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
for i, mFlow in enumerate(freeConvectionMassFlow):
|
|
40
|
+
storage.freeConvections.append(
|
|
41
|
+
FreeConvection.newFreeConvection(
|
|
42
|
+
storage1=storage.layers[i],
|
|
43
|
+
storage2=storage.layers[i+1],
|
|
44
|
+
massFlow=mFlow,
|
|
45
|
+
cpFluid=cpFluid
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
return storage
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def newStratifiedStorageByCapacities(cls, layerCapacities:list,layerTemperatures:list,conductionCoeff:list[float]|float,freeConvectionMassFlow:list[float]|float=0,cpFluid=CP_Water, name=None): #TODO name: Stratified or MultiLayer?
|
|
52
|
+
storage = cls(name)
|
|
53
|
+
if not isinstance(conductionCoeff,list):
|
|
54
|
+
conductionCoeff = [conductionCoeff] * (len(layerCapacities) - 1)
|
|
55
|
+
if not isinstance(freeConvectionMassFlow,list):
|
|
56
|
+
freeConvectionMassFlow = [freeConvectionMassFlow] * (len(layerCapacities) - 1)
|
|
57
|
+
if not len(layerCapacities) == len(layerTemperatures) == len(conductionCoeff)+1:
|
|
58
|
+
raise ValueError("layerCapacities and layerTemperatures must have the same length.")
|
|
59
|
+
|
|
60
|
+
for i in range(len(layerCapacities)):
|
|
61
|
+
storage.layers.append(ThermalStorage.newStorage(layerCapacities[i],layerTemperatures[i]))
|
|
62
|
+
for i, cond_coeff in enumerate(conductionCoeff):
|
|
63
|
+
storage.conductions.append(
|
|
64
|
+
Conduction(
|
|
65
|
+
storage1=storage.layers[i],
|
|
66
|
+
storage2=storage.layers[i+1],
|
|
67
|
+
coeff=cond_coeff
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
for i, mFlow in enumerate(freeConvectionMassFlow):
|
|
71
|
+
storage.freeConvections.append(
|
|
72
|
+
FreeConvection.newFreeConvection(
|
|
73
|
+
storage1=storage.layers[i],
|
|
74
|
+
storage2=storage.layers[i+1],
|
|
75
|
+
massFlow=mFlow,
|
|
76
|
+
cpFluid=cpFluid
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
return storage
|
|
80
|
+
|
|
81
|
+
def updateConductionCoeffs(self, conductionCoeff:list[float]|float):
|
|
82
|
+
if not isinstance(conductionCoeff,list):
|
|
83
|
+
conductionCoeff = [conductionCoeff] * (len(self.layers) - 1)
|
|
84
|
+
if not len(conductionCoeff) == len(self.layers) - 1:
|
|
85
|
+
raise ValueError("conductionCoeff must have length equal to number of layers - 1.")
|
|
86
|
+
for i,cond in enumerate(self.conductions):
|
|
87
|
+
cond.coeff = conductionCoeff[i]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def getLayers(self, start:int, end:int):
|
|
91
|
+
if start < 0 or end >= len(self.layers) :
|
|
92
|
+
raise IndexError("Invalid layer indices.")
|
|
93
|
+
if start<end:
|
|
94
|
+
return self.layers[start:end+1]
|
|
95
|
+
else:
|
|
96
|
+
return list(reversed(self.layers[end:start+1]))
|
|
97
|
+
|
|
98
|
+
def getLayer(self, i:int):
|
|
99
|
+
if i<0 or i> len(self.layers)-1:
|
|
100
|
+
raise ValueError(f'Storage has {len(self.layers)} layers. Index must be 0<=i<{len(self.layers)}')
|
|
101
|
+
return self.layers[i]
|
|
102
|
+
|
|
103
|
+
def plot_temp_res(self):
|
|
104
|
+
for i,layer in enumerate(self.layers):
|
|
105
|
+
plt.plot(layer.get_temp_res(), label=layer.name)
|
|
106
|
+
plt.title(f'Temperature of {self.name} Layers Over Time')
|
|
107
|
+
plt.xlabel('Time Step')
|
|
108
|
+
plt.ylabel('Temperature (°C)')
|
|
109
|
+
plt.legend()
|
|
110
|
+
plt.show()
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
from .connection import Connection,Conduction,ForcedConvection,GeneralHeatTransfer,FreeConvection
|
|
2
|
+
from .storage import ExtStorage, ThermalStorage
|
|
3
|
+
from .stratifiedStorage import StratifiedStorage
|
|
4
|
+
from .component import Component,Components
|
|
5
|
+
import numpy as np
|
|
6
|
+
from enum import Enum
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
class SimulationMethod(Enum):
|
|
11
|
+
EXPLICIT_EULER = 1
|
|
12
|
+
IMPLICIT_EULER = 2
|
|
13
|
+
CRANK_NICOLSON = 3
|
|
14
|
+
|
|
15
|
+
def expl_euler(M, Q0, b_expl,dt):
|
|
16
|
+
M_expl = np.eye(M.shape[0]) + M * dt
|
|
17
|
+
rhs = M_expl.dot(Q0) + b_expl*dt
|
|
18
|
+
Q1_expl = rhs
|
|
19
|
+
return Q1_expl
|
|
20
|
+
|
|
21
|
+
def impl_euler(M, Q0, b_impl,dt):
|
|
22
|
+
M_impl = np.eye(M.shape[0]) - M * dt
|
|
23
|
+
M_impl_inv = np.linalg.inv(M_impl)
|
|
24
|
+
rhs = Q0 + b_impl*dt
|
|
25
|
+
Q1_impl = M_impl_inv.dot(rhs)
|
|
26
|
+
return Q1_impl
|
|
27
|
+
|
|
28
|
+
def crank_nicolson(M, Q0, b_CN,dt):
|
|
29
|
+
M1 = np.eye(M.shape[0]) - M * dt / 2
|
|
30
|
+
M2 = np.eye(M.shape[0]) + M * dt / 2
|
|
31
|
+
M_1_inv = np.linalg.inv(M1)
|
|
32
|
+
rhs = M2.dot(Q0) + b_CN*dt
|
|
33
|
+
Q1_CN = M_1_inv.dot(rhs)
|
|
34
|
+
return Q1_CN
|
|
35
|
+
|
|
36
|
+
class ThermalSystem:
|
|
37
|
+
def __init__(self,name):
|
|
38
|
+
self.storages:Components = Components('Thermal Storages')
|
|
39
|
+
self.conductions:Components = Components('Conductions')
|
|
40
|
+
self.forcedConvections:Components = Components('Forced Convections')
|
|
41
|
+
self.freeConvections:Components = Components('Free Convections')
|
|
42
|
+
self.stratfiedStorages:Components = Components('Stratified Storages')
|
|
43
|
+
self.generalHeatTransfers:Components = Components('General Heat Transfers')
|
|
44
|
+
self.ext_storages:Components = Components('External Storages')
|
|
45
|
+
self.__componentsLst=[self.storages,self.conductions,self.forcedConvections,self.freeConvections,self.ext_storages,self.stratfiedStorages,self.generalHeatTransfers]
|
|
46
|
+
self.name = name
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def newThermalSystem(cls, storages=None,conductions=None,forcedConvection=None,freeConvection=None,extStorages=None,stratifiedStorages=None,generalHeatTransfers=None,name=None):
|
|
50
|
+
system = cls(name)
|
|
51
|
+
system.storages.add_components(storages)
|
|
52
|
+
system.conductions.add_components(conductions)
|
|
53
|
+
system.forcedConvections.add_components(forcedConvection)
|
|
54
|
+
system.freeConvections.add_components(freeConvection)
|
|
55
|
+
system.ext_storages.add_components(extStorages)
|
|
56
|
+
system.generalHeatTransfers.add_components(generalHeatTransfers)
|
|
57
|
+
if stratifiedStorages is not None:
|
|
58
|
+
for stratifiedStorage in stratifiedStorages:
|
|
59
|
+
system.add_stratifiedStorage(stratifiedStorage)
|
|
60
|
+
return system
|
|
61
|
+
|
|
62
|
+
def add_storage(self, storage: ThermalStorage):
|
|
63
|
+
self.storages.add_component(storage)
|
|
64
|
+
|
|
65
|
+
def add_conduction(self, conduction: Conduction):
|
|
66
|
+
self.conductions.add_component(conduction)
|
|
67
|
+
|
|
68
|
+
def add_forcedConvection(self, florcedFlow:ForcedConvection):
|
|
69
|
+
self.forcedConvections.add_component(florcedFlow)
|
|
70
|
+
|
|
71
|
+
def add_freeConvection(self, freeConvection:FreeConvection):
|
|
72
|
+
self.freeConvections.add_component(freeConvection)
|
|
73
|
+
|
|
74
|
+
def add_extStorage(self, extStorage: ExtStorage):
|
|
75
|
+
self.ext_storages.add_component(extStorage)
|
|
76
|
+
|
|
77
|
+
def add_stratifiedStorage(self, stratifiedStorage: StratifiedStorage):
|
|
78
|
+
self.stratfiedStorages.add_component(stratifiedStorage)
|
|
79
|
+
self.storages.add_components(stratifiedStorage.layers)
|
|
80
|
+
self.conductions.add_components(stratifiedStorage.conductions)
|
|
81
|
+
self.freeConvections.add_components(stratifiedStorage.freeConvections)
|
|
82
|
+
|
|
83
|
+
def prepare_simulation(self,stepsize,simulation_method=SimulationMethod.CRANK_NICOLSON):
|
|
84
|
+
self.stepsize = stepsize
|
|
85
|
+
self.simulation_method = simulation_method
|
|
86
|
+
|
|
87
|
+
self.conductions.set_simulation_parameter(simulation_method,self.stepsize)
|
|
88
|
+
self.forcedConvections.set_simulation_parameter(simulation_method,self.stepsize)
|
|
89
|
+
self.freeConvections.set_simulation_parameter(simulation_method,self.stepsize)
|
|
90
|
+
self.generalHeatTransfers.set_simulation_parameter(simulation_method,self.stepsize)
|
|
91
|
+
|
|
92
|
+
for i,storage in enumerate(self.storages):
|
|
93
|
+
storage.set_col(i)
|
|
94
|
+
storage.sim_prep()
|
|
95
|
+
for ext_storage in self.ext_storages:
|
|
96
|
+
ext_storage.sim_prep()
|
|
97
|
+
self.dim=len(self.storages)
|
|
98
|
+
self.M=np.zeros((self.dim, self.dim))
|
|
99
|
+
self.b=np.zeros(self.dim)
|
|
100
|
+
self.Q=np.zeros(self.dim)
|
|
101
|
+
|
|
102
|
+
[self.Q.__setitem__(i, storage.get_Q()) for i, storage in enumerate(self.storages)]
|
|
103
|
+
self.set_therm_storage_Qs(self.Q,0)
|
|
104
|
+
self.did_simstep_info()
|
|
105
|
+
self.M_cond = np.zeros((self.dim, self.dim))
|
|
106
|
+
|
|
107
|
+
for cond in self.conductions:
|
|
108
|
+
|
|
109
|
+
if isinstance(cond.storage1,ThermalStorage):
|
|
110
|
+
c1 = cond.coeff / cond.storage1.cap
|
|
111
|
+
self.M_cond[cond.storage1.col, cond.storage1.col] += -c1
|
|
112
|
+
if isinstance(cond.storage2,ThermalStorage):
|
|
113
|
+
c2 = cond.coeff / cond.storage2.cap
|
|
114
|
+
self.M_cond[cond.storage1.col, cond.storage2.col] += c2
|
|
115
|
+
self.M_cond[cond.storage2.col, cond.storage2.col] += -c2
|
|
116
|
+
self.M_cond[cond.storage2.col, cond.storage1.col] += c1
|
|
117
|
+
else:
|
|
118
|
+
# External storage case
|
|
119
|
+
if isinstance(cond.storage2, ThermalStorage):
|
|
120
|
+
c2 = cond.coeff / cond.storage2.cap
|
|
121
|
+
self.M_cond[cond.storage2.col, cond.storage2.col] += -c2
|
|
122
|
+
else:
|
|
123
|
+
raise TypeError("Conduction must connect two ThermalStorage objects or one ExtStorage and one ThermalStorage")
|
|
124
|
+
|
|
125
|
+
def do_simstep(self):
|
|
126
|
+
self.__sim_step(t=None)
|
|
127
|
+
|
|
128
|
+
def simulate(self,num_steps=1000,stepsize=1/60,simulation_method=SimulationMethod.CRANK_NICOLSON):
|
|
129
|
+
self.num_steps=num_steps
|
|
130
|
+
self.stepsize=stepsize#! stepsize als timedelta?
|
|
131
|
+
self.simulation_method = simulation_method
|
|
132
|
+
self.__sim_prep()
|
|
133
|
+
for t in range(1,self.num_steps+1):
|
|
134
|
+
self.__sim_step(t)
|
|
135
|
+
|
|
136
|
+
def __sim_prep(self):
|
|
137
|
+
|
|
138
|
+
self.conductions.set_simulation_parameter(self.simulation_method,self.stepsize)
|
|
139
|
+
self.forcedConvections.set_simulation_parameter(self.simulation_method,self.stepsize)
|
|
140
|
+
self.freeConvections.set_simulation_parameter(self.simulation_method,self.stepsize)
|
|
141
|
+
self.generalHeatTransfers.set_simulation_parameter(self.simulation_method,self.stepsize)
|
|
142
|
+
|
|
143
|
+
for i,storage in enumerate(self.storages):
|
|
144
|
+
storage.set_col(i)
|
|
145
|
+
storage.sim_prep(num_steps=self.num_steps)
|
|
146
|
+
for ext_storage in self.ext_storages:
|
|
147
|
+
ext_storage.sim_prep(num_steps=self.num_steps)
|
|
148
|
+
self.dim=len(self.storages)
|
|
149
|
+
self.M=np.zeros((self.dim, self.dim))
|
|
150
|
+
self.b=np.zeros(self.dim)
|
|
151
|
+
self.Q=np.zeros(self.dim)
|
|
152
|
+
|
|
153
|
+
[self.Q.__setitem__(i, storage.get_Q()) for i, storage in enumerate(self.storages)]
|
|
154
|
+
self.set_therm_storage_Qs(self.Q,0)
|
|
155
|
+
self.did_simstep_info()
|
|
156
|
+
self.M_cond = np.zeros((self.dim, self.dim))
|
|
157
|
+
|
|
158
|
+
for cond in self.conductions:
|
|
159
|
+
|
|
160
|
+
if isinstance(cond.storage1,ThermalStorage):
|
|
161
|
+
c1 = cond.coeff / cond.storage1.cap
|
|
162
|
+
self.M_cond[cond.storage1.col, cond.storage1.col] += -c1
|
|
163
|
+
if isinstance(cond.storage2,ThermalStorage):
|
|
164
|
+
c2 = cond.coeff / cond.storage2.cap
|
|
165
|
+
self.M_cond[cond.storage1.col, cond.storage2.col] += c2
|
|
166
|
+
self.M_cond[cond.storage2.col, cond.storage2.col] += -c2
|
|
167
|
+
self.M_cond[cond.storage2.col, cond.storage1.col] += c1
|
|
168
|
+
else:
|
|
169
|
+
# External storage case
|
|
170
|
+
if isinstance(cond.storage2, ThermalStorage):
|
|
171
|
+
c2 = cond.coeff / cond.storage2.cap
|
|
172
|
+
self.M_cond[cond.storage2.col, cond.storage2.col] += -c2
|
|
173
|
+
else:
|
|
174
|
+
raise TypeError("Conduction must connect two ThermalStorage objects or one ExtStorage and one ThermalStorage")
|
|
175
|
+
|
|
176
|
+
# vorbereitung von listen für zeitreihensimulation
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# wo werden die Ergebnisse gespeichert? hier zentral oder in den Klassen?
|
|
180
|
+
def sim_step_control(self,t):
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
def __sim_step(self,t):
|
|
184
|
+
self.b_cond = np.zeros(self.dim)
|
|
185
|
+
|
|
186
|
+
for cond in self.conductions:
|
|
187
|
+
if isinstance(cond.storage1,ExtStorage):
|
|
188
|
+
c = cond.coeff * cond.storage1.get_temp()
|
|
189
|
+
self.b_cond[cond.storage2.col] += c
|
|
190
|
+
elif isinstance(cond.storage2,ExtStorage):
|
|
191
|
+
c = cond.coeff * cond.storage2.get_temp()
|
|
192
|
+
self.b_cond[cond.storage1.col] += c
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# Free Convection
|
|
196
|
+
self.M_freeConvection = np.zeros((self.dim, self.dim))
|
|
197
|
+
for conv in self.freeConvections:
|
|
198
|
+
if conv.storage1.get_temp() > conv.storage2.get_temp() + conv.tolerance:
|
|
199
|
+
c1 = conv.get_mFlow() / conv.storage1.cap * conv.cpFluid
|
|
200
|
+
c2 = conv.get_mFlow() / conv.storage2.cap * conv.cpFluid
|
|
201
|
+
self.M_freeConvection[conv.storage1.col,conv.storage1.col] += -c1
|
|
202
|
+
self.M_freeConvection[conv.storage1.col,conv.storage2.col] += c2
|
|
203
|
+
self.M_freeConvection[conv.storage2.col,conv.storage1.col] += c2
|
|
204
|
+
self.M_freeConvection[conv.storage2.col,conv.storage2.col] += -c2
|
|
205
|
+
#TODO richtig so?
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# forced Convection
|
|
209
|
+
self.M_forcedConvection = np.zeros((self.dim, self.dim))
|
|
210
|
+
self.b_forcedConvection = np.zeros(self.dim)
|
|
211
|
+
for conv in self.forcedConvections:
|
|
212
|
+
mFlow = conv.get_mFlow()
|
|
213
|
+
if mFlow is None:
|
|
214
|
+
raise ValueError("mFlow must be set for ForcedFlow")
|
|
215
|
+
|
|
216
|
+
for i in range(len(conv.storageList)-1):
|
|
217
|
+
from_stor = conv.storageList[i]
|
|
218
|
+
to_stor = conv.storageList[i+1]
|
|
219
|
+
if isinstance(from_stor,ExtStorage):
|
|
220
|
+
coeff = mFlow * from_stor.get_temp() * conv.cpFluid
|
|
221
|
+
self.b_forcedConvection[to_stor.col] += coeff
|
|
222
|
+
from_stor.add_q(-coeff, t)
|
|
223
|
+
else:
|
|
224
|
+
coeff = mFlow / from_stor.cap * conv.cpFluid
|
|
225
|
+
self.M_forcedConvection[from_stor.col, from_stor.col] += -coeff
|
|
226
|
+
if isinstance(to_stor,ExtStorage):
|
|
227
|
+
to_stor.add_q(coeff, t)#! das hier ist falsch: q_from fehlt
|
|
228
|
+
else:
|
|
229
|
+
self.M_forcedConvection[to_stor.col, from_stor.col] += coeff
|
|
230
|
+
m=self.M_forcedConvection
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
self.M_generalHeatTransfer = np.zeros((self.dim, self.dim))
|
|
234
|
+
self.b_generalHeatTransfer = np.zeros(self.dim)
|
|
235
|
+
for ht in self.generalHeatTransfers:
|
|
236
|
+
targetStorage = ht.targestStorage
|
|
237
|
+
coeff = ht.b
|
|
238
|
+
for i,storage in enumerate(ht.storageList):
|
|
239
|
+
self.M_generalHeatTransfer[targetStorage.col, storage.col] += ht.coeffList[i]
|
|
240
|
+
self.b_generalHeatTransfer[targetStorage.col] += coeff
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
self.M = self.M_forcedConvection+self.M_cond+self.M_generalHeatTransfer + self.M_freeConvection #TODO
|
|
244
|
+
self.b = self.b_forcedConvection+self.b_cond+self.b_generalHeatTransfer
|
|
245
|
+
match self.simulation_method:
|
|
246
|
+
case SimulationMethod.EXPLICIT_EULER:
|
|
247
|
+
self.Q = expl_euler(self.M,self.Q,self.b,self.stepsize)
|
|
248
|
+
case SimulationMethod.IMPLICIT_EULER:
|
|
249
|
+
self.Q = impl_euler(self.M,self.Q,self.b,self.stepsize)
|
|
250
|
+
case SimulationMethod.CRANK_NICOLSON:
|
|
251
|
+
self.Q = crank_nicolson(self.M, self.Q, self.b, self.stepsize)
|
|
252
|
+
|
|
253
|
+
self.did_simstep_info()
|
|
254
|
+
self.set_therm_storage_Qs(self.Q,t)
|
|
255
|
+
self.sim_step_control(t)
|
|
256
|
+
|
|
257
|
+
def did_simstep_info(self):
|
|
258
|
+
for ext_storage in self.ext_storages:
|
|
259
|
+
ext_storage.save_temp()
|
|
260
|
+
for forcedConv in self.forcedConvections:
|
|
261
|
+
forcedConv.save_massFlow()
|
|
262
|
+
for freeConv in self.freeConvections:
|
|
263
|
+
freeConv.save_massFlow()
|
|
264
|
+
|
|
265
|
+
def set_therm_storage_Qs(self, Q,t):
|
|
266
|
+
for i, storage in enumerate(self.storages):
|
|
267
|
+
storage.set_Q(Q[i],t)
|
|
268
|
+
|
|
269
|
+
def to_Excel(self, filename):
|
|
270
|
+
# Placeholder for exporting to Excel logic
|
|
271
|
+
with pd.ExcelWriter(filename) as writer:
|
|
272
|
+
for comp in self.__componentsLst:
|
|
273
|
+
sheet_name = comp.name if comp.name else 'Component'
|
|
274
|
+
comp.toDataFrame().to_excel(writer, sheet_name=sheet_name, index=False)
|
|
275
|
+
|
|
276
|
+
def plot_temps(self,storage_list:list[ThermalStorage],color_list:list[str]=None,xLabel='Steps',yLabel='Temp in °C'):
|
|
277
|
+
_,ax = plt.subplots()
|
|
278
|
+
for i,stor in enumerate(storage_list):
|
|
279
|
+
if color_list:
|
|
280
|
+
stor.plot_temp_res(ax,color_list[i])
|
|
281
|
+
else:
|
|
282
|
+
stor.plot_temp_res(ax)
|
|
283
|
+
ax.legend()
|
|
284
|
+
plt.xlabel(xLabel)
|
|
285
|
+
plt.ylabel(yLabel)
|
|
286
|
+
plt.show()
|
|
287
|
+
|
|
288
|
+
@classmethod
|
|
289
|
+
def from_Excel(cls, filename):
|
|
290
|
+
xls = pd.ExcelFile(filename)
|
|
291
|
+
system = cls(filename)
|
|
292
|
+
for sheet_name in xls.sheet_names:
|
|
293
|
+
df = pd.read_excel(xls, sheet_name=sheet_name)
|
|
294
|
+
match sheet_name:
|
|
295
|
+
case 'Thermal Storages':
|
|
296
|
+
storages = [ThermalStorage.fromPDSeries(row) for _, row in df.iterrows()]
|
|
297
|
+
system.storages.add_components(storages)
|
|
298
|
+
case 'External Storages':
|
|
299
|
+
extStor = [ExtStorage.fromPDSeries(row) for _, row in df.iterrows()]
|
|
300
|
+
system.ext_storages.add_components(extStor)
|
|
301
|
+
case _:
|
|
302
|
+
pass
|
|
303
|
+
|
|
304
|
+
for sheet_name in xls.sheet_names:
|
|
305
|
+
df = pd.read_excel(xls, sheet_name=sheet_name)
|
|
306
|
+
match sheet_name:
|
|
307
|
+
case 'Conductions':
|
|
308
|
+
conds=[]
|
|
309
|
+
for _,row in df.iterrows():
|
|
310
|
+
storage1 = next((st for st in system.storages.items if st.name==row['Storage1']),None)
|
|
311
|
+
if storage1 is None:
|
|
312
|
+
storage1 = next((st for st in system.ext_storages.items if st.name==row['Storage1']),None)
|
|
313
|
+
if storage1 is None:
|
|
314
|
+
raise ValueError(f"Can not find Storage1: {row['Storage1']} of {row['Name']} in {sheet_name}. Check if storage exists.")
|
|
315
|
+
|
|
316
|
+
storage2 = next((st for st in system.storages.items if st.name==row['Storage2']),None)
|
|
317
|
+
if storage2 is None:
|
|
318
|
+
storage2 = next((st for st in system.ext_storages.items if st.name==row['Storage2']),None)
|
|
319
|
+
if storage2 is None:
|
|
320
|
+
raise ValueError(f"Can not find Storage2: {row['Storage2']} of {row['Name']} in {sheet_name}. Check if storage exists.")
|
|
321
|
+
conds.append(Conduction(storage1,storage2,row['Coeff'],row['Name']))
|
|
322
|
+
system.conductions.add_components(conds)
|
|
323
|
+
case 'Forced Convections':
|
|
324
|
+
forcedConv=[]
|
|
325
|
+
for _,row in df.iterrows():
|
|
326
|
+
stors=[]
|
|
327
|
+
for storage in eval(row['StorageList']):
|
|
328
|
+
stor = next((st for st in system.storages.items if st.name==storage),None)
|
|
329
|
+
if stor is None:
|
|
330
|
+
stor = next((st for st in system.ext_storages.items if st.name==storage),None)
|
|
331
|
+
if stor is None:
|
|
332
|
+
raise ValueError(f"Can not find storage: {storage} of {row['Name']} in {sheet_name}. Check if storage exists.")
|
|
333
|
+
stors.append(stor)
|
|
334
|
+
forcedConv.append(ForcedConvection.newForcedConvection(stors,row['MassFlow'],row['Name'],row['CpFluid']))
|
|
335
|
+
system.forcedConvections.add_components(forcedConv)
|
|
336
|
+
case 'Free Convections':
|
|
337
|
+
freeConvs=[]
|
|
338
|
+
for _,row in df.iterrows():
|
|
339
|
+
storage1 = next((st for st in system.storages.items if st.name==row['Storage1']),None)
|
|
340
|
+
if storage1 is None:
|
|
341
|
+
storage1 = next((st for st in system.ext_storages.items if st.name==row['Storage1']),None)
|
|
342
|
+
if storage1 is None:
|
|
343
|
+
raise ValueError(f"Can not find Storage1: {row['Storage1']} of {row['Name']} in {sheet_name}. Check if storage exists.")
|
|
344
|
+
|
|
345
|
+
storage2 = next((st for st in system.storages.items if st.name==row['Storage2']),None)
|
|
346
|
+
if storage2 is None:
|
|
347
|
+
storage2 = next((st for st in system.ext_storages.items if st.name==row['Storage2']),None)
|
|
348
|
+
if storage2 is None:
|
|
349
|
+
raise ValueError(f"Can not find Storage2: {row['Storage2']} of {row['Name']} in {sheet_name}. Check if storage exists.")
|
|
350
|
+
freeConvs.append(FreeConvection.newFreeConvection(storage1,storage2,row['MassFlow'],row['CpFluid'],row['Tolerance'],row['Name']))
|
|
351
|
+
system.freeConvections.add_components(freeConvs)
|
|
352
|
+
case 'General Heat Transfers':
|
|
353
|
+
pass #TODO
|
|
354
|
+
case _:
|
|
355
|
+
pass
|
|
356
|
+
return system
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: thermobuilpy
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: ToDo
|
|
5
|
+
Home-page: https://github.com/hanneshanse/MilPython
|
|
6
|
+
Author: Hannes Hanse
|
|
7
|
+
Author-email: hannes.hanse@tu-clausthal.de
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: milp,time series,optimization,gurobi,gurobipy
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.txt
|
|
15
|
+
Requires-Dist: numpy
|
|
16
|
+
Requires-Dist: matplotlib
|
|
17
|
+
|
|
18
|
+
# ThermoBuilPy
|
|
19
|
+
MilPython is a python Framework ... TODO
|
|
20
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
src/ThermoBuilPy/__init__.py
|
|
5
|
+
src/ThermoBuilPy/component.py
|
|
6
|
+
src/ThermoBuilPy/connection.py
|
|
7
|
+
src/ThermoBuilPy/storage.py
|
|
8
|
+
src/ThermoBuilPy/stratifiedStorage.py
|
|
9
|
+
src/ThermoBuilPy/thermalSystem.py
|
|
10
|
+
src/ThermoBuilPy.egg-info/PKG-INFO
|
|
11
|
+
src/ThermoBuilPy.egg-info/SOURCES.txt
|
|
12
|
+
src/ThermoBuilPy.egg-info/dependency_links.txt
|
|
13
|
+
src/ThermoBuilPy.egg-info/requires.txt
|
|
14
|
+
src/ThermoBuilPy.egg-info/top_level.txt
|
|
15
|
+
src/thermobuilpy.egg-info/PKG-INFO
|
|
16
|
+
src/thermobuilpy.egg-info/SOURCES.txt
|
|
17
|
+
src/thermobuilpy.egg-info/dependency_links.txt
|
|
18
|
+
src/thermobuilpy.egg-info/requires.txt
|
|
19
|
+
src/thermobuilpy.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ThermoBuilPy
|