crashmetrics 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CrashMetrics contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,290 @@
1
+ Metadata-Version: 2.4
2
+ Name: crashmetrics
3
+ Version: 0.3.0
4
+ Summary: Python tools for automotive crash engineering metrics
5
+ Author: Voonna Satish Kumar
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/vsk1729/crashmetrics
8
+ Project-URL: Source, https://github.com/vsk1729/crashmetrics
9
+ Project-URL: Issues, https://github.com/vsk1729/crashmetrics/issues
10
+ Project-URL: Changelog, https://github.com/vsk1729/crashmetrics/blob/main/CHANGELOG.md
11
+ Keywords: crashworthiness,LS-DYNA,CAE,HIC,automotive-safety
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Intended Audience :: Manufacturing
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Typing :: Typed
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: numpy>=1.24
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=8; extra == "dev"
29
+ Requires-Dist: build; extra == "dev"
30
+ Requires-Dist: twine; extra == "dev"
31
+ Requires-Dist: mypy; extra == "dev"
32
+ Requires-Dist: ruff; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # CrashMetrics
36
+
37
+ Python tools for automotive crash engineering calculations.
38
+
39
+ **Development status:** Alpha — not validated for regulatory compliance.
40
+
41
+ ## Features
42
+
43
+ - Head Injury Criterion (HIC), for example HIC15 and HIC36, via `crashmetrics.pedestrian.calculate_hic`
44
+ - Signal processing in `crashmetrics.signals`: SAE J211 CFC filtering (`cfc_filter`), resultants (`resultant`), unit conversion (`to_g`, `to_seconds`) and uniform resampling (`resample_uniform`)
45
+ - Acceleration metrics in `crashmetrics.acceleration`: peak value and time (`peak`) and the 3 ms clip, cumulative or continuous (`clip_3ms`)
46
+ - Vehicle pulse metrics in `crashmetrics.pulse`: Occupant Load Criterion (`olc`) and a pulse summary with delta-V, time to zero velocity, maximum displacement, rebound and deceleration (`pulse_summary`)
47
+ - Lower-extremity criteria in `crashmetrics.lower_extremity`: Tibia Index (`tibia_index`) and femur compressive force against a force–time limit (`femur_force_criterion`)
48
+ - Generic exceedance helpers in `crashmetrics.acceleration`: `exceedance_level` and `exceedance_duration`
49
+ - Energy and force–displacement metrics in `crashmetrics.energy`: velocity and displacement from acceleration (`integrate_acceleration`), cumulative absorbed energy (`absorbed_energy`), crush summary with peak and mean force, crush force efficiency and specific energy absorption (`crush_metrics`), and `kinetic_energy`
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ python -m pip install crashmetrics
55
+ ```
56
+
57
+ To get the latest unreleased changes, install from GitHub instead:
58
+
59
+ ```bash
60
+ python -m pip install "git+https://github.com/vsk1729/crashmetrics.git"
61
+ ```
62
+
63
+ Requires Python 3.10 or later and NumPy 1.24 or later.
64
+
65
+ ## Example
66
+
67
+ ```python
68
+ from crashmetrics.pedestrian import calculate_hic
69
+
70
+ time = [0.0, 0.005, 0.010, 0.015]
71
+ acceleration = [10.0, 10.0, 10.0, 10.0]
72
+
73
+ result = calculate_hic(
74
+ time,
75
+ acceleration,
76
+ window_ms=15.0,
77
+ )
78
+
79
+ print(result.value)
80
+ print(result.duration_ms)
81
+ ```
82
+
83
+ ### From LS-DYNA output to HIC15
84
+
85
+ For a headform model in the mm-ms-kg unit system, with axis
86
+ accelerations `ax`, `ay`, `az` in mm/ms² and `time_ms` in ms
87
+ (uniformly sampled):
88
+
89
+ ```python
90
+ from crashmetrics.pedestrian import calculate_hic
91
+ from crashmetrics.signals import cfc_filter, resultant, to_g, to_seconds
92
+
93
+ t = to_seconds(time_ms, "ms")
94
+
95
+ # Filter each component with CFC 1000, then form the resultant.
96
+ components_g = [
97
+ cfc_filter(t, to_g(a, "mm/ms^2"), cfc=1000) for a in (ax, ay, az)
98
+ ]
99
+ a_res = resultant(*components_g)
100
+
101
+ hic15 = calculate_hic(t, a_res, window_ms=15.0)
102
+ print(f"HIC15 = {hic15.value:.0f} over {hic15.duration_ms:.2f} ms")
103
+ ```
104
+
105
+ The same filtered resultant gives the peak and the 3 ms clip:
106
+
107
+ ```python
108
+ from crashmetrics.acceleration import clip_3ms, peak
109
+
110
+ a_peak = peak(t, a_res)
111
+ print(f"Peak = {a_peak.value:.1f} g at {a_peak.time * 1000:.2f} ms")
112
+ print(f"3 ms clip = {clip_3ms(t, a_res):.1f} g")
113
+ ```
114
+
115
+ If the output interval is not uniform, use
116
+ `resample_uniform(t, a, sample_rate)` before filtering.
117
+
118
+ ### Energy and crush metrics
119
+
120
+ For a crash box or rail with force in kN and crush in mm (energy in J):
121
+
122
+ ```python
123
+ from crashmetrics.energy import absorbed_energy, crush_metrics
124
+
125
+ energy_curve = absorbed_energy(crush_mm, force_kN) # J at each sample
126
+
127
+ result = crush_metrics(crush_mm, force_kN, mass=1.2, max_displacement=150.0)
128
+ print(f"Energy {result.energy:.0f} J, mean force {result.mean_force:.1f} kN")
129
+ print(f"Peak {result.peak_force:.1f} kN at {result.peak_force_displacement:.1f} mm")
130
+ print(f"CFE {result.crush_force_efficiency:.2f}, SEA {result.specific_energy_absorption:.0f} J/kg")
131
+ ```
132
+
133
+ `max_displacement` evaluates the metrics over a fixed stroke. Mean
134
+ force is net energy divided by crush distance and CFE is mean force
135
+ divided by peak force, so always report the stroke with them.
136
+
137
+ From a vehicle or impactor acceleration pulse (mm/ms² and ms give
138
+ m/s and mm):
139
+
140
+ ```python
141
+ from crashmetrics.energy import integrate_acceleration, kinetic_energy
142
+
143
+ velocity, displacement = integrate_acceleration(time_ms, accel_mm_ms2, v0=15.6)
144
+ print(f"Dynamic crush {displacement.max():.0f} mm")
145
+ print(f"Initial kinetic energy {kinetic_energy(1200.0, 15.6) / 1000:.1f} kJ")
146
+ ```
147
+
148
+ The energy functions do not convert units; use a consistent system.
149
+ Integration amplifies sensor offset and drift, so check the input
150
+ pulse before integrating test data.
151
+
152
+ ### Occupant Load Criterion and pulse summary
153
+
154
+ Inputs are SI (s, m/s², m/s). The acceleration is the vehicle's
155
+ longitudinal acceleration in its direction of travel, so it is
156
+ negative while decelerating, and the record starts at T0:
157
+
158
+ ```python
159
+ from crashmetrics.pulse import olc, pulse_summary
160
+ from crashmetrics.signals import cfc_filter
161
+
162
+ a = cfc_filter(t, accel_x_m_s2, cfc=180) # Euro NCAP uses CFC 180 for OLC
163
+ result = olc(t, a, v0=50 / 3.6)
164
+ print(f"OLC = {result.value:.1f} g (t1 = {result.t1*1000:.1f} ms, t2 = {result.t2*1000:.1f} ms)")
165
+
166
+ summary = pulse_summary(t, a, v0=50 / 3.6)
167
+ print(f"delta-V {summary.delta_v:.2f} m/s, max displacement {summary.max_displacement*1000:.0f} mm")
168
+ ```
169
+
170
+ `olc` follows the Euro NCAP definition (Data Acquisition and Injury
171
+ Calculation protocol): a virtual occupant moves 65 mm in free flight,
172
+ then decelerates uniformly over a further 235 mm until it matches the
173
+ vehicle velocity. The OLC is that deceleration, expressed in g using
174
+ 9.81 m/s² as Euro NCAP does (change with `gravity`). The tests check it
175
+ against the closed-form solution for constant deceleration and against
176
+ an independent numerical formulation.
177
+
178
+ ### Tibia Index and femur force
179
+
180
+ Forces in N, moments in N m, compression negative by default (Euro
181
+ NCAP convention):
182
+
183
+ ```python
184
+ from crashmetrics.lower_extremity import femur_force_criterion, tibia_index
185
+
186
+ ti = tibia_index(t, mx_upper, my_upper, fz_upper, dummy="hybrid3_50")
187
+ print(f"TI upper = {ti.value:.2f}")
188
+
189
+ femur = femur_force_criterion(t, fz_femur, limit="r94_hybrid3_50")
190
+ print(f"Peak {femur.peak_compression/1000:.2f} kN, utilisation {femur.utilisation:.2f}, passed {femur.passed}")
191
+ ```
192
+
193
+ - `tibia_index`: `TI = |MR/MRc| + |Fz/Fzc|` while the tibia is in
194
+ compression, zero otherwise, with `MR = sqrt(Mx² + My²)`. Critical
195
+ values for Hybrid III 50th, 5th, 95th and THOR are taken from Euro
196
+ NCAP CP 005 (`TIBIA_INDEX_CRITICAL_VALUES`); you can also pass your
197
+ own.
198
+ - `femur_force_criterion`: builds the exceedance curve (force reached
199
+ or exceeded versus duration) and compares it with a force–time limit.
200
+ `"r94_hybrid3_50"` is the UN-ECE R94 limit of 9.07 kN at 0 ms falling
201
+ to 7.58 kN at 10 ms and longer; you can pass your own points. The
202
+ default `mode="cumulative"` adds up separate intervals; use
203
+ `mode="continuous"` if your regulation requires a single interval.
204
+
205
+ Check all constants against the protocol version you assess to.
206
+
207
+ ### 3 ms clip
208
+
209
+ `clip_3ms` returns the highest level that the signal reaches or
210
+ exceeds for 3 ms (change with `duration_ms`):
211
+
212
+ - `mode="cumulative"` (default): 3 ms in total, possibly over several
213
+ separate intervals. Computed exactly for the linearly interpolated
214
+ signal.
215
+ - `mode="continuous"`: 3 ms in a single interval. Never higher than
216
+ the cumulative value.
217
+
218
+ Check which definition your assessment protocol requires.
219
+
220
+ ### CFC filter
221
+
222
+ `cfc_filter` implements the SAE J211-1 Appendix C digital filter: a
223
+ 2-pole Butterworth low-pass run forward and backward, giving a
224
+ phaseless 4-pole response. The tests check the gain against the J211
225
+ corridor points for CFC 60, 180, 600 and 1000 (flat to the CFC
226
+ frequency, about −3 dB at 1.65 × CFC). The sample rate must exceed
227
+ 2 × 2.0775 × CFC (about 4.2 kHz for CFC 1000).
228
+
229
+ Each end of the signal is padded by odd reflection (`pad_ms`, default
230
+ 10 ms) to reduce start-up transients. This padding choice is part of
231
+ this package, not of J211; confirm it matches your protocol.
232
+
233
+ ## HIC units and numerical method
234
+
235
+ - Time input: seconds
236
+ - Acceleration input: non-negative resultant magnitude in g
237
+ - HIC window: milliseconds
238
+ - Numerical integration: trapezoidal rule on a refined, linearly interpolated time grid
239
+
240
+ Results depend on the selected grid resolution and the numerical method.
241
+
242
+ If the record is shorter than `window_ms`, `calculate_hic` issues a `UserWarning` and evaluates HIC over the available duration. Make sure the record contains the whole impact.
243
+
244
+ ## Development
245
+
246
+ Clone the repository and install it in editable mode with the
247
+ development tools (pytest, ruff, mypy, build):
248
+
249
+ ```bash
250
+ git clone https://github.com/vsk1729/crashmetrics.git
251
+ cd crashmetrics
252
+ python -m pip install -e ".[dev]"
253
+ ```
254
+
255
+ Run the same checks as CI before opening a pull request:
256
+
257
+ ```bash
258
+ python -m pytest # tests
259
+ ruff check . # lint
260
+ mypy # type check
261
+ ```
262
+
263
+ `examples/hic_convergence.py` shows how the HIC result changes with
264
+ `resolution_ms`; run it after changing the HIC algorithm.
265
+
266
+ The version is set only in `src/crashmetrics/__init__.py`. Record
267
+ user-visible changes in [CHANGELOG.md](CHANGELOG.md).
268
+
269
+ ### Releasing
270
+
271
+ Releases are published by `.github/workflows/release.yml` using PyPI
272
+ Trusted Publishing, so no API tokens are stored in the repository.
273
+
274
+ 1. Bump `__version__` and add the CHANGELOG entry; merge to `main`.
275
+ 2. Optional dry run: run the **Release** workflow manually from the
276
+ Actions tab to publish to TestPyPI.
277
+ 3. Publish a GitHub release with tag `v<version>`, for example
278
+ `v0.3.0`. The workflow checks the tag matches `__version__`,
279
+ builds and checks the sdist and wheel, and publishes to PyPI.
280
+
281
+ A version can be uploaded to PyPI only once; fix mistakes with a new
282
+ version.
283
+
284
+ ## Engineering disclaimer
285
+
286
+ This software is an engineering calculation aid. Users must independently validate numerical results and confirm that input processing, filtering, sampling, and calculation methods match the applicable assessment protocol. The software does not certify regulatory compliance.
287
+
288
+ ## License
289
+
290
+ MIT. See the LICENSE file.
@@ -0,0 +1,256 @@
1
+ # CrashMetrics
2
+
3
+ Python tools for automotive crash engineering calculations.
4
+
5
+ **Development status:** Alpha — not validated for regulatory compliance.
6
+
7
+ ## Features
8
+
9
+ - Head Injury Criterion (HIC), for example HIC15 and HIC36, via `crashmetrics.pedestrian.calculate_hic`
10
+ - Signal processing in `crashmetrics.signals`: SAE J211 CFC filtering (`cfc_filter`), resultants (`resultant`), unit conversion (`to_g`, `to_seconds`) and uniform resampling (`resample_uniform`)
11
+ - Acceleration metrics in `crashmetrics.acceleration`: peak value and time (`peak`) and the 3 ms clip, cumulative or continuous (`clip_3ms`)
12
+ - Vehicle pulse metrics in `crashmetrics.pulse`: Occupant Load Criterion (`olc`) and a pulse summary with delta-V, time to zero velocity, maximum displacement, rebound and deceleration (`pulse_summary`)
13
+ - Lower-extremity criteria in `crashmetrics.lower_extremity`: Tibia Index (`tibia_index`) and femur compressive force against a force–time limit (`femur_force_criterion`)
14
+ - Generic exceedance helpers in `crashmetrics.acceleration`: `exceedance_level` and `exceedance_duration`
15
+ - Energy and force–displacement metrics in `crashmetrics.energy`: velocity and displacement from acceleration (`integrate_acceleration`), cumulative absorbed energy (`absorbed_energy`), crush summary with peak and mean force, crush force efficiency and specific energy absorption (`crush_metrics`), and `kinetic_energy`
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ python -m pip install crashmetrics
21
+ ```
22
+
23
+ To get the latest unreleased changes, install from GitHub instead:
24
+
25
+ ```bash
26
+ python -m pip install "git+https://github.com/vsk1729/crashmetrics.git"
27
+ ```
28
+
29
+ Requires Python 3.10 or later and NumPy 1.24 or later.
30
+
31
+ ## Example
32
+
33
+ ```python
34
+ from crashmetrics.pedestrian import calculate_hic
35
+
36
+ time = [0.0, 0.005, 0.010, 0.015]
37
+ acceleration = [10.0, 10.0, 10.0, 10.0]
38
+
39
+ result = calculate_hic(
40
+ time,
41
+ acceleration,
42
+ window_ms=15.0,
43
+ )
44
+
45
+ print(result.value)
46
+ print(result.duration_ms)
47
+ ```
48
+
49
+ ### From LS-DYNA output to HIC15
50
+
51
+ For a headform model in the mm-ms-kg unit system, with axis
52
+ accelerations `ax`, `ay`, `az` in mm/ms² and `time_ms` in ms
53
+ (uniformly sampled):
54
+
55
+ ```python
56
+ from crashmetrics.pedestrian import calculate_hic
57
+ from crashmetrics.signals import cfc_filter, resultant, to_g, to_seconds
58
+
59
+ t = to_seconds(time_ms, "ms")
60
+
61
+ # Filter each component with CFC 1000, then form the resultant.
62
+ components_g = [
63
+ cfc_filter(t, to_g(a, "mm/ms^2"), cfc=1000) for a in (ax, ay, az)
64
+ ]
65
+ a_res = resultant(*components_g)
66
+
67
+ hic15 = calculate_hic(t, a_res, window_ms=15.0)
68
+ print(f"HIC15 = {hic15.value:.0f} over {hic15.duration_ms:.2f} ms")
69
+ ```
70
+
71
+ The same filtered resultant gives the peak and the 3 ms clip:
72
+
73
+ ```python
74
+ from crashmetrics.acceleration import clip_3ms, peak
75
+
76
+ a_peak = peak(t, a_res)
77
+ print(f"Peak = {a_peak.value:.1f} g at {a_peak.time * 1000:.2f} ms")
78
+ print(f"3 ms clip = {clip_3ms(t, a_res):.1f} g")
79
+ ```
80
+
81
+ If the output interval is not uniform, use
82
+ `resample_uniform(t, a, sample_rate)` before filtering.
83
+
84
+ ### Energy and crush metrics
85
+
86
+ For a crash box or rail with force in kN and crush in mm (energy in J):
87
+
88
+ ```python
89
+ from crashmetrics.energy import absorbed_energy, crush_metrics
90
+
91
+ energy_curve = absorbed_energy(crush_mm, force_kN) # J at each sample
92
+
93
+ result = crush_metrics(crush_mm, force_kN, mass=1.2, max_displacement=150.0)
94
+ print(f"Energy {result.energy:.0f} J, mean force {result.mean_force:.1f} kN")
95
+ print(f"Peak {result.peak_force:.1f} kN at {result.peak_force_displacement:.1f} mm")
96
+ print(f"CFE {result.crush_force_efficiency:.2f}, SEA {result.specific_energy_absorption:.0f} J/kg")
97
+ ```
98
+
99
+ `max_displacement` evaluates the metrics over a fixed stroke. Mean
100
+ force is net energy divided by crush distance and CFE is mean force
101
+ divided by peak force, so always report the stroke with them.
102
+
103
+ From a vehicle or impactor acceleration pulse (mm/ms² and ms give
104
+ m/s and mm):
105
+
106
+ ```python
107
+ from crashmetrics.energy import integrate_acceleration, kinetic_energy
108
+
109
+ velocity, displacement = integrate_acceleration(time_ms, accel_mm_ms2, v0=15.6)
110
+ print(f"Dynamic crush {displacement.max():.0f} mm")
111
+ print(f"Initial kinetic energy {kinetic_energy(1200.0, 15.6) / 1000:.1f} kJ")
112
+ ```
113
+
114
+ The energy functions do not convert units; use a consistent system.
115
+ Integration amplifies sensor offset and drift, so check the input
116
+ pulse before integrating test data.
117
+
118
+ ### Occupant Load Criterion and pulse summary
119
+
120
+ Inputs are SI (s, m/s², m/s). The acceleration is the vehicle's
121
+ longitudinal acceleration in its direction of travel, so it is
122
+ negative while decelerating, and the record starts at T0:
123
+
124
+ ```python
125
+ from crashmetrics.pulse import olc, pulse_summary
126
+ from crashmetrics.signals import cfc_filter
127
+
128
+ a = cfc_filter(t, accel_x_m_s2, cfc=180) # Euro NCAP uses CFC 180 for OLC
129
+ result = olc(t, a, v0=50 / 3.6)
130
+ print(f"OLC = {result.value:.1f} g (t1 = {result.t1*1000:.1f} ms, t2 = {result.t2*1000:.1f} ms)")
131
+
132
+ summary = pulse_summary(t, a, v0=50 / 3.6)
133
+ print(f"delta-V {summary.delta_v:.2f} m/s, max displacement {summary.max_displacement*1000:.0f} mm")
134
+ ```
135
+
136
+ `olc` follows the Euro NCAP definition (Data Acquisition and Injury
137
+ Calculation protocol): a virtual occupant moves 65 mm in free flight,
138
+ then decelerates uniformly over a further 235 mm until it matches the
139
+ vehicle velocity. The OLC is that deceleration, expressed in g using
140
+ 9.81 m/s² as Euro NCAP does (change with `gravity`). The tests check it
141
+ against the closed-form solution for constant deceleration and against
142
+ an independent numerical formulation.
143
+
144
+ ### Tibia Index and femur force
145
+
146
+ Forces in N, moments in N m, compression negative by default (Euro
147
+ NCAP convention):
148
+
149
+ ```python
150
+ from crashmetrics.lower_extremity import femur_force_criterion, tibia_index
151
+
152
+ ti = tibia_index(t, mx_upper, my_upper, fz_upper, dummy="hybrid3_50")
153
+ print(f"TI upper = {ti.value:.2f}")
154
+
155
+ femur = femur_force_criterion(t, fz_femur, limit="r94_hybrid3_50")
156
+ print(f"Peak {femur.peak_compression/1000:.2f} kN, utilisation {femur.utilisation:.2f}, passed {femur.passed}")
157
+ ```
158
+
159
+ - `tibia_index`: `TI = |MR/MRc| + |Fz/Fzc|` while the tibia is in
160
+ compression, zero otherwise, with `MR = sqrt(Mx² + My²)`. Critical
161
+ values for Hybrid III 50th, 5th, 95th and THOR are taken from Euro
162
+ NCAP CP 005 (`TIBIA_INDEX_CRITICAL_VALUES`); you can also pass your
163
+ own.
164
+ - `femur_force_criterion`: builds the exceedance curve (force reached
165
+ or exceeded versus duration) and compares it with a force–time limit.
166
+ `"r94_hybrid3_50"` is the UN-ECE R94 limit of 9.07 kN at 0 ms falling
167
+ to 7.58 kN at 10 ms and longer; you can pass your own points. The
168
+ default `mode="cumulative"` adds up separate intervals; use
169
+ `mode="continuous"` if your regulation requires a single interval.
170
+
171
+ Check all constants against the protocol version you assess to.
172
+
173
+ ### 3 ms clip
174
+
175
+ `clip_3ms` returns the highest level that the signal reaches or
176
+ exceeds for 3 ms (change with `duration_ms`):
177
+
178
+ - `mode="cumulative"` (default): 3 ms in total, possibly over several
179
+ separate intervals. Computed exactly for the linearly interpolated
180
+ signal.
181
+ - `mode="continuous"`: 3 ms in a single interval. Never higher than
182
+ the cumulative value.
183
+
184
+ Check which definition your assessment protocol requires.
185
+
186
+ ### CFC filter
187
+
188
+ `cfc_filter` implements the SAE J211-1 Appendix C digital filter: a
189
+ 2-pole Butterworth low-pass run forward and backward, giving a
190
+ phaseless 4-pole response. The tests check the gain against the J211
191
+ corridor points for CFC 60, 180, 600 and 1000 (flat to the CFC
192
+ frequency, about −3 dB at 1.65 × CFC). The sample rate must exceed
193
+ 2 × 2.0775 × CFC (about 4.2 kHz for CFC 1000).
194
+
195
+ Each end of the signal is padded by odd reflection (`pad_ms`, default
196
+ 10 ms) to reduce start-up transients. This padding choice is part of
197
+ this package, not of J211; confirm it matches your protocol.
198
+
199
+ ## HIC units and numerical method
200
+
201
+ - Time input: seconds
202
+ - Acceleration input: non-negative resultant magnitude in g
203
+ - HIC window: milliseconds
204
+ - Numerical integration: trapezoidal rule on a refined, linearly interpolated time grid
205
+
206
+ Results depend on the selected grid resolution and the numerical method.
207
+
208
+ If the record is shorter than `window_ms`, `calculate_hic` issues a `UserWarning` and evaluates HIC over the available duration. Make sure the record contains the whole impact.
209
+
210
+ ## Development
211
+
212
+ Clone the repository and install it in editable mode with the
213
+ development tools (pytest, ruff, mypy, build):
214
+
215
+ ```bash
216
+ git clone https://github.com/vsk1729/crashmetrics.git
217
+ cd crashmetrics
218
+ python -m pip install -e ".[dev]"
219
+ ```
220
+
221
+ Run the same checks as CI before opening a pull request:
222
+
223
+ ```bash
224
+ python -m pytest # tests
225
+ ruff check . # lint
226
+ mypy # type check
227
+ ```
228
+
229
+ `examples/hic_convergence.py` shows how the HIC result changes with
230
+ `resolution_ms`; run it after changing the HIC algorithm.
231
+
232
+ The version is set only in `src/crashmetrics/__init__.py`. Record
233
+ user-visible changes in [CHANGELOG.md](CHANGELOG.md).
234
+
235
+ ### Releasing
236
+
237
+ Releases are published by `.github/workflows/release.yml` using PyPI
238
+ Trusted Publishing, so no API tokens are stored in the repository.
239
+
240
+ 1. Bump `__version__` and add the CHANGELOG entry; merge to `main`.
241
+ 2. Optional dry run: run the **Release** workflow manually from the
242
+ Actions tab to publish to TestPyPI.
243
+ 3. Publish a GitHub release with tag `v<version>`, for example
244
+ `v0.3.0`. The workflow checks the tag matches `__version__`,
245
+ builds and checks the sdist and wheel, and publishes to PyPI.
246
+
247
+ A version can be uploaded to PyPI only once; fix mistakes with a new
248
+ version.
249
+
250
+ ## Engineering disclaimer
251
+
252
+ This software is an engineering calculation aid. Users must independently validate numerical results and confirm that input processing, filtering, sampling, and calculation methods match the applicable assessment protocol. The software does not certify regulatory compliance.
253
+
254
+ ## License
255
+
256
+ MIT. See the LICENSE file.
@@ -0,0 +1,74 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "crashmetrics"
7
+ dynamic = ["version"]
8
+ description = "Python tools for automotive crash engineering metrics"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Voonna Satish Kumar" }]
14
+ dependencies = [
15
+ "numpy>=1.24"
16
+ ]
17
+ keywords = [
18
+ "crashworthiness",
19
+ "LS-DYNA",
20
+ "CAE",
21
+ "HIC",
22
+ "automotive-safety"
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 3 - Alpha",
26
+ "Intended Audience :: Science/Research",
27
+ "Intended Audience :: Manufacturing",
28
+ "Operating System :: OS Independent",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Typing :: Typed",
35
+ "Topic :: Scientific/Engineering"
36
+ ]
37
+
38
+ [project.optional-dependencies]
39
+ dev = [
40
+ "pytest>=8",
41
+ "build",
42
+ "twine",
43
+ "mypy",
44
+ "ruff"
45
+ ]
46
+
47
+ [project.urls]
48
+ Homepage = "https://github.com/vsk1729/crashmetrics"
49
+ Source = "https://github.com/vsk1729/crashmetrics"
50
+ Issues = "https://github.com/vsk1729/crashmetrics/issues"
51
+ Changelog = "https://github.com/vsk1729/crashmetrics/blob/main/CHANGELOG.md"
52
+
53
+ [tool.ruff]
54
+ line-length = 88
55
+ target-version = "py310"
56
+
57
+ [tool.ruff.lint]
58
+ select = ["E", "F", "W", "I"]
59
+
60
+ [tool.setuptools.dynamic]
61
+ version = { attr = "crashmetrics.__version__" }
62
+
63
+ [tool.setuptools.packages.find]
64
+ where = ["src"]
65
+
66
+ [tool.setuptools.package-data]
67
+ crashmetrics = ["py.typed"]
68
+
69
+ [tool.mypy]
70
+ python_version = "3.10"
71
+ files = ["src"]
72
+
73
+ [tool.pytest.ini_options]
74
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ """Python tools for automotive crash engineering.
2
+
3
+ Modules
4
+ -------
5
+ crashmetrics.signals
6
+ CFC filtering, resultants, unit conversion and resampling.
7
+ crashmetrics.pedestrian
8
+ Head Injury Criterion (HIC).
9
+ crashmetrics.acceleration
10
+ Peak acceleration and the 3 ms clip.
11
+ crashmetrics.energy
12
+ Energy and force-displacement metrics.
13
+ crashmetrics.pulse
14
+ Occupant Load Criterion and crash pulse summary.
15
+ crashmetrics.lower_extremity
16
+ Tibia Index and femur force criterion.
17
+ """
18
+
19
+ # Single source of the package version; pyproject.toml reads it.
20
+ __version__ = "0.3.0"