-
Notifications
You must be signed in to change notification settings - Fork 1
/
KilnSimulator.py
142 lines (112 loc) · 6.04 KB
/
KilnSimulator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import logging
import time
log = logging.getLogger(__name__)
# log.level = logging.DEBUG
# One instance to allow for heat transfer between zones. (Could use a singleton)
class ZoneTemps:
def __init__(self):
self.old_temps = {}
self.new_temps = {}
def add_new_temp(self, zone_name, temp):
self.new_temps.update({zone_name: temp})
def set_old_temps_to_new(self):
self.old_temps = self.new_temps
def get_temps(self):
return self.old_temps
# Each zone has its own simulator.
class KilnSimulator:
def __init__(self, zone_name: str, speed_up_factor: int, zone_temps: ZoneTemps):
self.latest_temperature = 27 # Temperatures are in degrees Centigrade
self.latest_time = time.time()
self.sim_speedup = speed_up_factor
log.warning('Running simulator. In case you thought otherwise.')
zone_temps.add_new_temp(zone_name, 27)
self.zone_temps = zone_temps
self.zone_name = zone_name
self.t_environment = 27 # Degrees C
self.t_elements = 27
self.power = 2800 # Kiln power watts for this zone
self.steve_b = 5.669e-8 # Stephan-Boltzmann constant w/m**2/K**4
self.area_el = 0.157 # m**2, Effective area of elements in square meters. Estimate used for conducti0n and radiation.
self.area_load = 0.325 # m**2, Zone heat loss area not incuding elements area, e.g. Zone one top and side wall.
self.area_adjacent_zones = 0.168 # m**2, Area
self.heat_loss = 3.5 # Conductive/convective heat loss resistance through kiln walls, watts/m**2/degrees C
self.heat_capacity = 850 # J/kg/degrees C, ceramic materials are similar.
self.elements_mass = 1 # kg, include part of the nearby bricks. (This is a guess.)
self.load_mass = 20 # kg, the ware and kiln shelves
def update_sim(self, heat_factor: float):
now = time.time()
delta_time = (now - self.latest_time) * self.sim_speedup
self.latest_temperature = self.find_temperature(delta_time, heat_factor)
self.latest_time = now
def get_latest_temperature(self) -> float:
return self.latest_temperature
def find_temperature(self, delta_time, heat_factor):
# Two lumped heat capacities kiln model. This assumes two temperatures, t-elements and t_kiln
# Heat is lost by conduction and convection to the environement.
# This assumes convection in the kiln is not important, it becomes much less important at higher
# tempertures.
# Radiant heat transfer coupling between zones added 08/07/2023
power_in = self.power * heat_factor
log.debug('Power factor watts: ' + str(power_in))
# Heat lost from the elements direclty through the kiln walls
loss_elements = self.area_el * self.heat_loss * (self.t_elements - self.t_environment)
log.debug('Elements loss watts: ' + str(loss_elements))
# Assumes all the power (heat) to the load (ware plus shelves) is via thermal radiation
power_to_load = self.steve_b * self.area_el * ((self.t_elements + 273)**4 - (self.latest_temperature + 273) **4)
log.debug('Load input power watts: ' + str(power_to_load))
# This is from the differential equation for lumped heat capacity: q = -Cm(dT/dt). q here is the power stored in
# the elements.
delta_t_elements = (delta_time / (self.elements_mass * self.heat_capacity)) * \
(power_in - loss_elements - power_to_load)
self.t_elements = self.t_elements + delta_t_elements
log.debug('Elements temperature: ' + str(self.t_elements))
loss_load = self.heat_loss * self.area_load * (self.latest_temperature - self.t_environment)
coupling_gain = self.radiative_coupling_gain(self.zone_temps.get_temps(), self.zone_name)
log.debug('Coupling gain: ' + str(coupling_gain))
delta_t_load = (delta_time / (self.load_mass * self.heat_capacity)) * \
(power_to_load - loss_load + coupling_gain)
temperature = self.latest_temperature + delta_t_load
log.debug('Temp: ' + str(temperature))
self.update_zone_temps(temperature)
return temperature
# Heat transfer between adjacent Zones
def radiative_coupling_gain(self, zone_old_temps: dict, zone_name: str):
coupling_power = 0
keys = zone_old_temps.keys()
num_zones = len(keys)
if num_zones > 1:
temps = list(zone_old_temps.values())
index = -1
for i, key in enumerate(keys):
if key == zone_name:
index = i
if index == 0:
coupling_power = self.coupling(temps[index + 1], temps[index])
elif index == 1:
coupling_power = self.coupling(temps[index - 1], temps[index])
if num_zones > 2:
coupling_power = coupling_power + self.coupling(temps[index + 1], temps[index])
elif index == 2:
coupling_power = self.coupling(temps[index - 1], temps[index])
if num_zones > 3:
coupling_power = coupling_power + self.coupling(temps[index + 1], temps[index])
elif index == 3: # Four zones max
coupling_power = self.coupling(temps[index - 1], temps[index])
log.debug('Coupling: ' + str(coupling_power))
return coupling_power
def coupling(self, t1: float, t2: float) -> float:
return self.steve_b * self.area_adjacent_zones * ((t1 + 273) **4 - (t2 + 273) **4)
# This is for radiative coupling, the heat transfer between zones
def update_zone_temps(self, temperature):
self.zone_temps.add_new_temp(self.zone_name, temperature)
keys = self.zone_temps.new_temps.keys()
if self.zone_name == list(keys)[len(keys) - 1]: # It's the last (bottom) zone
self.zone_temps.set_old_temps_to_new()
# This is for testing
if __name__ == '__main__':
sim = KilnSimulator(100)
while True:
sim.update_sim(0.6)
temp = sim.get_latest_temperature()
time.sleep(0.7)