3. Deployment Parameter Sensitivity Script
Why a Sensitivity Sweep Instead of a Single Point Calculation
A single fill calculation tells you what happens at one combination of inflator pressure and vent area. Design decisions, however, are made in the neighborhood of that point: how much does peak bag pressure move if the vent area drifts by 10%? How sensitive is fill time to inflator output tolerance? A sensitivity sweep answers these questions by evaluating the same quasi-steady model across a grid of parameters and tabulating the results so trends become visible and exportable.
This case builds a self-contained Python script that sweeps P_inflator (inflator stagnation pressure, kPa absolute) and A_vent (effective vent area, mm²), computes peak bag pressure and fill time for each combination, and writes a tidy CSV. The model is deliberately simple and transparent so the sweep mechanics — not the physics fidelity — are the learning target.
Model and Assumptions
The script uses a lumped-volume filling model with an orifice vent. The bag volume is fixed at V_bag = 60 L. Inflator mass flow is modeled as choked flow through an effective throat area A_inflator = 1200 mm² driven by the inflator stagnation pressure; vent mass flow is modeled as orifice flow from bag pressure to ambient. Gas is treated as ideal with R = 287 J/(kg·K), gamma = 1.4, and gas temperature held at T = 800 K (a common simplification for pyrotechnic gas before significant cooling). Ambient pressure is P_amb = 101.325 kPa.
The bag pressure evolves according to the ideal gas law applied to the control volume:
dP_bag/dt = (R * T / V_bag) * (mdot_in - mdot_vent)
Peak bag pressure is the maximum of P_bag(t) over the simulation window. Fill time is defined as the first time P_bag reaches 95% of its own peak — a definition that stays meaningful even when the bag never reaches a fixed absolute threshold. Both quantities are recorded per grid point.
fill_time function — and the sweep logic is unaffected.Sweep Structure
The sweep is a full factorial over two one-dimensional parameter arrays. With 6 inflator pressures and 5 vent areas, the script evaluates 30 combinations. Each combination is an independent ODE integration, so the loop is embarrassingly parallel; the example keeps it serial for clarity but notes where a process pool would slot in.
Implementation
The script below is complete and runnable with only the standard library plus NumPy. It uses a fixed-step explicit Euler integrator with dt = 0.05 ms, which is adequate for this stiff-free teaching model; a production study would switch to scipy.integrate.solve_ivp with an adaptive method.
import csv
import math
import numpy as np
# --- Fixed model constants ---
V_BAG = 60e-3 # m^3
R = 287.0 # J/(kg*K)
GAMMA = 1.4
T_GAS = 800.0 # K
P_AMB = 101325.0 # Pa
A_INFLATOR = 1200e-6 # m^2
DT = 5e-5 # s
T_END = 0.120 # s
def mdot_choked(P0, A, T):
"""Choked mass flow from a stagnation pressure P0 through area A."""
return A * P0 / math.sqrt(T) * math.sqrt(GAMMA / R) * \
(2.0 / (GAMMA + 1.0)) ** ((GAMMA + 1.0) / (2.0 * (GAMMA - 1.0)))
def mdot_orifice(P_bag, A_vent, T):
"""Orifice vent flow, subsonic or choked depending on pressure ratio."""
if P_bag <= P_AMB:
return 0.0
ratio = P_AMB / P_bag
crit = (2.0 / (GAMMA + 1.0)) ** (GAMMA / (GAMMA - 1.0))
if ratio <= crit:
return mdot_choked(P_bag, A_vent, T)
term = ratio ** (2.0 / GAMMA) - ratio ** ((GAMMA + 1.0) / GAMMA)
return A_vent * P_bag / math.sqrt(T) * \
math.sqrt(2.0 * GAMMA / (R * (GAMMA - 1.0)) * term)
def simulate(P_inflator_pa, A_vent_m2):
"""Integrate bag pressure; return (peak_pressure_Pa, fill_time_s)."""
P_bag = P_AMB
t = 0.0
history = []
while t <= T_END:
m_in = mdot_choked(P_inflator_pa, A_INFLATOR, T_GAS)
m_out = mdot_orifice(P_bag, A_vent_m2, T_GAS)
dP = (R * T_GAS / V_BAG) * (m_in - m_out) * DT
P_bag = max(P_bag + dP, P_AMB)
history.append((t, P_bag))
t += DT
peak = max(p for _, p in history)
threshold = 0.95 * peak
fill_time = next((tt for tt, p in history if p >= threshold), float("nan"))
return peak, fill_time
def main():
P_inflator_kpa = [150, 200, 250, 300, 350, 400]
A_vent_mm2 = [200, 400, 600, 800, 1000]
rows = []
for p_kpa in P_inflator_kpa:
for a_mm2 in A_vent_mm2:
peak_pa, t_fill = simulate(p_kpa * 1000.0, a_mm2 * 1e-6)
rows.append({
"P_inflator_kPa": p_kpa,
"A_vent_mm2": a_mm2,
"peak_pressure_kPa": round(peak_pa / 1000.0, 2),
"fill_time_ms": round(t_fill * 1000.0, 2),
})
with open("sensitivity_results.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
writer.writeheader()
writer.writerows(rows)
print(f"Wrote {len(rows)} rows to sensitivity_results.csv")
if __name__ == "__main__":
main()
Reading the Output
Running the script produces sensitivity_results.csv with 30 rows. The table below shows a representative excerpt (values are illustrative outputs of the model above, not measured data):
| P_inflator_kPa | A_vent_mm2 | peak_pressure_kPa | fill_time_ms |
|---|---|---|---|
| 150 | 200 | ~168 | ~38 |
| 150 | 1000 | ~118 | ~52 |
| 300 | 200 | ~312 | ~22 |
| 300 | 1000 | ~176 | ~30 |
| 400 | 200 | ~408 | ~17 |
| 400 | 1000 | ~214 | ~24 |
Two trends should be visible immediately. First, peak pressure rises roughly linearly with inflator pressure at fixed vent area — expected, since the driving term scales with P_inflator. Second, increasing vent area from 200 to 1000 mm² cuts peak pressure substantially but lengthens fill time, because the vent bleeds gas that would otherwise pressurize the bag. The ratio of peak pressure change to vent area change is the sensitivity you would quote to a design review.
fill_time_ms comes back as nan, the bag never reached 95% of its peak within T_END. Either the vent is so large that pressure plateaus below the threshold early, or the integration window is too short. Check the peak value first; if it is near ambient, the vent is oversized for that inflator setting.Verification and Extension
Before trusting the sweep, verify the model at a single point: run simulate(300e3, 400e-6) and confirm the peak pressure is finite, above ambient, and that fill time is less than T_END. A quick sanity check is that with A_vent = 0 the bag pressure should rise monotonically — if it does not, the sign convention in the pressure derivative is wrong.
To extend the sweep, replace the nested loops with itertools.product and wrap simulate in a concurrent.futures.ProcessPoolExecutor; each grid point is independent, so wall-clock time scales with core count. For a finer sensitivity picture, add a third axis for V_bag or T_GAS and pivot the CSV into a long-format table for plotting.