4. Calculate 3 ms Chest Acceleration Clip and Overlay Limit Curves
Why the 3 ms Clip Is Not a Simple Peak
The 3 ms chest acceleration criterion answers a different question than a raw peak: it asks how much acceleration the chest can sustain continuously for a 3 millisecond window. A short spike of 90 g lasting 0.4 ms is far less injurious than a sustained 65 g plateau spanning 5 ms, yet a naive max(a) call ranks the spike higher. The clip value is therefore defined as the maximum, over all sliding 3 ms windows, of the window average:
clip3ms = max over t of [ (1/Δt) · ∫(t → t+3ms) a(τ) dτ ]
With sampled data at uniform step dt, the integral becomes a moving sum. For a window of N = round(0.003 / dt) samples, the clip is max(cumsum[i+N] − cumsum[i]) / (N · dt). Two practical details dominate accuracy: the signal must already be filtered to the correct CFC class (CFC 600 for chest acceleration per SAE J211), and the window length must be expressed in seconds, not samples, so that a change of sampling rate does not silently change the criterion.
Reference Signal and Expected Clip
To make the calculation verifiable, use a synthetic pulse with a known answer. A rectangular plateau of 60 g held for exactly 4 ms, sampled at 10 kHz, must produce a clip of 60 g: every fully contained 3 ms window averages 60 g. A triangular pulse peaking at 80 g with a 6 ms base gives a lower clip, because no 3 ms window contains the full peak. These two cases let you confirm the implementation before trusting it on simulation output.
import numpy as np
def clip_3ms(acc_g, dt):
"""Maximum 3 ms moving average of a chest acceleration signal.
acc_g : 1-D array of acceleration in g (already CFC-filtered)
dt : sample interval in seconds
"""
n = int(round(0.003 / dt))
if n < 1:
raise ValueError("sampling rate too low for a 3 ms window")
c = np.concatenate(([0.0], np.cumsum(acc_g)))
# window sums for every fully contained window
sums = c[n:] - c[:-n]
return float(sums.max() / (n * dt))
# --- verification on synthetic pulses ---
dt = 1e-4 # 10 kHz
t = np.arange(0, 0.020, dt)
plateau = np.where((t >= 0.005) & (t < 0.009), 60.0, 0.0)
tri = np.where((t >= 0.005) & (t <= 0.011),
80.0 * (1 - np.abs(t - 0.008) / 0.003), 0.0)
print("plateau clip:", clip_3ms(plateau, dt)) # expect 60.0
print("triangle clip:", clip_3ms(tri, dt)) # expect < 80.0
The plateau result should print 60.0 to within floating-point noise. The triangle result is a useful sanity check rather than a fixed number: it must be strictly below 80 g, and it should be close to the average of the triangle over its steepest 3 ms span. If the plateau returns something like 59.7 or 60.3, the window length is off by one sample — a classic off-by-one in the c[n:] − c[:-n] slicing.
Overlaying Against the Limit Line
The regulatory limit for chest acceleration is conventionally drawn as a horizontal line at 60 g for the 3 ms criterion. The overlay is not the acceleration trace itself but a clip-versus-load-case chart: one marker per simulation, positioned by its clip value, with the limit line as a reference. This makes pass/fail readable at a glance across a matrix of load cases, which a stack of time histories cannot do.
import matplotlib.pyplot as plt
LIMIT_G = 60.0
cases = ["Frontal 48 km/h", "Frontal 56 km/h", "Oblique 32 km/h",
"Side 27 km/h", "Rear 32 km/h"]
clips = [52.4, 63.1, 47.8, 58.9, 41.2] # example values, g
fig, ax = plt.subplots(figsize=(8, 4.2))
colors = ["#2e7d32" if v <= LIMIT_G else "#c62828" for v in clips]
bars = ax.barh(cases, clips, color=colors, edgecolor="white")
ax.axvline(LIMIT_G, color="#e65100", lw=2, ls="--", label="3 ms limit (60 g)")
for bar, v in zip(bars, clips):
status = "PASS" if v <= LIMIT_G else "FAIL"
ax.text(v + 0.8, bar.get_y() + bar.get_height()/2,
f"{v:.1f} g {status}", va="center", fontsize=9)
ax.set_xlabel("3 ms chest acceleration clip (g)")
ax.set_xlim(0, 75)
ax.legend(loc="lower right")
ax.set_title("3 ms clip vs regulatory limit — example load cases")
plt.tight_layout()
plt.savefig("clip_overlay.png", dpi=150)
In this example dataset the 56 km/h frontal case exceeds the limit at 63.1 g and is annotated FAIL; the remaining four cases pass. The annotation text is generated from the same comparison that colors the bar, so the label and the color can never disagree — a small but important guard against reporting errors.
Batch Processing and the Review Band
Real studies run dozens of load cases, so the clip function should be applied in a loop over a results directory, with the channel name and filter class read from metadata rather than hard-coded. A practical addition is a review band: any case whose clip falls within about 2 g of the limit is flagged REVIEW rather than PASS, because filtering choices, sampling rate, and channel calibration can shift the value by that much. The decision strip in the diagram encodes exactly this three-way outcome, and the purple branch captures the most common implementation defect — a plateau test that does not return 60 g, which almost always means the window length is off by one sample.
clip_3ms and run them on every pipeline change. They cost milliseconds and catch the two failure modes that silently corrupt every downstream pass/fail call: wrong window length and unfiltered input.Deliverable: Clip Summary Table
The following table is the artifact to hand to the design team. It carries the clip value, the limit, the margin, and the status, with the status derived from the margin so the table is internally consistent.
| Load case | Channel | Filter | Clip (g) | Limit (g) | Margin (g) | Status |
|---|---|---|---|---|---|---|
| Frontal 48 km/h | Chest_Accel_Res | CFC 600 | 52.4 | 60.0 | +7.6 | PASS |
| Frontal 56 km/h | Chest_Accel_Res | CFC 600 | 63.1 | 60.0 | −3.1 | FAIL |
| Oblique 32 km/h | Chest_Accel_Res | CFC 600 | 47.8 | 60.0 | +12.2 | PASS |
| Side 27 km/h | Chest_Accel_Res | CFC 600 | 58.9 | 60.0 | +1.1 | REVIEW |
| Rear 32 km/h | Chest_Accel_Res | CFC 600 | 41.2 | 60.0 | +18.8 | PASS |
Values above are illustrative example data for the workflow, not measured results. The side-impact case at 58.9 g sits inside the review band and should be re-examined for filter class and channel calibration before it is reported as a pass. Once the table and the overlay chart agree case-for-case, the clip analysis is complete and ready to feed the injury-criteria summary.