repyability 0.4.0__tar.gz → 0.5.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.
- repyability-0.5.0/PKG-INFO +118 -0
- repyability-0.4.0/repyability.egg-info/PKG-INFO → repyability-0.5.0/README.md +20 -15
- repyability-0.5.0/pyproject.toml +112 -0
- repyability-0.5.0/repyability/__init__.py +55 -0
- repyability-0.5.0/repyability/_version.py +8 -0
- repyability-0.5.0/repyability/maintenance.py +28 -0
- repyability-0.5.0/repyability/non_repairable.py +227 -0
- repyability-0.5.0/repyability/rbd/_model_utils.py +59 -0
- {repyability-0.4.0 → repyability-0.5.0}/repyability/rbd/helper_classes.py +11 -21
- {repyability-0.4.0 → repyability-0.5.0}/repyability/rbd/min_path_sets.py +22 -45
- repyability-0.5.0/repyability/rbd/non_repairable_rbd.py +1006 -0
- repyability-0.5.0/repyability/rbd/numerical_convolution.py +191 -0
- repyability-0.5.0/repyability/rbd/rbd.py +997 -0
- repyability-0.5.0/repyability/rbd/rbd_graph.py +139 -0
- repyability-0.5.0/repyability/rbd/repairable_rbd.py +941 -0
- repyability-0.5.0/repyability/rbd/repeated_node.py +55 -0
- repyability-0.5.0/repyability/rbd/repeated_standby_node.py +71 -0
- repyability-0.5.0/repyability/rbd/results.py +278 -0
- repyability-0.5.0/repyability/rbd/serialisation.py +248 -0
- repyability-0.5.0/repyability/rbd/standby_node.py +191 -0
- repyability-0.5.0/repyability/repairable.py +190 -0
- repyability-0.5.0/repyability/tests/conftest.py +324 -0
- repyability-0.5.0/repyability/tests/test_api_standardisation.py +192 -0
- repyability-0.5.0/repyability/tests/test_bx_life.py +97 -0
- repyability-0.5.0/repyability/tests/test_fussell_vesely_deprecation.py +44 -0
- repyability-0.5.0/repyability/tests/test_helper_classes.py +21 -0
- repyability-0.5.0/repyability/tests/test_model_utils.py +53 -0
- repyability-0.5.0/repyability/tests/test_non_repairable.py +173 -0
- repyability-0.5.0/repyability/tests/test_rbd_allocation.py +65 -0
- repyability-0.5.0/repyability/tests/test_rbd_analytic_solvability.py +79 -0
- repyability-0.5.0/repyability/tests/test_rbd_berge_cut_sets.py +126 -0
- repyability-0.5.0/repyability/tests/test_rbd_exact_probability.py +104 -0
- repyability-0.5.0/repyability/tests/test_rbd_exponential_standby.py +76 -0
- {repyability-0.4.0 → repyability-0.5.0}/repyability/tests/test_rbd_get_min_path_sets.py +2 -1
- repyability-0.5.0/repyability/tests/test_rbd_hazard.py +55 -0
- repyability-0.5.0/repyability/tests/test_rbd_importances.py +620 -0
- repyability-0.5.0/repyability/tests/test_rbd_koon.py +244 -0
- repyability-0.5.0/repyability/tests/test_rbd_misc.py +82 -0
- repyability-0.5.0/repyability/tests/test_rbd_non_repairable.py +244 -0
- repyability-0.5.0/repyability/tests/test_rbd_numerical_convolution.py +76 -0
- repyability-0.5.0/repyability/tests/test_rbd_repeated_standby.py +53 -0
- repyability-0.5.0/repyability/tests/test_rbd_set_working_failed.py +251 -0
- repyability-0.5.0/repyability/tests/test_rbd_sf.py +166 -0
- repyability-0.5.0/repyability/tests/test_rbd_structure.py +144 -0
- repyability-0.5.0/repyability/tests/test_rbd_switching_probability.py +96 -0
- repyability-0.5.0/repyability/tests/test_repairable.py +134 -0
- repyability-0.5.0/repyability/tests/test_repairable_accounting.py +50 -0
- repyability-0.5.0/repyability/tests/test_repairable_metrics.py +125 -0
- {repyability-0.4.0 → repyability-0.5.0}/repyability/tests/test_repairable_rbd.py +51 -51
- repyability-0.5.0/repyability/tests/test_repeated_node.py +62 -0
- repyability-0.5.0/repyability/tests/test_results.py +60 -0
- repyability-0.5.0/repyability/tests/test_rng_reproducibility.py +89 -0
- repyability-0.5.0/repyability/tests/test_serialisation.py +140 -0
- repyability-0.5.0/repyability/tests/test_simulation_validation.py +162 -0
- repyability-0.5.0/repyability/tests/test_standby.py +64 -0
- repyability-0.5.0/repyability/utils/wrappers.py +86 -0
- repyability-0.5.0/repyability.egg-info/PKG-INFO +118 -0
- repyability-0.5.0/repyability.egg-info/SOURCES.txt +64 -0
- {repyability-0.4.0 → repyability-0.5.0}/repyability.egg-info/dependency_links.txt +0 -0
- repyability-0.5.0/repyability.egg-info/requires.txt +20 -0
- {repyability-0.4.0 → repyability-0.5.0}/repyability.egg-info/top_level.txt +0 -0
- repyability-0.4.0/PKG-INFO +0 -51
- repyability-0.4.0/README.md +0 -37
- repyability-0.4.0/pyproject.toml +0 -15
- repyability-0.4.0/repyability/__init__.py +0 -2
- repyability-0.4.0/repyability/non_repairable.py +0 -51
- repyability-0.4.0/repyability/rbd/min_cut_sets.py +0 -71
- repyability-0.4.0/repyability/rbd/rbd.py +0 -613
- repyability-0.4.0/repyability/rbd/rbd_args_check.py +0 -53
- repyability-0.4.0/repyability/rbd/rbd_graph.py +0 -26
- repyability-0.4.0/repyability/rbd/repairable_rbd.py +0 -262
- repyability-0.4.0/repyability/repairable.py +0 -28
- repyability-0.4.0/repyability/tests/conftest.py +0 -429
- repyability-0.4.0/repyability/tests/fixed_probability.py +0 -17
- repyability-0.4.0/repyability/tests/test_non_repairable.py +0 -39
- repyability-0.4.0/repyability/tests/test_rbd_importances.py +0 -677
- repyability-0.4.0/repyability/tests/test_rbd_koon.py +0 -177
- repyability-0.4.0/repyability/tests/test_rbd_misc.py +0 -85
- repyability-0.4.0/repyability/tests/test_rbd_sf.py +0 -183
- repyability-0.4.0/repyability/tests/test_repairable.py +0 -13
- repyability-0.4.0/repyability.egg-info/SOURCES.txt +0 -32
- repyability-0.4.0/repyability.egg-info/requires.txt +0 -4
- repyability-0.4.0/setup.py +0 -23
- {repyability-0.4.0 → repyability-0.5.0}/LICENSE +0 -0
- {repyability-0.4.0 → repyability-0.5.0}/repyability/rbd/__init__.py +0 -0
- {repyability-0.4.0 → repyability-0.5.0}/repyability/tests/__init__.py +0 -0
- {repyability-0.4.0 → repyability-0.5.0}/repyability/tests/test_rbd_get_min_cut_sets.py +0 -0
- {repyability-0.4.0 → repyability-0.5.0}/setup.cfg +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: repyability
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Reliability engineering tools for Python
|
|
5
|
+
Author-email: derrynknife <derrynknife@gmail.com>
|
|
6
|
+
License: Copyright (c) 2020 The Python Packaging Authority
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
|
25
|
+
Project-URL: Homepage, https://github.com/derrynknife/RePyability
|
|
26
|
+
Project-URL: Documentation, https://derrynknife.github.io/RePyability/
|
|
27
|
+
Project-URL: Repository, https://github.com/derrynknife/RePyability
|
|
28
|
+
Project-URL: Issues, https://github.com/derrynknife/RePyability/issues
|
|
29
|
+
Keywords: reliability,reliability-engineering,reliability-block-diagram,rbd,survival-analysis,availability,maintainability
|
|
30
|
+
Classifier: Development Status :: 4 - Beta
|
|
31
|
+
Classifier: Intended Audience :: Science/Research
|
|
32
|
+
Classifier: Intended Audience :: Education
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Topic :: Scientific/Engineering
|
|
39
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
40
|
+
Requires-Python: >=3.11
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: numpy<3,>=2.0
|
|
44
|
+
Requires-Dist: scipy>=1.7
|
|
45
|
+
Requires-Dist: networkx>=3.0
|
|
46
|
+
Requires-Dist: surpyval<0.12,>=0.11.1
|
|
47
|
+
Requires-Dist: tqdm>=4.64
|
|
48
|
+
Provides-Extra: dev
|
|
49
|
+
Requires-Dist: black==26.3.1; extra == "dev"
|
|
50
|
+
Requires-Dist: isort>=5.12; extra == "dev"
|
|
51
|
+
Requires-Dist: flake8>=6.0; extra == "dev"
|
|
52
|
+
Requires-Dist: flake8-pyproject>=1.2; extra == "dev"
|
|
53
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
54
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
55
|
+
Requires-Dist: coverage>=7.0; extra == "dev"
|
|
56
|
+
Requires-Dist: pre-commit>=3.0; extra == "dev"
|
|
57
|
+
Requires-Dist: joblib>=1.0; extra == "dev"
|
|
58
|
+
Provides-Extra: docs
|
|
59
|
+
Requires-Dist: mkdocs-material>=9.0; extra == "docs"
|
|
60
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
|
|
61
|
+
Dynamic: license-file
|
|
62
|
+
|
|
63
|
+
# RePyability
|
|
64
|
+
|
|
65
|
+
[](https://github.com/derrynknife/RePyability/actions/workflows/actions.yml)
|
|
66
|
+
[](https://derrynknife.github.io/RePyability/)
|
|
67
|
+
|
|
68
|
+
Reliability Engineering Tools
|
|
69
|
+
|
|
70
|
+
This is a series of tools created to make an open source set of methods to be used by reliability engineers to make it more accessible for students right through to practicing professionals.
|
|
71
|
+
|
|
72
|
+
## Install
|
|
73
|
+
RePyability can be installed via pip using the PyPI [repository](https://pypi.org/project/repyability/)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install repyability
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Documentation
|
|
80
|
+
The full documentation — overview, user guide, and API reference — is hosted at
|
|
81
|
+
**[derrynknife.github.io/RePyability](https://derrynknife.github.io/RePyability/)**.
|
|
82
|
+
|
|
83
|
+
It is built with [MkDocs](https://www.mkdocs.org/) from the sources in `docs/`
|
|
84
|
+
and `mkdocs.yml`, and published to GitHub Pages on every push to `master`. To
|
|
85
|
+
build and serve it locally:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install -e .[docs]
|
|
89
|
+
mkdocs serve # then open http://127.0.0.1:8000
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The source lives in `docs/` and `mkdocs.yml`; start with `docs/index.md`.
|
|
93
|
+
|
|
94
|
+
## Testing
|
|
95
|
+
Run the testing suite by simply executing:
|
|
96
|
+
```bash
|
|
97
|
+
pytest
|
|
98
|
+
```
|
|
99
|
+
or use coverage to get a coverage report:
|
|
100
|
+
```bash
|
|
101
|
+
coverage run -m pytest # Run pytest under coverage's watch
|
|
102
|
+
coverage report # Print coverage report
|
|
103
|
+
coverage html # Make a html coverage report (really useful), open htmlcov/index.html
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Pre-commit
|
|
107
|
+
### TL;DR
|
|
108
|
+
- Pip install `pre-commit` (it's in `requirements_dev.txt` anyways)
|
|
109
|
+
- Run `pre-commit install` which sets up the git hook scripts
|
|
110
|
+
- If you'd like, run `pre-commit run --all-files` to run the hooks on all files
|
|
111
|
+
- When you go to commit, it will only proceed after all the hooks succeed
|
|
112
|
+
|
|
113
|
+
### Why?
|
|
114
|
+
To ensure the good code quality and consistency it is recommended that when contributing to this
|
|
115
|
+
repository to use the provided `.pre-commit-config.yaml` configuration for the Python package
|
|
116
|
+
`pre-commit` (https://pre-commit.com). Upon making a commit, it checks that imports
|
|
117
|
+
and requirements are sorted, syntax is up-to-date, code is formatted, linted, and statically type-checked,
|
|
118
|
+
all with the same tools and configurations as one another.
|
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: repyability
|
|
3
|
-
Version: 0.4.0
|
|
4
|
-
Summary: A reliability analysis python package
|
|
5
|
-
Home-page: https://github.com/derrynknife/RePyability
|
|
6
|
-
Author: Derryn Knife
|
|
7
|
-
Author-email: derryn@reliafy.com
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.6
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENSE
|
|
14
|
-
|
|
15
1
|
# RePyability
|
|
2
|
+
|
|
3
|
+
[](https://github.com/derrynknife/RePyability/actions/workflows/actions.yml)
|
|
4
|
+
[](https://derrynknife.github.io/RePyability/)
|
|
5
|
+
|
|
16
6
|
Reliability Engineering Tools
|
|
17
7
|
|
|
18
8
|
This is a series of tools created to make an open source set of methods to be used by reliability engineers to make it more accessible for students right through to practicing professionals.
|
|
@@ -24,6 +14,21 @@ RePyability can be installed via pip using the PyPI [repository](https://pypi.or
|
|
|
24
14
|
pip install repyability
|
|
25
15
|
```
|
|
26
16
|
|
|
17
|
+
## Documentation
|
|
18
|
+
The full documentation — overview, user guide, and API reference — is hosted at
|
|
19
|
+
**[derrynknife.github.io/RePyability](https://derrynknife.github.io/RePyability/)**.
|
|
20
|
+
|
|
21
|
+
It is built with [MkDocs](https://www.mkdocs.org/) from the sources in `docs/`
|
|
22
|
+
and `mkdocs.yml`, and published to GitHub Pages on every push to `master`. To
|
|
23
|
+
build and serve it locally:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install -e .[docs]
|
|
27
|
+
mkdocs serve # then open http://127.0.0.1:8000
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The source lives in `docs/` and `mkdocs.yml`; start with `docs/index.md`.
|
|
31
|
+
|
|
27
32
|
## Testing
|
|
28
33
|
Run the testing suite by simply executing:
|
|
29
34
|
```bash
|
|
@@ -38,7 +43,7 @@ coverage html # Make a html coverage report (really useful), open html
|
|
|
38
43
|
|
|
39
44
|
## Pre-commit
|
|
40
45
|
### TL;DR
|
|
41
|
-
- Pip install `pre-commit` (it's in `
|
|
46
|
+
- Pip install `pre-commit` (it's in `requirements_dev.txt` anyways)
|
|
42
47
|
- Run `pre-commit install` which sets up the git hook scripts
|
|
43
48
|
- If you'd like, run `pre-commit run --all-files` to run the hooks on all files
|
|
44
49
|
- When you go to commit, it will only proceed after all the hooks succeed
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "repyability"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Reliability engineering tools for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [{ name = "derrynknife", email = "derrynknife@gmail.com" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"reliability",
|
|
15
|
+
"reliability-engineering",
|
|
16
|
+
"reliability-block-diagram",
|
|
17
|
+
"rbd",
|
|
18
|
+
"survival-analysis",
|
|
19
|
+
"availability",
|
|
20
|
+
"maintainability",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 4 - Beta",
|
|
24
|
+
"Intended Audience :: Science/Research",
|
|
25
|
+
"Intended Audience :: Education",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
"Operating System :: OS Independent",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Topic :: Scientific/Engineering",
|
|
32
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
33
|
+
]
|
|
34
|
+
dependencies = [
|
|
35
|
+
"numpy>=2.0,<3",
|
|
36
|
+
"scipy>=1.7",
|
|
37
|
+
"networkx>=3.0",
|
|
38
|
+
"surpyval>=0.11.1,<0.12",
|
|
39
|
+
"tqdm>=4.64",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = [
|
|
44
|
+
"black==26.3.1",
|
|
45
|
+
"isort>=5.12",
|
|
46
|
+
"flake8>=6.0",
|
|
47
|
+
"flake8-pyproject>=1.2",
|
|
48
|
+
"mypy>=1.8",
|
|
49
|
+
"pytest>=7.0",
|
|
50
|
+
"coverage>=7.0",
|
|
51
|
+
"pre-commit>=3.0",
|
|
52
|
+
# Testing-only: the suite imports surpyval, which transitively pulls in its
|
|
53
|
+
# RandomSurvivalForest (and hence joblib). Not a runtime dependency of
|
|
54
|
+
# RePyability.
|
|
55
|
+
"joblib>=1.0",
|
|
56
|
+
]
|
|
57
|
+
docs = [
|
|
58
|
+
"mkdocs-material>=9.0",
|
|
59
|
+
"mkdocstrings[python]>=0.24",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[project.urls]
|
|
63
|
+
Homepage = "https://github.com/derrynknife/RePyability"
|
|
64
|
+
Documentation = "https://derrynknife.github.io/RePyability/"
|
|
65
|
+
Repository = "https://github.com/derrynknife/RePyability"
|
|
66
|
+
Issues = "https://github.com/derrynknife/RePyability/issues"
|
|
67
|
+
|
|
68
|
+
[tool.setuptools.dynamic]
|
|
69
|
+
version = { attr = "repyability._version.__version__" }
|
|
70
|
+
|
|
71
|
+
[tool.setuptools.packages.find]
|
|
72
|
+
include = ["repyability*"]
|
|
73
|
+
|
|
74
|
+
# For Black pre-commit hook
|
|
75
|
+
[tool.black]
|
|
76
|
+
line-length = 79
|
|
77
|
+
|
|
78
|
+
# So isort is compatible with black
|
|
79
|
+
[tool.isort]
|
|
80
|
+
profile = "black"
|
|
81
|
+
line_length = 79
|
|
82
|
+
|
|
83
|
+
# mypy
|
|
84
|
+
[tool.mypy]
|
|
85
|
+
exclude = ['repyability/tests/']
|
|
86
|
+
|
|
87
|
+
[[tool.mypy.overrides]]
|
|
88
|
+
module = [
|
|
89
|
+
'scipy.*',
|
|
90
|
+
'networkx',
|
|
91
|
+
'surpyval.*',
|
|
92
|
+
'tqdm',
|
|
93
|
+
'numpy',
|
|
94
|
+
'numpy.*',
|
|
95
|
+
]
|
|
96
|
+
ignore_missing_imports = true
|
|
97
|
+
|
|
98
|
+
# Coverage
|
|
99
|
+
[tool.coverage.run]
|
|
100
|
+
source = ["repyability"]
|
|
101
|
+
omit = ["*/tests/*"]
|
|
102
|
+
|
|
103
|
+
[tool.coverage.report]
|
|
104
|
+
# The library (excluding tests) must stay above this bar.
|
|
105
|
+
fail_under = 80
|
|
106
|
+
|
|
107
|
+
# Flake8
|
|
108
|
+
[tool.flake8]
|
|
109
|
+
extend-ignore = ['E203', 'W503']
|
|
110
|
+
per-file-ignores = [
|
|
111
|
+
'*/__init__.py:F401',
|
|
112
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""RePyability — reliability engineering tools for Python.
|
|
2
|
+
|
|
3
|
+
The most commonly used classes are re-exported here so they can be imported
|
|
4
|
+
directly from the top-level package, e.g.::
|
|
5
|
+
|
|
6
|
+
from repyability import NonRepairableRBD, StandbyModel
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from repyability._version import __version__
|
|
10
|
+
from repyability.maintenance import MaintenancePolicy
|
|
11
|
+
from repyability.non_repairable import NonRepairable
|
|
12
|
+
from repyability.rbd.helper_classes import (
|
|
13
|
+
PerfectReliability,
|
|
14
|
+
PerfectUnreliability,
|
|
15
|
+
)
|
|
16
|
+
from repyability.rbd.non_repairable_rbd import NonRepairableRBD
|
|
17
|
+
from repyability.rbd.rbd import RBD
|
|
18
|
+
from repyability.rbd.repairable_rbd import RepairableRBD
|
|
19
|
+
from repyability.rbd.repeated_node import RepeatedNode
|
|
20
|
+
from repyability.rbd.repeated_standby_node import RepeatedStandbyNode
|
|
21
|
+
from repyability.rbd.results import (
|
|
22
|
+
AvailabilityResult,
|
|
23
|
+
ConfidenceInterval,
|
|
24
|
+
Criticalities,
|
|
25
|
+
FailureCriticalityIndex,
|
|
26
|
+
RestorationCriticalityIndex,
|
|
27
|
+
UpDownImportance,
|
|
28
|
+
)
|
|
29
|
+
from repyability.rbd.standby_node import StandbyModel
|
|
30
|
+
from repyability.repairable import Repairable
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"__version__",
|
|
34
|
+
# System models
|
|
35
|
+
"RBD",
|
|
36
|
+
"NonRepairableRBD",
|
|
37
|
+
"RepairableRBD",
|
|
38
|
+
# Component models
|
|
39
|
+
"NonRepairable",
|
|
40
|
+
"Repairable",
|
|
41
|
+
"StandbyModel",
|
|
42
|
+
"RepeatedNode",
|
|
43
|
+
"RepeatedStandbyNode",
|
|
44
|
+
# Helpers
|
|
45
|
+
"PerfectReliability",
|
|
46
|
+
"PerfectUnreliability",
|
|
47
|
+
# Result types
|
|
48
|
+
"AvailabilityResult",
|
|
49
|
+
"ConfidenceInterval",
|
|
50
|
+
"Criticalities",
|
|
51
|
+
"UpDownImportance",
|
|
52
|
+
"FailureCriticalityIndex",
|
|
53
|
+
"RestorationCriticalityIndex",
|
|
54
|
+
"MaintenancePolicy",
|
|
55
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Single source of truth for the package version.
|
|
2
|
+
|
|
3
|
+
Kept deliberately free of imports so the build backend can read
|
|
4
|
+
``__version__`` statically (without importing the package and its
|
|
5
|
+
dependencies) via ``[tool.setuptools.dynamic]`` in ``pyproject.toml``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.5.0"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Shared result types for component maintenance-policy optimisations."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class MaintenancePolicy:
|
|
8
|
+
"""An optimal preventive-maintenance policy for a single component.
|
|
9
|
+
|
|
10
|
+
Returned by ``NonRepairable.optimal_replacement_policy()`` (age
|
|
11
|
+
replacement) and ``Repairable.optimal_overhaul_policy()`` (overhaul
|
|
12
|
+
under minimal repair).
|
|
13
|
+
|
|
14
|
+
Attributes
|
|
15
|
+
----------
|
|
16
|
+
interval : float
|
|
17
|
+
The optimal preventive interval: the replacement age for an
|
|
18
|
+
age-replacement policy, or the overhaul interval for a
|
|
19
|
+
minimal-repair component. ``inf`` when preventive action never
|
|
20
|
+
pays (run to failure / never overhaul).
|
|
21
|
+
cost_rate : float
|
|
22
|
+
The long-run cost per unit time under the policy. When
|
|
23
|
+
``interval`` is ``inf`` this is the limiting cost rate of running
|
|
24
|
+
without preventive action.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
interval: float
|
|
28
|
+
cost_rate: float
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.integrate import quad
|
|
3
|
+
from scipy.interpolate import interp1d
|
|
4
|
+
from scipy.optimize import minimize
|
|
5
|
+
from surpyval import ExactEventTime, NonParametric, Parametric
|
|
6
|
+
|
|
7
|
+
from repyability.maintenance import MaintenancePolicy
|
|
8
|
+
from repyability.rbd._model_utils import (
|
|
9
|
+
distribution_name,
|
|
10
|
+
is_exponential,
|
|
11
|
+
model_mean,
|
|
12
|
+
)
|
|
13
|
+
from repyability.rbd.standby_node import StandbyModel
|
|
14
|
+
|
|
15
|
+
FAILURE = 1
|
|
16
|
+
REPLACE = 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NonRepairable:
|
|
20
|
+
"""A component renewed by replacement ("as good as new").
|
|
21
|
+
|
|
22
|
+
Pairs a lifetime (reliability) model with a time-to-replace model for
|
|
23
|
+
a unit that cannot be repaired in place: every failure or planned
|
|
24
|
+
replacement fits a new unit, so each cycle is a statistical renewal.
|
|
25
|
+
|
|
26
|
+
It plays two roles:
|
|
27
|
+
|
|
28
|
+
- Standalone, it prices the classic *age-replacement* policy — replace
|
|
29
|
+
preventively at age ``t`` (planned, cost ``cp``) or on failure
|
|
30
|
+
(unplanned, cost ``cu > cp``) — via ``find_optimal_replacement()``
|
|
31
|
+
and ``optimal_replacement_policy()``.
|
|
32
|
+
- Inside ``RepairableRBD``, it is the per-component representation
|
|
33
|
+
used by the availability engine (components are "repaired" by as-new
|
|
34
|
+
replacement, which is exactly the renewal this class models).
|
|
35
|
+
|
|
36
|
+
Contrast with ``Repairable``, which models *minimal repair* ("as bad
|
|
37
|
+
as old") and the overhaul-interval policy.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self, reliability, time_to_replace=ExactEventTime.from_params(0)
|
|
42
|
+
):
|
|
43
|
+
if isinstance(reliability, Parametric):
|
|
44
|
+
self.model_parameterization = "parametric"
|
|
45
|
+
self.reliability_function = reliability.sf
|
|
46
|
+
elif isinstance(reliability, NonParametric):
|
|
47
|
+
# TODO: Allow non-interpolated?
|
|
48
|
+
self.model_parameterization = "non-parametric"
|
|
49
|
+
self.reliability_function = interp1d(
|
|
50
|
+
reliability.x, 1 - reliability.F, fill_value="extrapolate"
|
|
51
|
+
)
|
|
52
|
+
elif isinstance(reliability, StandbyModel):
|
|
53
|
+
self.model_parameterization = "non-parametric"
|
|
54
|
+
self.reliability_function = interp1d(
|
|
55
|
+
reliability.model.x,
|
|
56
|
+
1 - reliability.model.F,
|
|
57
|
+
fill_value="extrapolate",
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
raise ValueError("Unknown reliability function")
|
|
61
|
+
|
|
62
|
+
self.reliability = reliability
|
|
63
|
+
self.time_to_replace = time_to_replace
|
|
64
|
+
self.cost_rate = np.vectorize(
|
|
65
|
+
self._cost_rate,
|
|
66
|
+
doc=(
|
|
67
|
+
"Long-run cost per unit time of an age-replacement policy "
|
|
68
|
+
"with replacement age t:\n"
|
|
69
|
+
"(cp * R(t) + cu * F(t)) / integral_0^t R(u) du.\n"
|
|
70
|
+
"Vectorised over t."
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
self.__next_event_type = FAILURE
|
|
74
|
+
|
|
75
|
+
def set_costs_planned_and_unplanned(self, cp, cu):
|
|
76
|
+
if cp >= cu:
|
|
77
|
+
raise ValueError("Planned costs must be less than unplanned costs")
|
|
78
|
+
self.cp = cp
|
|
79
|
+
self.cu = cu
|
|
80
|
+
|
|
81
|
+
def avg_replacement_time(self, t):
|
|
82
|
+
if self.model_parameterization == "parametric":
|
|
83
|
+
out = quad(self.reliability_function, 0, t)[0]
|
|
84
|
+
else:
|
|
85
|
+
mask = self.reliability.x < t
|
|
86
|
+
x_less_than_t = self.reliability.x[mask]
|
|
87
|
+
dt = np.diff(x_less_than_t)
|
|
88
|
+
F = self.reliability_function(x_less_than_t[1:])
|
|
89
|
+
out = (dt * F).sum()
|
|
90
|
+
|
|
91
|
+
return out
|
|
92
|
+
|
|
93
|
+
def _cost_rate(self, t):
|
|
94
|
+
# surpyval >= 0.11 returns 1-element arrays from sf/mean/qf, and t can
|
|
95
|
+
# arrive as a 1-element array from scipy.optimize.minimize. Coerce to a
|
|
96
|
+
# plain float so the quadrature bound in avg_replacement_time() is a
|
|
97
|
+
# scalar (scipy.integrate.quad rejects array bounds).
|
|
98
|
+
t = float(np.asarray(t).item())
|
|
99
|
+
planned_costs = self.reliability_function(t) * self.cp
|
|
100
|
+
unplanned_costs = (1 - self.reliability_function(t)) * self.cu
|
|
101
|
+
avg_repl_time = self.avg_replacement_time(t)
|
|
102
|
+
return (planned_costs + unplanned_costs) / avg_repl_time
|
|
103
|
+
|
|
104
|
+
def _log_cost_rate(self, t):
|
|
105
|
+
return np.log(self._cost_rate(t))
|
|
106
|
+
|
|
107
|
+
def mean_unavailability(self) -> float:
|
|
108
|
+
return 1 - self.mean_availability()
|
|
109
|
+
|
|
110
|
+
def mean_availability(self) -> float:
|
|
111
|
+
"""Long-run availability, MTTF / (MTTF + MTTR)."""
|
|
112
|
+
if isinstance(self.reliability, NonParametric):
|
|
113
|
+
raise ValueError(
|
|
114
|
+
"Mean Availability requires a parametric reliability model"
|
|
115
|
+
)
|
|
116
|
+
mttf = model_mean(self.reliability)
|
|
117
|
+
mttr = model_mean(self.time_to_replace)
|
|
118
|
+
return mttf / (mttr + mttf)
|
|
119
|
+
|
|
120
|
+
def failure_frequency(self) -> float:
|
|
121
|
+
"""Long-run failure frequency (failures per unit time).
|
|
122
|
+
|
|
123
|
+
For an alternating renewal process (fail, repair, fail, ...) this is
|
|
124
|
+
``1 / (MTTF + MTTR)`` — one failure per mean up-down cycle.
|
|
125
|
+
"""
|
|
126
|
+
if isinstance(self.reliability, NonParametric):
|
|
127
|
+
raise ValueError(
|
|
128
|
+
"Failure frequency requires a parametric reliability model"
|
|
129
|
+
)
|
|
130
|
+
mttf = model_mean(self.reliability)
|
|
131
|
+
mttr = model_mean(self.time_to_replace)
|
|
132
|
+
return 1.0 / (mttf + mttr)
|
|
133
|
+
|
|
134
|
+
def _cost_rate_with_log_x(self, x):
|
|
135
|
+
return self._cost_rate(np.exp(x))
|
|
136
|
+
|
|
137
|
+
def _log_cost_rate_with_log_x(self, x):
|
|
138
|
+
return self._cost_rate(np.exp(x))
|
|
139
|
+
|
|
140
|
+
def find_optimal_replacement(self, options=None):
|
|
141
|
+
if self.model_parameterization == "parametric":
|
|
142
|
+
if is_exponential(self.reliability) and not (
|
|
143
|
+
getattr(self.reliability, "offset", False)
|
|
144
|
+
or getattr(self.reliability, "zi", False)
|
|
145
|
+
or getattr(self.reliability, "lfp", False)
|
|
146
|
+
):
|
|
147
|
+
# Memoryless lifetime: an old unit is statistically as good
|
|
148
|
+
# as a new one, so preventive replacement never pays.
|
|
149
|
+
return np.inf
|
|
150
|
+
if distribution_name(self.reliability) == "Weibull":
|
|
151
|
+
if self.reliability.offset:
|
|
152
|
+
pass
|
|
153
|
+
elif self.reliability.zi:
|
|
154
|
+
pass
|
|
155
|
+
elif self.reliability.lfp:
|
|
156
|
+
pass
|
|
157
|
+
elif self.reliability.params[1] <= 1:
|
|
158
|
+
return np.inf
|
|
159
|
+
# When using a parametric distribution the optimisation is
|
|
160
|
+
# straight forward. Simply find the point in the support where
|
|
161
|
+
# the cost rate is minimised. Uses quadrature to integrate!
|
|
162
|
+
mean = self.reliability.mean()
|
|
163
|
+
old_err_state = np.seterr(all="ignore")
|
|
164
|
+
res = minimize(self._cost_rate_with_log_x, np.log(mean), tol=1e-10)
|
|
165
|
+
res_log = minimize(
|
|
166
|
+
self._log_cost_rate_with_log_x, np.log(mean), tol=1e-10
|
|
167
|
+
)
|
|
168
|
+
np.seterr(**old_err_state)
|
|
169
|
+
self.optimisation_results = res
|
|
170
|
+
if res["fun"] < np.exp(res_log["fun"]):
|
|
171
|
+
optimal = np.exp(res.x[0])
|
|
172
|
+
else:
|
|
173
|
+
optimal = np.exp(res_log.x[0])
|
|
174
|
+
else:
|
|
175
|
+
# When using non-parametric estimations, it can also be straight
|
|
176
|
+
# forward. Simply find the cost rate at a number of places
|
|
177
|
+
# from the min to the max support of the model and return the
|
|
178
|
+
# value of x which has the minimum cost rate.
|
|
179
|
+
x_search = np.linspace(
|
|
180
|
+
self.reliability.x.min(), self.reliability.x.max(), 10000
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
dt = np.diff(x_search, prepend=0)
|
|
184
|
+
R = self.reliability_function(x_search)
|
|
185
|
+
avg_replacement_times = (dt * R).cumsum()
|
|
186
|
+
|
|
187
|
+
planned_costs = R * self.cp
|
|
188
|
+
unplanned_costs = (1 - R) * self.cu
|
|
189
|
+
|
|
190
|
+
costs = (planned_costs + unplanned_costs) / avg_replacement_times
|
|
191
|
+
|
|
192
|
+
optimal_idx = np.argmin(costs)
|
|
193
|
+
optimal = x_search[optimal_idx]
|
|
194
|
+
return optimal
|
|
195
|
+
|
|
196
|
+
def optimal_replacement_policy(self) -> MaintenancePolicy:
|
|
197
|
+
"""The optimal age-replacement policy as a typed result.
|
|
198
|
+
|
|
199
|
+
Returns a ``MaintenancePolicy`` whose ``interval`` is the
|
|
200
|
+
cost-optimal replacement age and whose ``cost_rate`` is the
|
|
201
|
+
long-run cost per unit time under it. When preventive replacement
|
|
202
|
+
never pays (no aging — e.g. an exponential lifetime, or a Weibull
|
|
203
|
+
with shape <= 1) the interval is ``inf`` and the cost rate is the
|
|
204
|
+
run-to-failure rate ``cu / MTTF``.
|
|
205
|
+
"""
|
|
206
|
+
if not hasattr(self, "cp"):
|
|
207
|
+
raise ValueError(
|
|
208
|
+
"costs not set: call set_costs_planned_and_unplanned"
|
|
209
|
+
"(cp, cu) first"
|
|
210
|
+
)
|
|
211
|
+
interval = self.find_optimal_replacement()
|
|
212
|
+
if np.isinf(interval):
|
|
213
|
+
rate = self.cu / model_mean(self.reliability)
|
|
214
|
+
else:
|
|
215
|
+
rate = float(self._cost_rate(interval))
|
|
216
|
+
return MaintenancePolicy(interval=float(interval), cost_rate=rate)
|
|
217
|
+
|
|
218
|
+
def reset(self):
|
|
219
|
+
self.__next_event_type = FAILURE
|
|
220
|
+
|
|
221
|
+
def next_event(self):
|
|
222
|
+
if self.__next_event_type == FAILURE:
|
|
223
|
+
self.__next_event_type = REPLACE
|
|
224
|
+
return self.reliability.random(1).item(), False
|
|
225
|
+
elif self.__next_event_type == REPLACE:
|
|
226
|
+
self.__next_event_type = FAILURE
|
|
227
|
+
return self.time_to_replace.random(1).item(), True
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Helpers describing the *capabilities* of the reliability models the RBD
|
|
2
|
+
classes consume.
|
|
3
|
+
|
|
4
|
+
Historically the RBD code branched on ``model.dist.name`` string literals
|
|
5
|
+
(e.g. ``"FixedEventProbability"``, ``"Exponential"``) scattered across several
|
|
6
|
+
modules. That couples behaviour tightly to surpyval's internal naming: a
|
|
7
|
+
rename there silently changes results here. Centralising the checks in these
|
|
8
|
+
small helpers keeps that coupling in one place (easy to audit and to cover
|
|
9
|
+
with a compatibility test) and gives the call sites intention-revealing names.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
# surpyval distribution names whose event probability does not vary with time,
|
|
17
|
+
# i.e. ``sf(t)`` is constant. These behave as fixed per-demand probabilities
|
|
18
|
+
# rather than lifetime distributions.
|
|
19
|
+
FIXED_PROBABILITY_DIST_NAMES = frozenset(
|
|
20
|
+
{"FixedEventProbability", "Bernoulli"}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def distribution_name(model) -> Optional[str]:
|
|
25
|
+
"""Return the surpyval distribution name of ``model``.
|
|
26
|
+
|
|
27
|
+
Returns ``None`` for models that do not expose a surpyval distribution
|
|
28
|
+
(e.g. a ``StandbyModel``, ``RepeatedNode``, nested RBD, or the perfect
|
|
29
|
+
reliability/unreliability helpers).
|
|
30
|
+
"""
|
|
31
|
+
dist = getattr(model, "dist", None)
|
|
32
|
+
if dist is None:
|
|
33
|
+
return None
|
|
34
|
+
return getattr(dist, "name", None)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def is_fixed_probability(model) -> bool:
|
|
38
|
+
"""True if ``model`` is a time-invariant (fixed) event probability."""
|
|
39
|
+
return distribution_name(model) in FIXED_PROBABILITY_DIST_NAMES
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_exponential(model) -> bool:
|
|
43
|
+
"""True if ``model`` is a surpyval Exponential distribution."""
|
|
44
|
+
return distribution_name(model) == "Exponential"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def model_mean(model) -> float:
|
|
48
|
+
"""The model's mean as a plain float.
|
|
49
|
+
|
|
50
|
+
Works around surpyval 0.11's ExactEventTime, whose ``mean()`` raises
|
|
51
|
+
AttributeError (its underlying dist has no ``mean``); for an exact event
|
|
52
|
+
time the mean is simply its parameter.
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
return float(np.atleast_1d(model.mean())[0])
|
|
56
|
+
except AttributeError:
|
|
57
|
+
if distribution_name(model) == "ExactEventTime":
|
|
58
|
+
return float(np.atleast_1d(model.params)[0])
|
|
59
|
+
raise
|