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.
- crashmetrics-0.3.0/LICENSE +21 -0
- crashmetrics-0.3.0/PKG-INFO +290 -0
- crashmetrics-0.3.0/README.md +256 -0
- crashmetrics-0.3.0/pyproject.toml +74 -0
- crashmetrics-0.3.0/setup.cfg +4 -0
- crashmetrics-0.3.0/src/crashmetrics/__init__.py +20 -0
- crashmetrics-0.3.0/src/crashmetrics/acceleration.py +327 -0
- crashmetrics-0.3.0/src/crashmetrics/energy.py +261 -0
- crashmetrics-0.3.0/src/crashmetrics/lower_extremity.py +327 -0
- crashmetrics-0.3.0/src/crashmetrics/pedestrian/__init__.py +5 -0
- crashmetrics-0.3.0/src/crashmetrics/pedestrian/hic.py +205 -0
- crashmetrics-0.3.0/src/crashmetrics/pulse.py +318 -0
- crashmetrics-0.3.0/src/crashmetrics/py.typed +0 -0
- crashmetrics-0.3.0/src/crashmetrics/signals.py +336 -0
- crashmetrics-0.3.0/src/crashmetrics.egg-info/PKG-INFO +290 -0
- crashmetrics-0.3.0/src/crashmetrics.egg-info/SOURCES.txt +25 -0
- crashmetrics-0.3.0/src/crashmetrics.egg-info/dependency_links.txt +1 -0
- crashmetrics-0.3.0/src/crashmetrics.egg-info/requires.txt +8 -0
- crashmetrics-0.3.0/src/crashmetrics.egg-info/top_level.txt +1 -0
- crashmetrics-0.3.0/tests/test_acceleration.py +179 -0
- crashmetrics-0.3.0/tests/test_energy.py +189 -0
- crashmetrics-0.3.0/tests/test_hic.py +196 -0
- crashmetrics-0.3.0/tests/test_hic_reference.py +78 -0
- crashmetrics-0.3.0/tests/test_lower_extremity.py +208 -0
- crashmetrics-0.3.0/tests/test_package.py +8 -0
- crashmetrics-0.3.0/tests/test_pulse.py +183 -0
- crashmetrics-0.3.0/tests/test_signals.py +194 -0
|
@@ -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,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"
|