timoshenko-engine 2.0.2__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.2 → timoshenko_engine-2.0.3}/PKG-INFO +39 -16
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/README.md +32 -14
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/pyproject.toml +28 -3
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/__init__.py +1 -1
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/adapters.py +2 -2
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/modal.py +3 -4
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/multichannel.py +3 -3
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/oma.py +1 -1
- {timoshenko_engine-2.0.2 → 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.2 → timoshenko_engine-2.0.3}/src/timoshenko/sections.py +0 -1
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/sensors.py +1 -1
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/session.py +7 -5
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/storage.py +4 -3
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/uncertainty.py +4 -4
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/PKG-INFO +39 -16
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/SOURCES.txt +1 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/requires.txt +5 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/LICENSE +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/setup.cfg +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/_validation.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/assets.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/beams.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/csv_source.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/health.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/mechanics.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/monitor.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/mqtt.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/observations.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/plugins.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/polygon.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/pressure.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/report.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/sensorthings.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/shafts.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/stability.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/strength.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/structure.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/update.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko/vibration.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/dependency_links.txt +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/entry_points.txt +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/top_level.txt +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/tests/test_adapters.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/tests/test_formulas.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/tests/test_modal.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/tests/test_package.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/tests/test_polygon.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/tests/test_project_report.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/tests/test_storage_session.py +0 -0
- {timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/tests/test_uncertainty.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: timoshenko-engine
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: Composable structural engineering calculations and analysis primitives for Python applications.
|
|
5
5
|
Author: Ayberk Korkmaz
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/Ayberkrk/timoshenko
|
|
8
8
|
Project-URL: Repository, https://github.com/Ayberkrk/timoshenko
|
|
9
9
|
Project-URL: Issues, https://github.com/Ayberkrk/timoshenko/issues
|
|
10
|
-
Project-URL: Documentation, https://
|
|
10
|
+
Project-URL: Documentation, https://timoshenko.readthedocs.io/
|
|
11
11
|
Project-URL: Changelog, https://github.com/Ayberkrk/timoshenko/blob/main/docs/changelog.md
|
|
12
12
|
Keywords: civil engineering,structural engineering,structural health monitoring,modal analysis,digital twin
|
|
13
13
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
21
|
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Typing :: Typed
|
|
22
23
|
Requires-Python: >=3.10
|
|
23
24
|
Description-Content-Type: text/markdown
|
|
24
25
|
License-File: LICENSE
|
|
@@ -27,6 +28,10 @@ Provides-Extra: mqtt
|
|
|
27
28
|
Requires-Dist: paho-mqtt<3,>=2.1; extra == "mqtt"
|
|
28
29
|
Provides-Extra: test
|
|
29
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"
|
|
30
35
|
Provides-Extra: docs
|
|
31
36
|
Requires-Dist: mkdocs-material<10,>=9.5; extra == "docs"
|
|
32
37
|
Dynamic: license-file
|
|
@@ -46,7 +51,10 @@ Dynamic: license-file
|
|
|
46
51
|
<img alt="Python versions" src="https://img.shields.io/pypi/pyversions/timoshenko-engine?style=for-the-badge">
|
|
47
52
|
<img alt="Alpha" src="https://img.shields.io/badge/stage-alpha-orange?style=for-the-badge">
|
|
48
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>
|
|
49
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>
|
|
50
58
|
</p>
|
|
51
59
|
|
|
52
60
|
Timoshenko packages common structural calculations, modal analysis, sensor
|
|
@@ -56,7 +64,7 @@ Python library, not a hosted monitoring service or a general finite-element
|
|
|
56
64
|
solver.
|
|
57
65
|
|
|
58
66
|
> **Alpha:** the API may still change between releases. Every behavior change
|
|
59
|
-
> is listed in the [changelog](https://
|
|
67
|
+
> is listed in the [changelog](https://timoshenko.readthedocs.io/en/latest/changelog/).
|
|
60
68
|
|
|
61
69
|
## Install
|
|
62
70
|
|
|
@@ -100,6 +108,10 @@ for change in result.health.mode_changes:
|
|
|
100
108
|
print(result.health.review_recommended) # True
|
|
101
109
|
```
|
|
102
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
|
+
|
|
103
115
|
Real records load from CSV or JSON with
|
|
104
116
|
`tm.load_sensors("acceleration.csv", sampling_hz=100.0, column="acc")`. The
|
|
105
117
|
steps inside `tm.monitor` are also available separately as `tm.modal.identify`,
|
|
@@ -188,13 +200,15 @@ The session does not open network connections or run in the background. Use
|
|
|
188
200
|
|
|
189
201
|
## Documentation
|
|
190
202
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
- [
|
|
195
|
-
- [
|
|
196
|
-
- [
|
|
197
|
-
- [
|
|
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/)
|
|
198
212
|
|
|
199
213
|
## Limits and engineering posture
|
|
200
214
|
|
|
@@ -209,8 +223,10 @@ The session does not open network connections or run in the background. Use
|
|
|
209
223
|
## Development
|
|
210
224
|
|
|
211
225
|
```bash
|
|
212
|
-
python -m pip install -e ".[test]"
|
|
213
|
-
python -m pytest
|
|
226
|
+
python -m pip install -e ".[test,lint]"
|
|
227
|
+
python -m pytest --cov
|
|
228
|
+
python -m ruff check .
|
|
229
|
+
python -m mypy
|
|
214
230
|
```
|
|
215
231
|
|
|
216
232
|
To preview the documentation site:
|
|
@@ -221,13 +237,20 @@ python -m mkdocs serve
|
|
|
221
237
|
```
|
|
222
238
|
|
|
223
239
|
Bug reports and pull requests are welcome in the
|
|
224
|
-
[issue tracker](https://github.com/Ayberkrk/timoshenko/issues).
|
|
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.
|
|
225
245
|
|
|
226
246
|
## Citation
|
|
227
247
|
|
|
228
|
-
If you use Timoshenko in research, please cite it
|
|
229
|
-
[
|
|
230
|
-
|
|
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.
|
|
231
254
|
|
|
232
255
|
## License
|
|
233
256
|
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
<img alt="Python versions" src="https://img.shields.io/pypi/pyversions/timoshenko-engine?style=for-the-badge">
|
|
14
14
|
<img alt="Alpha" src="https://img.shields.io/badge/stage-alpha-orange?style=for-the-badge">
|
|
15
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>
|
|
16
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>
|
|
17
20
|
</p>
|
|
18
21
|
|
|
19
22
|
Timoshenko packages common structural calculations, modal analysis, sensor
|
|
@@ -23,7 +26,7 @@ Python library, not a hosted monitoring service or a general finite-element
|
|
|
23
26
|
solver.
|
|
24
27
|
|
|
25
28
|
> **Alpha:** the API may still change between releases. Every behavior change
|
|
26
|
-
> is listed in the [changelog](https://
|
|
29
|
+
> is listed in the [changelog](https://timoshenko.readthedocs.io/en/latest/changelog/).
|
|
27
30
|
|
|
28
31
|
## Install
|
|
29
32
|
|
|
@@ -67,6 +70,10 @@ for change in result.health.mode_changes:
|
|
|
67
70
|
print(result.health.review_recommended) # True
|
|
68
71
|
```
|
|
69
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
|
+
|
|
70
77
|
Real records load from CSV or JSON with
|
|
71
78
|
`tm.load_sensors("acceleration.csv", sampling_hz=100.0, column="acc")`. The
|
|
72
79
|
steps inside `tm.monitor` are also available separately as `tm.modal.identify`,
|
|
@@ -155,13 +162,15 @@ The session does not open network connections or run in the background. Use
|
|
|
155
162
|
|
|
156
163
|
## Documentation
|
|
157
164
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
- [
|
|
162
|
-
- [
|
|
163
|
-
- [
|
|
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/)
|
|
165
174
|
|
|
166
175
|
## Limits and engineering posture
|
|
167
176
|
|
|
@@ -176,8 +185,10 @@ The session does not open network connections or run in the background. Use
|
|
|
176
185
|
## Development
|
|
177
186
|
|
|
178
187
|
```bash
|
|
179
|
-
python -m pip install -e ".[test]"
|
|
180
|
-
python -m pytest
|
|
188
|
+
python -m pip install -e ".[test,lint]"
|
|
189
|
+
python -m pytest --cov
|
|
190
|
+
python -m ruff check .
|
|
191
|
+
python -m mypy
|
|
181
192
|
```
|
|
182
193
|
|
|
183
194
|
To preview the documentation site:
|
|
@@ -188,13 +199,20 @@ python -m mkdocs serve
|
|
|
188
199
|
```
|
|
189
200
|
|
|
190
201
|
Bug reports and pull requests are welcome in the
|
|
191
|
-
[issue tracker](https://github.com/Ayberkrk/timoshenko/issues).
|
|
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.
|
|
192
207
|
|
|
193
208
|
## Citation
|
|
194
209
|
|
|
195
|
-
If you use Timoshenko in research, please cite it
|
|
196
|
-
[
|
|
197
|
-
|
|
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.
|
|
198
216
|
|
|
199
217
|
## License
|
|
200
218
|
|
|
@@ -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,7 @@ 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/"
|
|
48
50
|
Changelog = "https://github.com/Ayberkrk/timoshenko/blob/main/docs/changelog.md"
|
|
49
51
|
|
|
50
52
|
[tool.setuptools]
|
|
@@ -53,6 +55,29 @@ package-dir = { "" = "src" }
|
|
|
53
55
|
[tool.setuptools.packages.find]
|
|
54
56
|
where = ["src"]
|
|
55
57
|
|
|
58
|
+
[tool.setuptools.package-data]
|
|
59
|
+
timoshenko = ["py.typed"]
|
|
60
|
+
|
|
56
61
|
[tool.pytest.ini_options]
|
|
57
62
|
testpaths = ["tests"]
|
|
58
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
|
|
@@ -178,7 +178,7 @@ class MonitoringSession:
|
|
|
178
178
|
self._review_threshold_pct = validate_review_threshold(review_threshold_pct)
|
|
179
179
|
self._baseline_structure = structure
|
|
180
180
|
self._store = store
|
|
181
|
-
self._buffers = {sensor_id: deque(maxlen=window) for sensor_id in ids}
|
|
181
|
+
self._buffers: dict[str, deque[tuple[int, float]]] = {sensor_id: deque(maxlen=window) for sensor_id in ids}
|
|
182
182
|
self._last_key: dict[str, int] = {}
|
|
183
183
|
self._seen_by_key: dict[int, set[str]] = {}
|
|
184
184
|
self._seen_key_heap: list[int] = []
|
|
@@ -350,23 +350,25 @@ class MonitoringSession:
|
|
|
350
350
|
if snapshot is None:
|
|
351
351
|
return None
|
|
352
352
|
keys, matrix = snapshot
|
|
353
|
+
observations: SensorData | MultiChannelData
|
|
354
|
+
modal: ModalResult | FDDResult
|
|
353
355
|
if len(self._sensor_ids) == 1:
|
|
354
|
-
|
|
356
|
+
channel = SensorData(
|
|
355
357
|
samples=matrix[:, 0],
|
|
356
358
|
sampling_hz=self._sampling_hz,
|
|
357
359
|
unit=self._units[0],
|
|
358
360
|
channel=self._sensor_ids[0],
|
|
359
361
|
)
|
|
360
|
-
modal = identify(
|
|
362
|
+
observations, modal = channel, identify(channel, **self._options)
|
|
361
363
|
method = "peak_picking"
|
|
362
364
|
else:
|
|
363
|
-
|
|
365
|
+
channels = MultiChannelData(
|
|
364
366
|
samples=matrix,
|
|
365
367
|
sampling_hz=self._sampling_hz,
|
|
366
368
|
channel_ids=self._sensor_ids,
|
|
367
369
|
units=self._units,
|
|
368
370
|
)
|
|
369
|
-
modal = identify_fdd(
|
|
371
|
+
observations, modal = channels, identify_fdd(channels, **self._options)
|
|
370
372
|
method = "fdd"
|
|
371
373
|
if modal.modes:
|
|
372
374
|
self._structure = update(self._baseline_structure, modal)
|
|
@@ -75,7 +75,7 @@ class SQLiteStore:
|
|
|
75
75
|
timeout = float(timeout_seconds)
|
|
76
76
|
if not math.isfinite(timeout) or timeout <= 0.0:
|
|
77
77
|
raise ValueError("timeout_seconds must be finite and positive")
|
|
78
|
-
self._connection = sqlite3.connect(self.path, timeout=timeout)
|
|
78
|
+
self._connection: sqlite3.Connection | None = sqlite3.connect(self.path, timeout=timeout)
|
|
79
79
|
self._connection.row_factory = sqlite3.Row
|
|
80
80
|
self._db.execute("PRAGMA foreign_keys = ON")
|
|
81
81
|
self._initialize()
|
|
@@ -154,8 +154,9 @@ class SQLiteStore:
|
|
|
154
154
|
return connection
|
|
155
155
|
|
|
156
156
|
def close(self) -> None:
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
connection = getattr(self, "_connection", None)
|
|
158
|
+
if connection is not None:
|
|
159
|
+
connection.close()
|
|
159
160
|
self._connection = None
|
|
160
161
|
|
|
161
162
|
def __enter__(self) -> "SQLiteStore":
|
|
@@ -227,7 +227,7 @@ def _finite_scalar(value: Any, label: str) -> float:
|
|
|
227
227
|
if isinstance(value, (bool, np.bool_)) or not np.isscalar(value):
|
|
228
228
|
raise UncertaintyError(f"{label} must be a finite real number")
|
|
229
229
|
try:
|
|
230
|
-
result = float(value)
|
|
230
|
+
result = float(value) # type: ignore[arg-type]
|
|
231
231
|
except (TypeError, ValueError) as error:
|
|
232
232
|
raise UncertaintyError(f"{label} must be a finite real number") from error
|
|
233
233
|
if not math.isfinite(result):
|
|
@@ -235,7 +235,7 @@ def _finite_scalar(value: Any, label: str) -> float:
|
|
|
235
235
|
return result
|
|
236
236
|
|
|
237
237
|
|
|
238
|
-
def _evaluate(function: Callable[..., float], names: tuple[str, ...], values: Sequence[float], label: str) -> float:
|
|
238
|
+
def _evaluate(function: Callable[..., float], names: tuple[str, ...], values: Sequence[float] | np.ndarray, label: str) -> float:
|
|
239
239
|
try:
|
|
240
240
|
result = function(**dict(zip(names, values)))
|
|
241
241
|
except Exception as error:
|
|
@@ -243,7 +243,7 @@ def _evaluate(function: Callable[..., float], names: tuple[str, ...], values: Se
|
|
|
243
243
|
if isinstance(result, (bool, np.bool_)) or not np.isscalar(result):
|
|
244
244
|
raise UncertaintyError(f"equation output for {label} must be a finite scalar number")
|
|
245
245
|
try:
|
|
246
|
-
output = float(result)
|
|
246
|
+
output = float(result) # type: ignore[arg-type]
|
|
247
247
|
except (TypeError, ValueError) as error:
|
|
248
248
|
raise UncertaintyError(f"equation output for {label} must be a finite scalar number") from error
|
|
249
249
|
if not math.isfinite(output):
|
|
@@ -251,7 +251,7 @@ def _evaluate(function: Callable[..., float], names: tuple[str, ...], values: Se
|
|
|
251
251
|
return output
|
|
252
252
|
|
|
253
253
|
|
|
254
|
-
def _try_evaluate(function: Callable[..., float], names: tuple[str, ...], values: Sequence[float]) -> float | None:
|
|
254
|
+
def _try_evaluate(function: Callable[..., float], names: tuple[str, ...], values: Sequence[float] | np.ndarray) -> float | None:
|
|
255
255
|
try:
|
|
256
256
|
return _evaluate(function, names, values, "finite difference input")
|
|
257
257
|
except UncertaintyError:
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: timoshenko-engine
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: Composable structural engineering calculations and analysis primitives for Python applications.
|
|
5
5
|
Author: Ayberk Korkmaz
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/Ayberkrk/timoshenko
|
|
8
8
|
Project-URL: Repository, https://github.com/Ayberkrk/timoshenko
|
|
9
9
|
Project-URL: Issues, https://github.com/Ayberkrk/timoshenko/issues
|
|
10
|
-
Project-URL: Documentation, https://
|
|
10
|
+
Project-URL: Documentation, https://timoshenko.readthedocs.io/
|
|
11
11
|
Project-URL: Changelog, https://github.com/Ayberkrk/timoshenko/blob/main/docs/changelog.md
|
|
12
12
|
Keywords: civil engineering,structural engineering,structural health monitoring,modal analysis,digital twin
|
|
13
13
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
21
|
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Typing :: Typed
|
|
22
23
|
Requires-Python: >=3.10
|
|
23
24
|
Description-Content-Type: text/markdown
|
|
24
25
|
License-File: LICENSE
|
|
@@ -27,6 +28,10 @@ Provides-Extra: mqtt
|
|
|
27
28
|
Requires-Dist: paho-mqtt<3,>=2.1; extra == "mqtt"
|
|
28
29
|
Provides-Extra: test
|
|
29
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"
|
|
30
35
|
Provides-Extra: docs
|
|
31
36
|
Requires-Dist: mkdocs-material<10,>=9.5; extra == "docs"
|
|
32
37
|
Dynamic: license-file
|
|
@@ -46,7 +51,10 @@ Dynamic: license-file
|
|
|
46
51
|
<img alt="Python versions" src="https://img.shields.io/pypi/pyversions/timoshenko-engine?style=for-the-badge">
|
|
47
52
|
<img alt="Alpha" src="https://img.shields.io/badge/stage-alpha-orange?style=for-the-badge">
|
|
48
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>
|
|
49
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>
|
|
50
58
|
</p>
|
|
51
59
|
|
|
52
60
|
Timoshenko packages common structural calculations, modal analysis, sensor
|
|
@@ -56,7 +64,7 @@ Python library, not a hosted monitoring service or a general finite-element
|
|
|
56
64
|
solver.
|
|
57
65
|
|
|
58
66
|
> **Alpha:** the API may still change between releases. Every behavior change
|
|
59
|
-
> is listed in the [changelog](https://
|
|
67
|
+
> is listed in the [changelog](https://timoshenko.readthedocs.io/en/latest/changelog/).
|
|
60
68
|
|
|
61
69
|
## Install
|
|
62
70
|
|
|
@@ -100,6 +108,10 @@ for change in result.health.mode_changes:
|
|
|
100
108
|
print(result.health.review_recommended) # True
|
|
101
109
|
```
|
|
102
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
|
+
|
|
103
115
|
Real records load from CSV or JSON with
|
|
104
116
|
`tm.load_sensors("acceleration.csv", sampling_hz=100.0, column="acc")`. The
|
|
105
117
|
steps inside `tm.monitor` are also available separately as `tm.modal.identify`,
|
|
@@ -188,13 +200,15 @@ The session does not open network connections or run in the background. Use
|
|
|
188
200
|
|
|
189
201
|
## Documentation
|
|
190
202
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
- [
|
|
195
|
-
- [
|
|
196
|
-
- [
|
|
197
|
-
- [
|
|
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/)
|
|
198
212
|
|
|
199
213
|
## Limits and engineering posture
|
|
200
214
|
|
|
@@ -209,8 +223,10 @@ The session does not open network connections or run in the background. Use
|
|
|
209
223
|
## Development
|
|
210
224
|
|
|
211
225
|
```bash
|
|
212
|
-
python -m pip install -e ".[test]"
|
|
213
|
-
python -m pytest
|
|
226
|
+
python -m pip install -e ".[test,lint]"
|
|
227
|
+
python -m pytest --cov
|
|
228
|
+
python -m ruff check .
|
|
229
|
+
python -m mypy
|
|
214
230
|
```
|
|
215
231
|
|
|
216
232
|
To preview the documentation site:
|
|
@@ -221,13 +237,20 @@ python -m mkdocs serve
|
|
|
221
237
|
```
|
|
222
238
|
|
|
223
239
|
Bug reports and pull requests are welcome in the
|
|
224
|
-
[issue tracker](https://github.com/Ayberkrk/timoshenko/issues).
|
|
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.
|
|
225
245
|
|
|
226
246
|
## Citation
|
|
227
247
|
|
|
228
|
-
If you use Timoshenko in research, please cite it
|
|
229
|
-
[
|
|
230
|
-
|
|
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.
|
|
231
254
|
|
|
232
255
|
## License
|
|
233
256
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{timoshenko_engine-2.0.2 → timoshenko_engine-2.0.3}/src/timoshenko_engine.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|