forked from smross106/propellant-selector
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathperformance_calculator.py
More file actions
249 lines (194 loc) · 9.78 KB
/
performance_calculator.py
File metadata and controls
249 lines (194 loc) · 9.78 KB
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
from propellant import Propellant, Propellant_Mix, Pressurant
from nist_reader import propellant_data
from math import pi, ceil
class Material(object):
def __init__(self, name, yield_strength, density):
self.name = name
self.yield_strength = yield_strength
self.density = density
class Vehicle(object):
def __init__(self, propellant_mix, material, pressurant, diameter, ullage):
self.propellant_mix = propellant_mix
self.material = material
self.pressurant = pressurant
self.radius = diameter/2
self.ullage = ullage
self.temperature = self.propellant_mix.temperature
def set_tank_thickness(self, safety_factor, resolution):
pressure = self.propellant_mix.pressure * 1e5
min_thickness = pressure * self.radius / self.material.yield_strength
self.wall_thickness = \
ceil(safety_factor * min_thickness / resolution) * resolution
def set_representative_pressurant_data(self,
pressurant_storage_pressure,
pressurant_tank_safety_factor,
pressurant_plumbing_mass_factor):
"""
Representative Pressurant Mass is the mass of pressurant gas needed to
fill a Representative Length tank to full pressure, accounting for
propellant vapour pressure
Representative Pressurant System Mass is the mass of spherical tanks + \
pressurant needed to achieve this
"""
area = (self.radius**2 * pi) - \
(2 * pi * self.radius * self.wall_thickness)
fuel_overpressure = \
(self.propellant_mix.pressure - \
self.propellant_mix.fuel.pressure(self.temperature)) * \
(10**5)
oxidiser_overpressure = \
(self.propellant_mix.pressure - \
self.propellant_mix.oxidiser.pressure(self.temperature)) * \
(10**5)
fuel_overpressure_volume = self.rep_fuel_length * area
oxidiser_overpressure_volume = self.rep_oxidiser_length * area
fuel_pressurant_mass = \
fuel_overpressure * fuel_overpressure_volume * \
self.pressurant.molar_mass / \
(self.pressurant.R * self.temperature)
oxidiser_pressurant_mass = \
oxidiser_overpressure * oxidiser_overpressure_volume * \
self.pressurant.molar_mass / \
(self.pressurant.R * self.temperature)
representative_pressurant_mass = \
fuel_pressurant_mass + oxidiser_pressurant_mass
pressurant_tank_volume = \
representative_pressurant_mass * self.pressurant.R * \
self.temperature / \
(pressurant_storage_pressure * (10**5) * self.pressurant.molar_mass)
pressurant_tank_radius = ((3*pressurant_tank_volume)/(4*pi))**(1/3)
pressurant_tank_wall_thickness = \
(pressurant_storage_pressure * (10**5) * pressurant_tank_radius) / \
(2 * self.material.yield_strength / pressurant_tank_safety_factor)
pressurant_tank_mass = 4 * pi * pressurant_tank_radius**2 * \
pressurant_tank_wall_thickness * self.material.density
pressurant_system_mass = \
(pressurant_tank_mass * pressurant_plumbing_mass_factor) + \
representative_pressurant_mass
self.rep_pressurant_mass = representative_pressurant_mass
self.rep_pressurant_system_mass = pressurant_system_mass
def set_representative_scales(self, bulkhead_mass_factor):
"""
Representative Length is the total length of tank required for 1kg of
fuel and equivalent oxidiser
Representative Mass is the mass of tankage (not tanks and propellant)
needed to hold the above quantities of propellant
Representative Prop System Mass is Representative Mass plus mass of
propllant needed to fill that
Representative Mass Fraction is the mass fraction of a tank with
Representative Length
Note that this does not include bulkheads etc - potential future feature?
"""
effective_fuel_density = \
(self.ullage * self.propellant_mix.fuel.vapor_density(self.temperature)) + \
((1-self.ullage) * self.propellant_mix.fuel.liquid_density(self.temperature))
effective_oxidiser_density = \
(self.ullage * self.propellant_mix.oxidiser.vapor_density(self.temperature)) + \
((1-self.ullage) * self.propellant_mix.oxidiser.liquid_density(self.temperature))
area = (self.radius**2 * pi) - \
(2 * pi * self.radius * self.wall_thickness)
fuel_length = 1 / (effective_fuel_density * area)
oxidisier_length = \
self.propellant_mix.OF_mass_ratio / \
(effective_oxidiser_density * area)
self.rep_fuel_length = fuel_length
self.rep_oxidiser_length = oxidisier_length
representative_length = fuel_length + oxidisier_length
representative_mass = \
2 * pi * self.radius * self.wall_thickness * \
representative_length * self.material.density * bulkhead_mass_factor
representative_system_mass = \
(1+self.propellant_mix.OF_mass_ratio) + \
representative_mass
representative_mass_fraction = \
(1+self.propellant_mix.OF_mass_ratio) / \
representative_mass
self.rep_length = representative_length
self.rep_mass = representative_mass
self.rep_system_mass = representative_system_mass
def set_representative_engine_data(self):
"""
Representative thrust - assume a max flow rate of 0.1kg/s for oxidiser
Representative TWR - representative Thrust divided by (mass of )
"""
oxidiser_mass_flow = 1
burn_time = self.propellant_mix.OF_mass_ratio
fuel_mass_flow = 1 / burn_time
mass_flow = oxidiser_mass_flow + fuel_mass_flow
representative_thrust = self.propellant_mix.ISP_sea_level * 9.81 * mass_flow
representative_mass = self.rep_system_mass + self.rep_pressurant_system_mass
representative_TWR = representative_thrust / representative_mass
representative_impulse = representative_thrust * burn_time
self.rep_thrust = representative_thrust
self.rep_TWR = representative_TWR
self.rep_impulse = representative_impulse
def __repr__(self):
# Output as nicely formatted markdown for any use case
outstr = f"""\
# Initial Vehicle Parameters:
| |
|--------------------------------------|---
| Fuel | {self.propellant_mix.fuel_name}
| Oxidiser | {self.propellant_mix.oxidiser_name}
| Propellant temperature | {str(self.temperature)}K
| Tank pressure | {str(self.propellant_mix.pressure)}bar
| Tank diameter | {str(self.radius*2)}m
| Tank material | {self.material.name}
| Pressurant | {self.pressurant.name}
# Representative Performance of a System with 1kg of fuel
** Masses of tank walls, propellants and pressurising system considered **
* Representative Thrust and TWR assumes a constant fuel flow rate of 0.1kg/s, *
* and does not factor in engine or airframe mass *
| |
|--------------------------------------|---
| Length of tank required | {self.rep_length:.2f}m
| Tank wall thickness | {self.wall_thickness*1000:.2f}mm
| Tank mass (inc. bulkhead estimate) | {self.rep_mass:.2f}kg
| Total propellant system mass | {self.rep_system_mass:.2f}kg
| Pressurant mass required | {self.rep_pressurant_mass:.2f}kg
| Pressurant system mass estimate | {self.rep_pressurant_system_mass:.2f}kg
| Representative burn time | {self.propellant_mix.OF_mass_ratio:.2f}s
| Representative thrust | {self.rep_thrust:.2f}N
| Representative system TWR | {self.rep_TWR:.2f}
| Representative system impulse | {self.rep_impulse:.2f}Ns"""
# pad and add ending |
maxlen = max((len(i) for i in outstr.split("\n")
if len(i) > 0 and i[0] == "|"))
return "\n".join(f"{i}{i[1]*(maxlen - len(i) + 2)}|"
if len(i) > 0 and i[0] == "|" else i for i in outstr.split("\n"))
if __name__ == "__main__":
aluminium = Material("aluminium", 170*(10**6), 2700)
pressurants = {
"helium": Pressurant("helium", 0.004, 1.66),
"nitrogen": Pressurant("nitrogen", 0.028, 1.40),
"carbon-dioxide": Pressurant("carbon-dioxide", 0.044, 1.29),
}
ethane_nitrous = Propellant_Mix("ammonia", "nitrous", 250, 17)
test_tank = Vehicle(
propellant_mix = ethane_nitrous,
material = aluminium,
pressurant = pressurants["carbon-dioxide"],
diameter = 0.1524,
# SR: "Tank volume that must be gas - change if current thinking
# changes, but a good rule of thumb"
ullage = 0.1,
)
# ECC: "Not sure why this safety factor is so high but this makes it the
# same as SR's original hard-coded value"
test_tank.set_tank_thickness(
safety_factor = 5,
resolution = 0.001, # Rounded up to a multiple of this value
)
test_tank.set_representative_scales(
bulkhead_mass_factor = 1.25
)
test_tank.set_representative_pressurant_data(
# lower end of paintball tank pressure
pressurant_storage_pressure = 210, # bar
pressurant_tank_safety_factor = 2,
# additional increase in mass of system to account for valves and
# plumbing - completely arbitrary
pressurant_plumbing_mass_factor = 5,
)
test_tank.set_representative_engine_data()
print(test_tank)