100 thrust: Sequence[float],
101 pressure: Sequence[float],
102 mdot_oxidiser: Sequence[float],
103 mdot_fuel: Sequence[float],
106 cstar: Sequence[float] |
None =
None,
107 stiffness_oxidiser: Sequence[float] |
None =
None,
108 stiffness_fuel: Sequence[float] |
None =
None,
109 extrapolated: Sequence[bool] |
None =
None,
110 burning: bool =
False,
112 """Total one burn given as columns over its burning samples.
114 ``before`` is the clock of the sample preceding the first one: where the
115 first burning step began.
119 raise ValueError(
"a burn needs at least one burning sample")
120 dt = [t[0] - before] + [t[k] - t[k - 1]
for k
in range(1, n)]
121 duration = t[-1] - before
122 impulse = sum(f * d
for f, d
in zip(thrust, dt))
123 ox = sum(m * d
for m, d
in zip(mdot_oxidiser, dt))
124 fu = sum(m * d
for m, d
in zip(mdot_fuel, dt))
125 total = [o + f
for o, f
in zip(mdot_oxidiser, mdot_fuel)]
128 median = sorted(total)[n // 2]
129 full = [k
for k
in range(n)
if total[k] >= FULL_FLOW_FRACTION * median]
or list(
133 def lowest(values: Sequence[float] |
None) -> float:
136 return min(values[k]
for k
in full)
138 ratios = [mdot_oxidiser[k] / mdot_fuel[k]
for k
in full
if mdot_fuel[k] > 0.0]
145 thrust_mean_N=impulse / duration
if duration > 0.0
else 0.0,
146 thrust_peak_N=
max(thrust),
147 thrust_min_N=lowest(thrust),
149 sum(p * d
for p, d
in zip(pressure, dt)) / duration
153 pc_min_Pa=lowest(pressure),
154 pc_max_Pa=
max(pressure),
155 of_mean=ox / fu
if fu > 0.0
else 0.0,
156 of_min=
min(ratios)
if ratios
else 0.0,
157 of_max=
max(ratios)
if ratios
else 0.0,
158 isp_s=impulse / (GRAVITY * burned)
if burned > 0.0
else 0.0,
160 sum(c * m * d
for c, m, d
in zip(cstar, total, dt)) / burned
161 if cstar
is not None and burned > 0.0
166 stiffness_oxidiser_min=lowest(stiffness_oxidiser),
167 stiffness_fuel_min=lowest(stiffness_fuel),
168 extrapolated_steps=sum(1
for e
in extrapolated
or ()
if e),
182 samples: Sequence[Sample], inlets: Mapping[str, str] |
None =
None
183) -> list[BurnReport]:
184 """Every burn in ``samples``, oldest first.
186 ``inlets`` maps ``oxidiser``/``fuel`` to the injector inlet node
187 (:attr:`feedtwin.session.burn.Probes.injector_inlet`). With it, stiffness
188 is the drop from that node to the chamber over chamber pressure -- the
189 line exit to the chamber, which is the whole of what an engine card owns.
190 Without it, a sample's mixture balance is used when it carries one.
192 A burn whose first step is the first sample given has no sample before it
193 to start from; it is timed from that sample's own clock less the next
194 step, which is right when the steps are even and is said by
195 :attr:`BurnReport.start_s` either way.
197 out: list[BurnReport] = []
207 run = samples[first:k]
209 before = samples[first - 1].t
211 before = run[0].t - (run[1].t - run[0].t)
219 run: Sequence[Sample],
221 inlets: Mapping[str, str],
225 chambers = [s.chamber
for s
in run]
226 assert all(c
is not None for c
in chambers)
228 def stiffness(side: str) -> list[float] |
None:
229 node = inlets.get(side)
230 if node
and all(node
in s.pressures
for s
in run):
232 (s.pressures[node] - c.pressure) / c.pressure
233 for s, c
in zip(run, chambers)
235 out: list[float] = []
240 leg = getattr(balance, side)
241 out.append(leg.stiffness(balance.chamber_pressure))
246 [c.thrust
for c
in chambers],
247 [c.pressure
for c
in chambers],
248 [c.mdot_oxidiser
for c
in chambers],
249 [c.mdot_fuel
for c
in chambers],
251 cstar=[c.combustion.cstar
for c
in chambers],
252 stiffness_oxidiser=stiffness(
"oxidiser"),
253 stiffness_fuel=stiffness(
"fuel"),
254 extrapolated=[bool(c.combustion.extrapolated)
for c
in chambers],
BurnReport summarise(Sequence[float] t, Sequence[float] thrust, Sequence[float] pressure, Sequence[float] mdot_oxidiser, Sequence[float] mdot_fuel, *, float before, Sequence[float]|None cstar=None, Sequence[float]|None stiffness_oxidiser=None, Sequence[float]|None stiffness_fuel=None, Sequence[bool]|None extrapolated=None, bool burning=False)