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.
@@ -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,3 @@
1
+ # ThermoBuilPy
2
+ MilPython is a python Framework ... TODO
3
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,2 @@
1
+ numpy
2
+ matplotlib
@@ -0,0 +1 @@
1
+ ThermoBuilPy