timoshenko-engine 2.0.1__tar.gz → 2.0.3__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.
- timoshenko_engine-2.0.3/PKG-INFO +257 -0
- timoshenko_engine-2.0.3/README.md +219 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/pyproject.toml +29 -3
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/__init__.py +1 -1
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/adapters.py +2 -2
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/modal.py +3 -4
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/multichannel.py +3 -3
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/oma.py +1 -1
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/project.py +1 -0
- timoshenko_engine-2.0.3/src/timoshenko/py.typed +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/sections.py +0 -1
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/sensors.py +1 -1
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/session.py +7 -5
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/storage.py +4 -3
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/uncertainty.py +4 -4
- timoshenko_engine-2.0.3/src/timoshenko_engine.egg-info/PKG-INFO +257 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/SOURCES.txt +1 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/requires.txt +5 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/tests/test_package.py +20 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/tests/test_project_report.py +16 -1
- timoshenko_engine-2.0.1/PKG-INFO +0 -237
- timoshenko_engine-2.0.1/README.md +0 -205
- timoshenko_engine-2.0.1/src/timoshenko_engine.egg-info/PKG-INFO +0 -237
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/LICENSE +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/setup.cfg +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/_validation.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/assets.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/beams.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/csv_source.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/health.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/mechanics.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/monitor.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/mqtt.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/observations.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/plugins.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/polygon.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/pressure.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/report.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/sensorthings.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/shafts.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/stability.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/strength.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/structure.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/update.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko/vibration.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/dependency_links.txt +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/entry_points.txt +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/top_level.txt +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/tests/test_adapters.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/tests/test_formulas.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/tests/test_modal.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/tests/test_polygon.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/tests/test_storage_session.py +0 -0
- {timoshenko_engine-2.0.1 → timoshenko_engine-2.0.3}/tests/test_uncertainty.py +0 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: timoshenko-engine
|
|
3
|
+
Version: 2.0.3
|
|
4
|
+
Summary: Composable structural engineering calculations and analysis primitives for Python applications.
|
|
5
|
+
Author: Ayberk Korkmaz
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/Ayberkrk/timoshenko
|
|
8
|
+
Project-URL: Repository, https://github.com/Ayberkrk/timoshenko
|
|
9
|
+
Project-URL: Issues, https://github.com/Ayberkrk/timoshenko/issues
|
|
10
|
+
Project-URL: Documentation, https://timoshenko.readthedocs.io/
|
|
11
|
+
Project-URL: Changelog, https://github.com/Ayberkrk/timoshenko/blob/main/docs/changelog.md
|
|
12
|
+
Keywords: civil engineering,structural engineering,structural health monitoring,modal analysis,digital twin
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
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: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy>=1.24
|
|
27
|
+
Provides-Extra: mqtt
|
|
28
|
+
Requires-Dist: paho-mqtt<3,>=2.1; extra == "mqtt"
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-cov>=5; extra == "test"
|
|
32
|
+
Provides-Extra: lint
|
|
33
|
+
Requires-Dist: ruff>=0.6; extra == "lint"
|
|
34
|
+
Requires-Dist: mypy>=1.10; extra == "lint"
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: mkdocs-material<10,>=9.5; extra == "docs"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img src="https://raw.githubusercontent.com/Ayberkrk/timoshenko/main/assets/timoshenko-logo.png" alt="Timoshenko Engine logo" width="920">
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<h1 align="center">Timoshenko Engine</h1>
|
|
44
|
+
|
|
45
|
+
<p align="center">
|
|
46
|
+
Reusable structural engineering building blocks for Python applications.
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<p align="center">
|
|
50
|
+
<a href="https://pypi.org/project/timoshenko-engine/"><img alt="PyPI" src="https://img.shields.io/pypi/v/timoshenko-engine?style=for-the-badge"></a>
|
|
51
|
+
<img alt="Python versions" src="https://img.shields.io/pypi/pyversions/timoshenko-engine?style=for-the-badge">
|
|
52
|
+
<img alt="Alpha" src="https://img.shields.io/badge/stage-alpha-orange?style=for-the-badge">
|
|
53
|
+
<img alt="Apache 2.0 license" src="https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge">
|
|
54
|
+
<a href="https://doi.org/10.5281/zenodo.22968739"><img alt="DOI" src="https://img.shields.io/badge/DOI-10.5281%2Fzenodo.22968739-blue?style=for-the-badge"></a>
|
|
55
|
+
<a href="https://github.com/Ayberkrk/timoshenko/actions/workflows/tests.yml"><img alt="Tests" src="https://github.com/Ayberkrk/timoshenko/actions/workflows/tests.yml/badge.svg"></a>
|
|
56
|
+
<a href="https://github.com/Ayberkrk/timoshenko/tree/python-coverage-comment-action-data"><img alt="Coverage" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FAyberkrk%2Ftimoshenko%2Fpython-coverage-comment-action-data%2Fendpoint.json&style=for-the-badge"></a>
|
|
57
|
+
<a href="https://timoshenko.readthedocs.io/en/latest/"><img alt="Documentation" src="https://img.shields.io/readthedocs/timoshenko?style=for-the-badge"></a>
|
|
58
|
+
</p>
|
|
59
|
+
|
|
60
|
+
Timoshenko packages common structural calculations, modal analysis, sensor
|
|
61
|
+
workflows, and monitoring components so applications can reuse them instead
|
|
62
|
+
of rebuilding the same foundations for every project. It is an embeddable
|
|
63
|
+
Python library, not a hosted monitoring service or a general finite-element
|
|
64
|
+
solver.
|
|
65
|
+
|
|
66
|
+
> **Alpha:** the API may still change between releases. Every behavior change
|
|
67
|
+
> is listed in the [changelog](https://timoshenko.readthedocs.io/en/latest/changelog/).
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python -m pip install timoshenko-engine
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The distribution is named `timoshenko-engine`; the import package is
|
|
76
|
+
`timoshenko`. Optional MQTT support is an extra:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
python -m pip install "timoshenko-engine[mqtt]"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quick start
|
|
83
|
+
|
|
84
|
+
Compare measured vibration with a reference model. This example simulates a
|
|
85
|
+
record in which both modes are 5% below the model:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import numpy as np
|
|
89
|
+
import timoshenko as tm
|
|
90
|
+
|
|
91
|
+
structure = tm.Structure(
|
|
92
|
+
structure_id="building-01",
|
|
93
|
+
story_masses_kg=[120_000.0, 110_000.0],
|
|
94
|
+
story_stiffness_n_m=[85_000_000.0, 70_000_000.0],
|
|
95
|
+
)
|
|
96
|
+
print(structure.natural_frequencies_hz) # (2.626, 6.476)
|
|
97
|
+
|
|
98
|
+
fs = 100.0
|
|
99
|
+
t = np.arange(60_000) / fs
|
|
100
|
+
f1, f2 = (0.95 * f for f in structure.natural_frequencies_hz)
|
|
101
|
+
signal = np.sin(2 * np.pi * f1 * t) + 0.4 * np.sin(2 * np.pi * f2 * t)
|
|
102
|
+
sensors = tm.SensorData(signal, sampling_hz=fs, unit="m/s^2")
|
|
103
|
+
|
|
104
|
+
result = tm.monitor(structure, sensors, review_threshold_pct=3.0)
|
|
105
|
+
print(result.structure.update_scale_factor) # 0.903, since stiffness scales with frequency squared
|
|
106
|
+
for change in result.health.mode_changes:
|
|
107
|
+
print(change.mode_number, change.change_pct) # 1 -5.0, then 2 -5.0
|
|
108
|
+
print(result.health.review_recommended) # True
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
<p align="center">
|
|
112
|
+
<img src="https://raw.githubusercontent.com/Ayberkrk/timoshenko/main/assets/modal-comparison.svg" alt="Both identified modes plot 5% below the reference model, past the 3% review threshold" width="800">
|
|
113
|
+
</p>
|
|
114
|
+
|
|
115
|
+
Real records load from CSV or JSON with
|
|
116
|
+
`tm.load_sensors("acceleration.csv", sampling_hz=100.0, column="acc")`. The
|
|
117
|
+
steps inside `tm.monitor` are also available separately as `tm.modal.identify`,
|
|
118
|
+
`tm.update`, and `tm.health.assess`. The review flag is raised only when you
|
|
119
|
+
pass a threshold: a meaningful value depends on the structure and its
|
|
120
|
+
environmental variability, so the engine does not choose one.
|
|
121
|
+
|
|
122
|
+
## Engineering calculations
|
|
123
|
+
|
|
124
|
+
Closed-form functions take and return SI units and validate their inputs:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
import timoshenko as tm
|
|
128
|
+
|
|
129
|
+
section = tm.rectangle_section(width_m=0.3, height_m=0.6)
|
|
130
|
+
beam = tm.simply_supported_uniform_load(
|
|
131
|
+
20_000.0, 6.0, 30e9, section.second_moment_y_m4,
|
|
132
|
+
shear_modulus_pa=12.5e9, area_m2=section.area_m2,
|
|
133
|
+
)
|
|
134
|
+
print(beam.bending_m, beam.shear_m) # 2.083 mm bending, 0.048 mm shear
|
|
135
|
+
|
|
136
|
+
frequency = tm.propagate_uncertainty(
|
|
137
|
+
tm.natural_frequency_hz,
|
|
138
|
+
{"mass_kg": 250.0, "stiffness_n_m": 4e5},
|
|
139
|
+
standard_uncertainties={"mass_kg": 5.0, "stiffness_n_m": 8e3},
|
|
140
|
+
)
|
|
141
|
+
print(frequency.estimate, frequency.standard_uncertainty) # 6.366 Hz ± 0.090 Hz
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## What it provides
|
|
145
|
+
|
|
146
|
+
| Area | Components |
|
|
147
|
+
|---|---|
|
|
148
|
+
| Engineering calculations | Section properties (including polygons with holes), beam deflection with shear, torsion, Euler buckling, plane stress, thin-wall pressure, SDOF vibration, Rayleigh damping, and GUM / Monte Carlo uncertainty |
|
|
149
|
+
| Structural models | Lumped-mass shear-building models and their natural frequencies |
|
|
150
|
+
| Modal analysis | Single-channel peak picking with resolution-checked damping, and multi-channel FDD with complex mode shapes |
|
|
151
|
+
| Model comparison | Nearest-frequency mode pairing, global stiffness updating, and evidence-oriented health assessment |
|
|
152
|
+
| Monitoring | Bounded rolling-window sessions, restart from local history, and source adapters for CSV, MQTT, and OGC SensorThings |
|
|
153
|
+
| Data and reporting | Project manifests with SHA-256 provenance, local SQLite history, and standalone HTML reports |
|
|
154
|
+
|
|
155
|
+
Sensor collection, alarm policy, and engineering interpretation remain with the
|
|
156
|
+
application that embeds Timoshenko.
|
|
157
|
+
|
|
158
|
+
## Multi-channel modal analysis
|
|
159
|
+
|
|
160
|
+
Frequency domain decomposition needs synchronized channels with a common
|
|
161
|
+
sample rate and unit:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
signals = tm.load_multichannel_csv(
|
|
165
|
+
"aligned_accelerometers.csv",
|
|
166
|
+
columns=["deck_left", "deck_center", "deck_right"],
|
|
167
|
+
sampling_hz=100.0,
|
|
168
|
+
units=["m/s^2"] * 3,
|
|
169
|
+
)
|
|
170
|
+
fdd = tm.identify_fdd(signals, nperseg=1024, max_modes=5)
|
|
171
|
+
for mode in fdd.modes:
|
|
172
|
+
print(mode.frequency_hz, mode.shape_real)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Monitoring sessions
|
|
176
|
+
|
|
177
|
+
A host application feeds timestamped observation batches. The session aligns
|
|
178
|
+
samples to the sample grid, waits for a fresh contiguous window after gaps, and
|
|
179
|
+
analyzes every configured hop:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
session = tm.MonitoringSession(
|
|
183
|
+
structure,
|
|
184
|
+
sensor_ids=["deck-left", "deck-right"],
|
|
185
|
+
units=["m/s^2", "m/s^2"],
|
|
186
|
+
sampling_hz=100.0,
|
|
187
|
+
window_samples=2048,
|
|
188
|
+
hop_samples=512,
|
|
189
|
+
analysis_options={"nperseg": 512, "max_modes": 4},
|
|
190
|
+
review_threshold_pct=5.0,
|
|
191
|
+
)
|
|
192
|
+
result = session.ingest(batch) # batch: tm.ObservationBatch from your gateway or a source adapter
|
|
193
|
+
for report in result.reports:
|
|
194
|
+
tm.report.save_html(report, "reports/latest.html")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The session does not open network connections or run in the background. Use
|
|
198
|
+
`tm.SessionRunner` with a source such as `tm.CSVObservationSource` or
|
|
199
|
+
`tm.MqttObservationSource` to drive it.
|
|
200
|
+
|
|
201
|
+
## Documentation
|
|
202
|
+
|
|
203
|
+
The full documentation is at
|
|
204
|
+
[timoshenko.readthedocs.io](https://timoshenko.readthedocs.io/en/latest/). Good places to start:
|
|
205
|
+
|
|
206
|
+
- [API reference](https://timoshenko.readthedocs.io/en/latest/api/)
|
|
207
|
+
- [Numerical methods and limits](https://timoshenko.readthedocs.io/en/latest/numerical-methods/)
|
|
208
|
+
- [Architecture](https://timoshenko.readthedocs.io/en/latest/architecture/)
|
|
209
|
+
- [Monitoring sessions](https://timoshenko.readthedocs.io/en/latest/live-sessions/) and [source adapters](https://timoshenko.readthedocs.io/en/latest/adapters/)
|
|
210
|
+
- [Runnable examples](https://github.com/Ayberkrk/timoshenko/tree/main/examples), including a [cross-check of PyNite shear-deformable beams](https://timoshenko.readthedocs.io/en/latest/pynite-verification/)
|
|
211
|
+
- [Changelog](https://timoshenko.readthedocs.io/en/latest/changelog/)
|
|
212
|
+
|
|
213
|
+
## Limits and engineering posture
|
|
214
|
+
|
|
215
|
+
- Timoshenko is alpha software and is not a structural safety certification tool.
|
|
216
|
+
- The shear-building model is a small lumped-mass reference model, not a full FEM solver.
|
|
217
|
+
- Modal identification is a screening estimate. A single sensor may miss a mode near a modal node; unpaired peaks are reported rather than compared with the wrong mode.
|
|
218
|
+
- Damping is a coarse screening estimate, reported only when the averaged spectrum resolves the half-power bandwidth. FDD does not estimate damping.
|
|
219
|
+
- The model update applies a single stiffness scale. It cannot locate or size local damage.
|
|
220
|
+
- A frequency shift is evidence for human review, not a damage verdict. Temperature, sensor placement, boundary conditions, and other effects also shift measured frequencies.
|
|
221
|
+
- Closed-form mechanics and section functions rely on their documented ideal assumptions. They are not code-compliance checks.
|
|
222
|
+
|
|
223
|
+
## Development
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
python -m pip install -e ".[test,lint]"
|
|
227
|
+
python -m pytest --cov
|
|
228
|
+
python -m ruff check .
|
|
229
|
+
python -m mypy
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
To preview the documentation site:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
python -m pip install -e ".[docs]"
|
|
236
|
+
python -m mkdocs serve
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Bug reports and pull requests are welcome in the
|
|
240
|
+
[issue tracker](https://github.com/Ayberkrk/timoshenko/issues). See
|
|
241
|
+
[`CONTRIBUTING.md`](https://github.com/Ayberkrk/timoshenko/blob/main/CONTRIBUTING.md)
|
|
242
|
+
for the checks a pull request needs to pass; issues labelled
|
|
243
|
+
[good first issue](https://github.com/Ayberkrk/timoshenko/labels/good%20first%20issue)
|
|
244
|
+
are small, self-contained starting points.
|
|
245
|
+
|
|
246
|
+
## Citation
|
|
247
|
+
|
|
248
|
+
If you use Timoshenko in research, please cite it with its DOI,
|
|
249
|
+
[10.5281/zenodo.22968739](https://doi.org/10.5281/zenodo.22968739), which
|
|
250
|
+
always resolves to the latest release; each release also has its own DOI on
|
|
251
|
+
[Zenodo](https://zenodo.org/records/22968740). The metadata is in
|
|
252
|
+
[`CITATION.cff`](https://github.com/Ayberkrk/timoshenko/blob/main/CITATION.cff),
|
|
253
|
+
which GitHub's "Cite this repository" button reads.
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
Apache License 2.0. See [LICENSE](https://github.com/Ayberkrk/timoshenko/blob/main/LICENSE).
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Ayberkrk/timoshenko/main/assets/timoshenko-logo.png" alt="Timoshenko Engine logo" width="920">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Timoshenko Engine</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
Reusable structural engineering building blocks for Python applications.
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://pypi.org/project/timoshenko-engine/"><img alt="PyPI" src="https://img.shields.io/pypi/v/timoshenko-engine?style=for-the-badge"></a>
|
|
13
|
+
<img alt="Python versions" src="https://img.shields.io/pypi/pyversions/timoshenko-engine?style=for-the-badge">
|
|
14
|
+
<img alt="Alpha" src="https://img.shields.io/badge/stage-alpha-orange?style=for-the-badge">
|
|
15
|
+
<img alt="Apache 2.0 license" src="https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge">
|
|
16
|
+
<a href="https://doi.org/10.5281/zenodo.22968739"><img alt="DOI" src="https://img.shields.io/badge/DOI-10.5281%2Fzenodo.22968739-blue?style=for-the-badge"></a>
|
|
17
|
+
<a href="https://github.com/Ayberkrk/timoshenko/actions/workflows/tests.yml"><img alt="Tests" src="https://github.com/Ayberkrk/timoshenko/actions/workflows/tests.yml/badge.svg"></a>
|
|
18
|
+
<a href="https://github.com/Ayberkrk/timoshenko/tree/python-coverage-comment-action-data"><img alt="Coverage" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FAyberkrk%2Ftimoshenko%2Fpython-coverage-comment-action-data%2Fendpoint.json&style=for-the-badge"></a>
|
|
19
|
+
<a href="https://timoshenko.readthedocs.io/en/latest/"><img alt="Documentation" src="https://img.shields.io/readthedocs/timoshenko?style=for-the-badge"></a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
Timoshenko packages common structural calculations, modal analysis, sensor
|
|
23
|
+
workflows, and monitoring components so applications can reuse them instead
|
|
24
|
+
of rebuilding the same foundations for every project. It is an embeddable
|
|
25
|
+
Python library, not a hosted monitoring service or a general finite-element
|
|
26
|
+
solver.
|
|
27
|
+
|
|
28
|
+
> **Alpha:** the API may still change between releases. Every behavior change
|
|
29
|
+
> is listed in the [changelog](https://timoshenko.readthedocs.io/en/latest/changelog/).
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
python -m pip install timoshenko-engine
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The distribution is named `timoshenko-engine`; the import package is
|
|
38
|
+
`timoshenko`. Optional MQTT support is an extra:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m pip install "timoshenko-engine[mqtt]"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quick start
|
|
45
|
+
|
|
46
|
+
Compare measured vibration with a reference model. This example simulates a
|
|
47
|
+
record in which both modes are 5% below the model:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import numpy as np
|
|
51
|
+
import timoshenko as tm
|
|
52
|
+
|
|
53
|
+
structure = tm.Structure(
|
|
54
|
+
structure_id="building-01",
|
|
55
|
+
story_masses_kg=[120_000.0, 110_000.0],
|
|
56
|
+
story_stiffness_n_m=[85_000_000.0, 70_000_000.0],
|
|
57
|
+
)
|
|
58
|
+
print(structure.natural_frequencies_hz) # (2.626, 6.476)
|
|
59
|
+
|
|
60
|
+
fs = 100.0
|
|
61
|
+
t = np.arange(60_000) / fs
|
|
62
|
+
f1, f2 = (0.95 * f for f in structure.natural_frequencies_hz)
|
|
63
|
+
signal = np.sin(2 * np.pi * f1 * t) + 0.4 * np.sin(2 * np.pi * f2 * t)
|
|
64
|
+
sensors = tm.SensorData(signal, sampling_hz=fs, unit="m/s^2")
|
|
65
|
+
|
|
66
|
+
result = tm.monitor(structure, sensors, review_threshold_pct=3.0)
|
|
67
|
+
print(result.structure.update_scale_factor) # 0.903, since stiffness scales with frequency squared
|
|
68
|
+
for change in result.health.mode_changes:
|
|
69
|
+
print(change.mode_number, change.change_pct) # 1 -5.0, then 2 -5.0
|
|
70
|
+
print(result.health.review_recommended) # True
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
<p align="center">
|
|
74
|
+
<img src="https://raw.githubusercontent.com/Ayberkrk/timoshenko/main/assets/modal-comparison.svg" alt="Both identified modes plot 5% below the reference model, past the 3% review threshold" width="800">
|
|
75
|
+
</p>
|
|
76
|
+
|
|
77
|
+
Real records load from CSV or JSON with
|
|
78
|
+
`tm.load_sensors("acceleration.csv", sampling_hz=100.0, column="acc")`. The
|
|
79
|
+
steps inside `tm.monitor` are also available separately as `tm.modal.identify`,
|
|
80
|
+
`tm.update`, and `tm.health.assess`. The review flag is raised only when you
|
|
81
|
+
pass a threshold: a meaningful value depends on the structure and its
|
|
82
|
+
environmental variability, so the engine does not choose one.
|
|
83
|
+
|
|
84
|
+
## Engineering calculations
|
|
85
|
+
|
|
86
|
+
Closed-form functions take and return SI units and validate their inputs:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import timoshenko as tm
|
|
90
|
+
|
|
91
|
+
section = tm.rectangle_section(width_m=0.3, height_m=0.6)
|
|
92
|
+
beam = tm.simply_supported_uniform_load(
|
|
93
|
+
20_000.0, 6.0, 30e9, section.second_moment_y_m4,
|
|
94
|
+
shear_modulus_pa=12.5e9, area_m2=section.area_m2,
|
|
95
|
+
)
|
|
96
|
+
print(beam.bending_m, beam.shear_m) # 2.083 mm bending, 0.048 mm shear
|
|
97
|
+
|
|
98
|
+
frequency = tm.propagate_uncertainty(
|
|
99
|
+
tm.natural_frequency_hz,
|
|
100
|
+
{"mass_kg": 250.0, "stiffness_n_m": 4e5},
|
|
101
|
+
standard_uncertainties={"mass_kg": 5.0, "stiffness_n_m": 8e3},
|
|
102
|
+
)
|
|
103
|
+
print(frequency.estimate, frequency.standard_uncertainty) # 6.366 Hz ± 0.090 Hz
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## What it provides
|
|
107
|
+
|
|
108
|
+
| Area | Components |
|
|
109
|
+
|---|---|
|
|
110
|
+
| Engineering calculations | Section properties (including polygons with holes), beam deflection with shear, torsion, Euler buckling, plane stress, thin-wall pressure, SDOF vibration, Rayleigh damping, and GUM / Monte Carlo uncertainty |
|
|
111
|
+
| Structural models | Lumped-mass shear-building models and their natural frequencies |
|
|
112
|
+
| Modal analysis | Single-channel peak picking with resolution-checked damping, and multi-channel FDD with complex mode shapes |
|
|
113
|
+
| Model comparison | Nearest-frequency mode pairing, global stiffness updating, and evidence-oriented health assessment |
|
|
114
|
+
| Monitoring | Bounded rolling-window sessions, restart from local history, and source adapters for CSV, MQTT, and OGC SensorThings |
|
|
115
|
+
| Data and reporting | Project manifests with SHA-256 provenance, local SQLite history, and standalone HTML reports |
|
|
116
|
+
|
|
117
|
+
Sensor collection, alarm policy, and engineering interpretation remain with the
|
|
118
|
+
application that embeds Timoshenko.
|
|
119
|
+
|
|
120
|
+
## Multi-channel modal analysis
|
|
121
|
+
|
|
122
|
+
Frequency domain decomposition needs synchronized channels with a common
|
|
123
|
+
sample rate and unit:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
signals = tm.load_multichannel_csv(
|
|
127
|
+
"aligned_accelerometers.csv",
|
|
128
|
+
columns=["deck_left", "deck_center", "deck_right"],
|
|
129
|
+
sampling_hz=100.0,
|
|
130
|
+
units=["m/s^2"] * 3,
|
|
131
|
+
)
|
|
132
|
+
fdd = tm.identify_fdd(signals, nperseg=1024, max_modes=5)
|
|
133
|
+
for mode in fdd.modes:
|
|
134
|
+
print(mode.frequency_hz, mode.shape_real)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Monitoring sessions
|
|
138
|
+
|
|
139
|
+
A host application feeds timestamped observation batches. The session aligns
|
|
140
|
+
samples to the sample grid, waits for a fresh contiguous window after gaps, and
|
|
141
|
+
analyzes every configured hop:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
session = tm.MonitoringSession(
|
|
145
|
+
structure,
|
|
146
|
+
sensor_ids=["deck-left", "deck-right"],
|
|
147
|
+
units=["m/s^2", "m/s^2"],
|
|
148
|
+
sampling_hz=100.0,
|
|
149
|
+
window_samples=2048,
|
|
150
|
+
hop_samples=512,
|
|
151
|
+
analysis_options={"nperseg": 512, "max_modes": 4},
|
|
152
|
+
review_threshold_pct=5.0,
|
|
153
|
+
)
|
|
154
|
+
result = session.ingest(batch) # batch: tm.ObservationBatch from your gateway or a source adapter
|
|
155
|
+
for report in result.reports:
|
|
156
|
+
tm.report.save_html(report, "reports/latest.html")
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The session does not open network connections or run in the background. Use
|
|
160
|
+
`tm.SessionRunner` with a source such as `tm.CSVObservationSource` or
|
|
161
|
+
`tm.MqttObservationSource` to drive it.
|
|
162
|
+
|
|
163
|
+
## Documentation
|
|
164
|
+
|
|
165
|
+
The full documentation is at
|
|
166
|
+
[timoshenko.readthedocs.io](https://timoshenko.readthedocs.io/en/latest/). Good places to start:
|
|
167
|
+
|
|
168
|
+
- [API reference](https://timoshenko.readthedocs.io/en/latest/api/)
|
|
169
|
+
- [Numerical methods and limits](https://timoshenko.readthedocs.io/en/latest/numerical-methods/)
|
|
170
|
+
- [Architecture](https://timoshenko.readthedocs.io/en/latest/architecture/)
|
|
171
|
+
- [Monitoring sessions](https://timoshenko.readthedocs.io/en/latest/live-sessions/) and [source adapters](https://timoshenko.readthedocs.io/en/latest/adapters/)
|
|
172
|
+
- [Runnable examples](https://github.com/Ayberkrk/timoshenko/tree/main/examples), including a [cross-check of PyNite shear-deformable beams](https://timoshenko.readthedocs.io/en/latest/pynite-verification/)
|
|
173
|
+
- [Changelog](https://timoshenko.readthedocs.io/en/latest/changelog/)
|
|
174
|
+
|
|
175
|
+
## Limits and engineering posture
|
|
176
|
+
|
|
177
|
+
- Timoshenko is alpha software and is not a structural safety certification tool.
|
|
178
|
+
- The shear-building model is a small lumped-mass reference model, not a full FEM solver.
|
|
179
|
+
- Modal identification is a screening estimate. A single sensor may miss a mode near a modal node; unpaired peaks are reported rather than compared with the wrong mode.
|
|
180
|
+
- Damping is a coarse screening estimate, reported only when the averaged spectrum resolves the half-power bandwidth. FDD does not estimate damping.
|
|
181
|
+
- The model update applies a single stiffness scale. It cannot locate or size local damage.
|
|
182
|
+
- A frequency shift is evidence for human review, not a damage verdict. Temperature, sensor placement, boundary conditions, and other effects also shift measured frequencies.
|
|
183
|
+
- Closed-form mechanics and section functions rely on their documented ideal assumptions. They are not code-compliance checks.
|
|
184
|
+
|
|
185
|
+
## Development
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
python -m pip install -e ".[test,lint]"
|
|
189
|
+
python -m pytest --cov
|
|
190
|
+
python -m ruff check .
|
|
191
|
+
python -m mypy
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
To preview the documentation site:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
python -m pip install -e ".[docs]"
|
|
198
|
+
python -m mkdocs serve
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Bug reports and pull requests are welcome in the
|
|
202
|
+
[issue tracker](https://github.com/Ayberkrk/timoshenko/issues). See
|
|
203
|
+
[`CONTRIBUTING.md`](https://github.com/Ayberkrk/timoshenko/blob/main/CONTRIBUTING.md)
|
|
204
|
+
for the checks a pull request needs to pass; issues labelled
|
|
205
|
+
[good first issue](https://github.com/Ayberkrk/timoshenko/labels/good%20first%20issue)
|
|
206
|
+
are small, self-contained starting points.
|
|
207
|
+
|
|
208
|
+
## Citation
|
|
209
|
+
|
|
210
|
+
If you use Timoshenko in research, please cite it with its DOI,
|
|
211
|
+
[10.5281/zenodo.22968739](https://doi.org/10.5281/zenodo.22968739), which
|
|
212
|
+
always resolves to the latest release; each release also has its own DOI on
|
|
213
|
+
[Zenodo](https://zenodo.org/records/22968740). The metadata is in
|
|
214
|
+
[`CITATION.cff`](https://github.com/Ayberkrk/timoshenko/blob/main/CITATION.cff),
|
|
215
|
+
which GitHub's "Cite this repository" button reads.
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
Apache License 2.0. See [LICENSE](https://github.com/Ayberkrk/timoshenko/blob/main/LICENSE).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "timoshenko-engine"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.3"
|
|
8
8
|
description = "Composable structural engineering calculations and analysis primitives for Python applications."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -29,11 +29,13 @@ classifiers = [
|
|
|
29
29
|
"Programming Language :: Python :: 3.12",
|
|
30
30
|
"Programming Language :: Python :: 3.13",
|
|
31
31
|
"Topic :: Scientific/Engineering",
|
|
32
|
+
"Typing :: Typed",
|
|
32
33
|
]
|
|
33
34
|
|
|
34
35
|
[project.optional-dependencies]
|
|
35
36
|
mqtt = ["paho-mqtt>=2.1,<3"]
|
|
36
|
-
test = ["pytest>=7"]
|
|
37
|
+
test = ["pytest>=7", "pytest-cov>=5"]
|
|
38
|
+
lint = ["ruff>=0.6", "mypy>=1.10"]
|
|
37
39
|
docs = ["mkdocs-material>=9.5,<10"]
|
|
38
40
|
|
|
39
41
|
[project.entry-points."timoshenko.plugins"]
|
|
@@ -44,7 +46,8 @@ sensorthings = "timoshenko.sensorthings:SensorThingsPlugin"
|
|
|
44
46
|
Homepage = "https://github.com/Ayberkrk/timoshenko"
|
|
45
47
|
Repository = "https://github.com/Ayberkrk/timoshenko"
|
|
46
48
|
Issues = "https://github.com/Ayberkrk/timoshenko/issues"
|
|
47
|
-
Documentation = "https://
|
|
49
|
+
Documentation = "https://timoshenko.readthedocs.io/"
|
|
50
|
+
Changelog = "https://github.com/Ayberkrk/timoshenko/blob/main/docs/changelog.md"
|
|
48
51
|
|
|
49
52
|
[tool.setuptools]
|
|
50
53
|
package-dir = { "" = "src" }
|
|
@@ -52,6 +55,29 @@ package-dir = { "" = "src" }
|
|
|
52
55
|
[tool.setuptools.packages.find]
|
|
53
56
|
where = ["src"]
|
|
54
57
|
|
|
58
|
+
[tool.setuptools.package-data]
|
|
59
|
+
timoshenko = ["py.typed"]
|
|
60
|
+
|
|
55
61
|
[tool.pytest.ini_options]
|
|
56
62
|
testpaths = ["tests"]
|
|
57
63
|
xfail_strict = true
|
|
64
|
+
|
|
65
|
+
[tool.coverage.run]
|
|
66
|
+
source = ["timoshenko"]
|
|
67
|
+
relative_files = true
|
|
68
|
+
|
|
69
|
+
[tool.coverage.report]
|
|
70
|
+
show_missing = true
|
|
71
|
+
|
|
72
|
+
[tool.ruff]
|
|
73
|
+
target-version = "py310"
|
|
74
|
+
src = ["src", "tests"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint]
|
|
77
|
+
select = ["E4", "E7", "E9", "F"]
|
|
78
|
+
|
|
79
|
+
[tool.mypy]
|
|
80
|
+
files = ["src/timoshenko"]
|
|
81
|
+
check_untyped_defs = true
|
|
82
|
+
warn_unused_ignores = true
|
|
83
|
+
warn_redundant_casts = true
|
|
@@ -59,7 +59,7 @@ from .update import update
|
|
|
59
59
|
from .uncertainty import UncertaintyError, UncertaintyResult, propagate as propagate_uncertainty
|
|
60
60
|
from .vibration import RayleighDampingResult, damping_ratio, harmonic_response, natural_frequency_hz, rayleigh_damping_coefficients
|
|
61
61
|
|
|
62
|
-
__version__ = "2.0.
|
|
62
|
+
__version__ = "2.0.3"
|
|
63
63
|
|
|
64
64
|
__all__ = [
|
|
65
65
|
"HealthAssessment",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Iterator, Protocol, runtime_checkable
|
|
5
|
+
from typing import Iterator, Literal, Protocol, runtime_checkable
|
|
6
6
|
|
|
7
7
|
from .observations import ObservationBatch
|
|
8
8
|
from .session import MonitoringSession, SessionIngestResult
|
|
@@ -73,7 +73,7 @@ class SessionRunner:
|
|
|
73
73
|
self._consuming = False
|
|
74
74
|
return self
|
|
75
75
|
|
|
76
|
-
def __exit__(self, exc_type, exc, traceback) ->
|
|
76
|
+
def __exit__(self, exc_type, exc, traceback) -> Literal[False]:
|
|
77
77
|
self._active = False
|
|
78
78
|
try:
|
|
79
79
|
self.source.close()
|
|
@@ -8,7 +8,7 @@ import math
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
|
|
10
10
|
from .sensors import SensorData
|
|
11
|
-
from .oma import FDDMode, FDDResult, identify_fdd
|
|
11
|
+
from .oma import FDDMode as FDDMode, FDDResult as FDDResult, identify_fdd as identify_fdd
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass(frozen=True)
|
|
@@ -105,7 +105,7 @@ def identify(
|
|
|
105
105
|
)
|
|
106
106
|
for idx in selected
|
|
107
107
|
)
|
|
108
|
-
notes = ["Frequency spacing is limited by the record duration."] if resolution > 0.25 else []
|
|
108
|
+
notes: list[str] = ["Frequency spacing is limited by the record duration."] if resolution > 0.25 else []
|
|
109
109
|
if any(mode.damping_ratio is not None for mode in modes):
|
|
110
110
|
notes.append(
|
|
111
111
|
"Damping ratios are coarse half-power screening estimates from an averaged spectrum; "
|
|
@@ -116,7 +116,6 @@ def identify(
|
|
|
116
116
|
"Damping is withheld where the record is too short to resolve the half-power bandwidth "
|
|
117
117
|
f"with {_MIN_DAMPING_AVERAGES} averages and at least {_MIN_DAMPING_BINS:g} frequency bins."
|
|
118
118
|
)
|
|
119
|
-
notes = tuple(notes)
|
|
120
119
|
return ModalResult(
|
|
121
120
|
modes=modes,
|
|
122
121
|
sampling_hz=sensor_data.sampling_hz,
|
|
@@ -124,7 +123,7 @@ def identify(
|
|
|
124
123
|
resolution_hz=resolution,
|
|
125
124
|
channel=sensor_data.channel,
|
|
126
125
|
status="ok" if modes else "no_peaks_found",
|
|
127
|
-
notes=notes,
|
|
126
|
+
notes=tuple(notes),
|
|
128
127
|
)
|
|
129
128
|
|
|
130
129
|
|
|
@@ -54,11 +54,11 @@ class MultiChannelData:
|
|
|
54
54
|
|
|
55
55
|
@property
|
|
56
56
|
def sample_count(self) -> int:
|
|
57
|
-
return int(self.samples
|
|
57
|
+
return int(np.shape(self.samples)[0])
|
|
58
58
|
|
|
59
59
|
@property
|
|
60
60
|
def channel_count(self) -> int:
|
|
61
|
-
return int(self.samples
|
|
61
|
+
return int(np.shape(self.samples)[1])
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def load_multichannel_csv(
|
|
@@ -93,7 +93,7 @@ def load_multichannel_csv(
|
|
|
93
93
|
for row_number, row in enumerate(reader, start=2):
|
|
94
94
|
record: list[float] = []
|
|
95
95
|
for name in names:
|
|
96
|
-
raw = row.get(name)
|
|
96
|
+
raw = row.get(name) or ""
|
|
97
97
|
try:
|
|
98
98
|
value = float(raw)
|
|
99
99
|
except (TypeError, ValueError):
|
|
@@ -93,7 +93,7 @@ def identify_fdd(
|
|
|
93
93
|
raise TypeError("observations must be MultiChannelData; use tm.load_multichannel_csv() or construct it explicitly")
|
|
94
94
|
if len(set(observations.units)) != 1:
|
|
95
95
|
raise ValueError("FDD requires channels with the same measurement unit; calibrate/transform mixed-unit channels first")
|
|
96
|
-
sample_count, channel_count = observations.samples
|
|
96
|
+
sample_count, channel_count = np.shape(observations.samples)
|
|
97
97
|
nperseg_value, hop, segment_count, resolution, low, high = _plan(
|
|
98
98
|
sample_count,
|
|
99
99
|
observations.sampling_hz,
|
|
@@ -223,6 +223,7 @@ def run_project(project: LoadedProject | str | Path) -> ProjectRunResult:
|
|
|
223
223
|
if not isinstance(loaded, LoadedProject):
|
|
224
224
|
raise TypeError("project must be a manifest path or LoadedProject from tm.load_project()")
|
|
225
225
|
manifest = loaded.manifest
|
|
226
|
+
modal: ModalResult | FDDResult
|
|
226
227
|
if manifest.method == "peak_picking":
|
|
227
228
|
if not isinstance(loaded.observations, SensorData):
|
|
228
229
|
raise TypeError("peak_picking manifest must load one SensorData channel")
|
|
File without changes
|
|
@@ -94,7 +94,6 @@ def rectangular_tube(
|
|
|
94
94
|
if 2.0 * thickness >= min(width, height):
|
|
95
95
|
raise ValueError("twice wall_thickness_m must be smaller than both outer dimensions")
|
|
96
96
|
inner_height = height - 2.0 * thickness
|
|
97
|
-
inner_width = width - 2.0 * thickness
|
|
98
97
|
horizontal_plate_offset = (height - thickness) / 2.0
|
|
99
98
|
vertical_plate_offset = (width - thickness) / 2.0
|
|
100
99
|
area = 2.0 * width * thickness + 2.0 * thickness * inner_height
|