switchgear-thermal-model 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.3
2
+ Name: switchgear-thermal-model
3
+ Version: 0.0.1
4
+ Summary: Thermal model for switchgears.
5
+ Author: Contributors to the Switchgear Thermal Model project
6
+ Classifier: Natural Language :: English
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Requires-Dist: numpy>=2.2.5,<3
13
+ Requires-Dist: pandas>=2.0.0,<4.0.0
14
+ Requires-Dist: pydantic>=2.12.1,<3
15
+ Requires-Python: >=3.12, <4
16
+ Description-Content-Type: text/markdown
17
+
18
+ <!--
19
+ SPDX-FileCopyrightText: Contributors to the Switchgear Thermal Model project
20
+
21
+ SPDX-License-Identifier: MPL-2.0
22
+ -->
23
+
24
+ # Switchgear thermal model
25
+
26
+ [![License: MPL2.0](https://img.shields.io/badge/License-MPL2.0-informational.svg)](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/LICENSE)
27
+
28
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=alliander-opensource_switchgear-thermal-model&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=alliander-opensource_switchgear-thermal-model)
29
+ [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=alliander-opensource_switchgear-thermal-model&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=alliander-opensource_switchgear-thermal-model)
30
+ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=alliander-opensource_switchgear-thermal-model&metric=coverage)](https://sonarcloud.io/summary/new_code?id=alliander-opensource_switchgear-thermal-model)
31
+ [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=alliander-opensource_switchgear-thermal-model&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=alliander-opensource_switchgear-thermal-model)
32
+ [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=alliander-opensource_switchgear-thermal-model&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=alliander-opensource_switchgear-thermal-model)
33
+
34
+ thermal model which can simulate the temperature of the switchgear, based on
35
+ the switchgear specifications, ambient temperature and load profiles.
36
+
37
+ ## Quick start
38
+
39
+ One can install the switchgear thermal model from PyPI by running
40
+
41
+ ```bash
42
+ pip install switchgear-thermal-model
43
+ ```
44
+
45
+ ### Simple Example
46
+
47
+ The model uses an `InputProfile`, which is a combination of a datetime index,
48
+ load profile, and ambient temperature profile as input for the model.
49
+ A quick start can be made using the following code snippet:
50
+
51
+ ```python
52
+ import numpy as np
53
+
54
+ from switchgear_thermal_model.schemas.input_profile import InputProfile
55
+ from switchgear_thermal_model.switchgear import Switchgear
56
+ from switchgear_thermal_model.thermal_model import switchgear_temp
57
+
58
+ # Set up the InputProfile
59
+ start_time = np.datetime64("2024-01-01T00:00")
60
+ dt_minutes = 15
61
+ datetime_array = start_time + np.arange(96) * np.timedelta64(dt_minutes, "m")
62
+ load_array = np.full((1, 96), 2000)[0]
63
+ amb_temp_array = np.full((1, 96), 20)[0]
64
+
65
+ input_profile = InputProfile(
66
+ datetime_index=datetime_array,
67
+ ambient_temperature_profile=amb_temp_array,
68
+ load_profile=load_array,
69
+ )
70
+
71
+ # Set up a switchgear with specifications
72
+ switchgear = Switchgear(
73
+ rated_current=3150,
74
+ measured_temperature_rise=75,
75
+ temp_rise_exp=1.8,
76
+ thermal_time_constant=90
77
+ )
78
+
79
+ # run the model
80
+ result = switchgear_temp(input_profile, switchgear)
81
+ ```
82
+
83
+ ## Features
84
+
85
+ - **Creating switchgear models**: Create a switchgear model based on specified thermal parameters.
86
+ - **Temperature modeling**: Calculate conductor temperature according to IEC 62271‑306.
87
+
88
+ ## Documentation
89
+
90
+ - [Getting Started](https://alliander-opensource.github.io/switchgear-thermal-model/get_started/about/)
91
+ - [API Reference](https://alliander-opensource.github.io/switchgear-thermal-model/api_reference/switchgear_temp/)
92
+ - [Examples](https://alliander-opensource.github.io/switchgear-thermal-model/examples/quick_start/)
93
+ - [Technical Documentation](https://alliander-opensource.github.io/switchgear-thermal-model/theoretical_documentation/model_equations/)
94
+
95
+ ## License
96
+
97
+ This project is licensed under the Mozilla Public License, version 2.0 - see
98
+ [LICENSE](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/LICENSE) for details.
99
+
100
+ ## Licenses third-party libraries
101
+
102
+ This project includes third-party libraries,
103
+ which are licensed under their own respective Open-Source licenses.
104
+ SPDX-License-Identifier headers are used to show which license is applicable.
105
+
106
+ An overview of these third-party libraries and their licenses is outlined in [THIRD_PARTY_NOTICES](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/THIRD_PARTY_NOTICES.md)
107
+
108
+ ## Contributing
109
+
110
+ Please read
111
+ [CODE_OF_CONDUCT](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/CODE_OF_CONDUCT.md),
112
+ [CONTRIBUTING](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/CONTRIBUTING.md)
113
+ and
114
+ [PROJECT GOVERNANCE](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/GOVERNANCE.md)
115
+ for details on the process
116
+ for submitting pull requests to us.
117
+
118
+ ## Contact
119
+
120
+ Please read [SUPPORT](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/SUPPORT.md) for how to
121
+ get in touch with the Switchgear Thermal Model project.
@@ -0,0 +1,104 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: Contributors to the Switchgear Thermal Model project
3
+
4
+ SPDX-License-Identifier: MPL-2.0
5
+ -->
6
+
7
+ # Switchgear thermal model
8
+
9
+ [![License: MPL2.0](https://img.shields.io/badge/License-MPL2.0-informational.svg)](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/LICENSE)
10
+
11
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=alliander-opensource_switchgear-thermal-model&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=alliander-opensource_switchgear-thermal-model)
12
+ [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=alliander-opensource_switchgear-thermal-model&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=alliander-opensource_switchgear-thermal-model)
13
+ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=alliander-opensource_switchgear-thermal-model&metric=coverage)](https://sonarcloud.io/summary/new_code?id=alliander-opensource_switchgear-thermal-model)
14
+ [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=alliander-opensource_switchgear-thermal-model&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=alliander-opensource_switchgear-thermal-model)
15
+ [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=alliander-opensource_switchgear-thermal-model&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=alliander-opensource_switchgear-thermal-model)
16
+
17
+ thermal model which can simulate the temperature of the switchgear, based on
18
+ the switchgear specifications, ambient temperature and load profiles.
19
+
20
+ ## Quick start
21
+
22
+ One can install the switchgear thermal model from PyPI by running
23
+
24
+ ```bash
25
+ pip install switchgear-thermal-model
26
+ ```
27
+
28
+ ### Simple Example
29
+
30
+ The model uses an `InputProfile`, which is a combination of a datetime index,
31
+ load profile, and ambient temperature profile as input for the model.
32
+ A quick start can be made using the following code snippet:
33
+
34
+ ```python
35
+ import numpy as np
36
+
37
+ from switchgear_thermal_model.schemas.input_profile import InputProfile
38
+ from switchgear_thermal_model.switchgear import Switchgear
39
+ from switchgear_thermal_model.thermal_model import switchgear_temp
40
+
41
+ # Set up the InputProfile
42
+ start_time = np.datetime64("2024-01-01T00:00")
43
+ dt_minutes = 15
44
+ datetime_array = start_time + np.arange(96) * np.timedelta64(dt_minutes, "m")
45
+ load_array = np.full((1, 96), 2000)[0]
46
+ amb_temp_array = np.full((1, 96), 20)[0]
47
+
48
+ input_profile = InputProfile(
49
+ datetime_index=datetime_array,
50
+ ambient_temperature_profile=amb_temp_array,
51
+ load_profile=load_array,
52
+ )
53
+
54
+ # Set up a switchgear with specifications
55
+ switchgear = Switchgear(
56
+ rated_current=3150,
57
+ measured_temperature_rise=75,
58
+ temp_rise_exp=1.8,
59
+ thermal_time_constant=90
60
+ )
61
+
62
+ # run the model
63
+ result = switchgear_temp(input_profile, switchgear)
64
+ ```
65
+
66
+ ## Features
67
+
68
+ - **Creating switchgear models**: Create a switchgear model based on specified thermal parameters.
69
+ - **Temperature modeling**: Calculate conductor temperature according to IEC 62271‑306.
70
+
71
+ ## Documentation
72
+
73
+ - [Getting Started](https://alliander-opensource.github.io/switchgear-thermal-model/get_started/about/)
74
+ - [API Reference](https://alliander-opensource.github.io/switchgear-thermal-model/api_reference/switchgear_temp/)
75
+ - [Examples](https://alliander-opensource.github.io/switchgear-thermal-model/examples/quick_start/)
76
+ - [Technical Documentation](https://alliander-opensource.github.io/switchgear-thermal-model/theoretical_documentation/model_equations/)
77
+
78
+ ## License
79
+
80
+ This project is licensed under the Mozilla Public License, version 2.0 - see
81
+ [LICENSE](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/LICENSE) for details.
82
+
83
+ ## Licenses third-party libraries
84
+
85
+ This project includes third-party libraries,
86
+ which are licensed under their own respective Open-Source licenses.
87
+ SPDX-License-Identifier headers are used to show which license is applicable.
88
+
89
+ An overview of these third-party libraries and their licenses is outlined in [THIRD_PARTY_NOTICES](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/THIRD_PARTY_NOTICES.md)
90
+
91
+ ## Contributing
92
+
93
+ Please read
94
+ [CODE_OF_CONDUCT](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/CODE_OF_CONDUCT.md),
95
+ [CONTRIBUTING](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/CONTRIBUTING.md)
96
+ and
97
+ [PROJECT GOVERNANCE](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/GOVERNANCE.md)
98
+ for details on the process
99
+ for submitting pull requests to us.
100
+
101
+ ## Contact
102
+
103
+ Please read [SUPPORT](https://github.com/alliander-opensource/switchgear-thermal-model/blob/main/SUPPORT.md) for how to
104
+ get in touch with the Switchgear Thermal Model project.
@@ -0,0 +1,140 @@
1
+ # SPDX-FileCopyrightText: Contributors to the Switchgear Thermal Model project
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ [project]
6
+ name = "switchgear-thermal-model"
7
+ version = "0.0.1"
8
+ description = "Thermal model for switchgears."
9
+ authors = [{ name = "Contributors to the Switchgear Thermal Model project" }]
10
+ requires-python = ">=3.12,<4"
11
+ readme = "README.md"
12
+ classifiers = [
13
+ "Natural Language :: English",
14
+ "Operating System :: OS Independent",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Programming Language :: Python :: 3.14",
19
+ ]
20
+ dependencies = [
21
+ "numpy>=2.2.5,<3",
22
+ "pandas>=2.0.0, <4.0.0",
23
+ "pydantic>=2.12.1,<3",
24
+ ]
25
+
26
+ [dependency-groups]
27
+ dev = [
28
+ "ruff>=0.11.9,<0.16.0",
29
+ "mypy>=2.0.0,<3",
30
+ "pytest-cov>=7.1.0,<8",
31
+ "prek>=0.4.9",
32
+ "codespell>=2.4.1,<3",
33
+ "pytest>=9.0.3,<10",
34
+ "ipykernel>=7.0.1,<8",
35
+ ]
36
+ docs = [
37
+ "mkdocs>=1.6.1,<2",
38
+ "mkdocstrings-python>=2.0.0,<3",
39
+ "mkdocs-exclude==1.0.0",
40
+ "mkdocs-llmstxt>=0.5.0,<0.6",
41
+ "mkdocs-material>=9.7.6,<10",
42
+ "mkdocs-mermaid2-plugin>=1.2.1,<2",
43
+ "mkdocs-jupyter>=0.26.3,<0.27",
44
+ "matplotlib>=3.11.1,<4",
45
+ ]
46
+
47
+ [tool.uv]
48
+ exclude-newer = "7 days"
49
+
50
+ [tool.uv.build-backend]
51
+ module-name = "switchgear_thermal_model"
52
+ module-root = "."
53
+
54
+ [build-system]
55
+ requires = ["uv_build>=0.11.21,<0.12.0"]
56
+ build-backend = "uv_build"
57
+
58
+ [tool.ruff]
59
+ output-format = "github"
60
+ line-length = 120
61
+ show-fixes = true
62
+ exclude = [
63
+ ".bzr",
64
+ ".direnv",
65
+ ".eggs",
66
+ ".git",
67
+ ".git-rewrite",
68
+ ".hg",
69
+ ".ipynb_checkpoints",
70
+ ".mypy_cache",
71
+ ".nox",
72
+ ".pants.d",
73
+ ".pyenv",
74
+ ".pytest_cache",
75
+ ".pytype",
76
+ ".ruff_cache",
77
+ ".svn",
78
+ ".tox",
79
+ ".venv",
80
+ ".vscode",
81
+ "__pypackages__",
82
+ "_build",
83
+ "buck-out",
84
+ "build",
85
+ "dist",
86
+ "node_modules",
87
+ "site-packages",
88
+ "venv",
89
+ "migrations",
90
+ ]
91
+
92
+ [tool.ruff.lint]
93
+ select = [
94
+ # pycodestyle
95
+ "E",
96
+ # Pyflake
97
+ "F",
98
+ # pyupgrade
99
+ "UP",
100
+ # flake8-bugbear
101
+ "B",
102
+ # flake8-simplify
103
+ "SIM",
104
+ # isort
105
+ "I",
106
+ # pydocstyle
107
+ "D",
108
+ ]
109
+ ignore = [
110
+ # Missing docstring in public module
111
+ "D100",
112
+ # Missing docstring in public package
113
+ "D104",
114
+ # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
115
+ "D203",
116
+ # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
117
+ "D213",
118
+ ]
119
+
120
+ [tool.pytest.ini_options]
121
+ markers = ["integrationtest"]
122
+ addopts = [
123
+ "--cov",
124
+ "--cov-branch",
125
+ "--cov-report=xml",
126
+ "--cov-report=term-missing" # Coverage report incl missing lines in terminal
127
+ ]
128
+
129
+ [tool.coverage.run]
130
+ relative_files = true
131
+ omit = [
132
+ "tests/*"
133
+ ]
134
+
135
+ [tool.ruff.lint.pydocstyle]
136
+ convention = "google"
137
+
138
+ [tool.ruff.lint.per-file-ignores]
139
+ # Allow longer line length in conftest where many 1-line dictinaries are specified
140
+ "conftest.py" = ["E501"]
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: Contributors to the Switchgear Thermal Model project
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
@@ -0,0 +1,7 @@
1
+ # SPDX-FileCopyrightText: Contributors to the Switchgear Thermal Model project
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ from .input_profile import InputProfile
6
+
7
+ __all__ = ["InputProfile"]
@@ -0,0 +1,193 @@
1
+ # SPDX-FileCopyrightText: Contributors to the Transformer Thermal Model project
2
+ # SPDX-FileCopyrightText: Contributors to the Switchgear Thermal Model project
3
+ #
4
+ # SPDX-License-Identifier: MPL-2.0
5
+ #
6
+ # Modified from the Transformer Thermal Model project for use in the Switchgear Model project.
7
+ import logging
8
+ from collections.abc import Collection
9
+ from datetime import datetime
10
+ from typing import Self
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+ from pydantic import BaseModel, ConfigDict, model_validator
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ _NEGATIVE_LOAD_PROFILE_ERROR_MESSAGE = "The load profile must not contain negative values"
19
+
20
+
21
+ class InputProfile(BaseModel):
22
+ """Class containing the temperature and load profiles for the switchgear thermal model `Model()`.
23
+
24
+ This class is also capable of converting the results to a single dataframe with the timestamp as the index for
25
+ convenience.
26
+
27
+ This class is a simplified version of the transformer_thermal_model input_profile class (TTM 0.2.1).
28
+
29
+ Attributes:
30
+ datetime_index: A 1d array with the datetime index for the profiles.
31
+ load_profile: A 1d array with the load profile for the switchgear.
32
+ ambient_temperature_profile: A 1d array with the ambient temperature profile for the switchgear.
33
+
34
+ """
35
+
36
+ datetime_index: np.typing.NDArray[np.datetime64]
37
+ load_profile: np.typing.NDArray[np.float64]
38
+ ambient_temperature_profile: np.typing.NDArray[np.float64]
39
+
40
+ model_config = ConfigDict(arbitrary_types_allowed=True)
41
+
42
+ @model_validator(mode="after")
43
+ def _check_datetime_index_is_sorted(self) -> Self:
44
+ """Check if the datetime index is sorted."""
45
+ if not np.all(self.datetime_index[:-1] <= self.datetime_index[1:]):
46
+ raise ValueError("The datetime index should be sorted.")
47
+ return self
48
+
49
+ @model_validator(mode="after")
50
+ def _check_arrays_are_one_dimensional(self) -> Self:
51
+ """Check if the arrays are one-dimensional."""
52
+ if self.datetime_index.ndim != 1:
53
+ raise ValueError("The datetime_index array must be one-dimensional.")
54
+ if self.ambient_temperature_profile.ndim != 1:
55
+ raise ValueError("The ambient_temperature_profile array must be one-dimensional.")
56
+ if self.load_profile.ndim != 1:
57
+ raise ValueError("The load_profile array must be one-dimensional.")
58
+ return self
59
+
60
+ @model_validator(mode="after")
61
+ def _check_same_length_of_profiles(self) -> Self:
62
+ """Check if the length of the profiles is the same."""
63
+ if len(self.datetime_index) != len(self.ambient_temperature_profile):
64
+ raise ValueError(
65
+ f"The length of the profiles and index should be the same. Index length: {len(self.datetime_index)}, "
66
+ f"ambient temperature profile length: {len(self.ambient_temperature_profile)}"
67
+ )
68
+ if len(self.datetime_index) != len(self.load_profile):
69
+ raise ValueError(
70
+ f"The length of the profiles and index should be the same. Index length: {len(self.datetime_index)}, "
71
+ f"load profile length: {len(self.load_profile)}"
72
+ )
73
+ return self
74
+
75
+ @model_validator(mode="after")
76
+ def _check_load_profile_not_negative(self) -> Self:
77
+ """Check if the load profile contains negative values."""
78
+ if np.min(self.load_profile_array) < 0:
79
+ raise ValueError(_NEGATIVE_LOAD_PROFILE_ERROR_MESSAGE)
80
+ return self
81
+
82
+ @classmethod
83
+ def create(
84
+ cls,
85
+ datetime_index: Collection[datetime],
86
+ load_profile: Collection[float],
87
+ ambient_temperature_profile: Collection[float],
88
+ ) -> Self:
89
+ """Create an InputProfile.
90
+
91
+ Args:
92
+ datetime_index: The datetime index for the profiles.
93
+ load_profile: The load profile for the switchgear.
94
+ ambient_temperature_profile: The ambient temperature profile for the switchgear.
95
+
96
+ Returns:
97
+ An InputProfile object.
98
+
99
+ Example: Creating an InputProfile from collections.
100
+ ```python
101
+ >>> from datetime import datetime
102
+ >>> from switchgear_thermal_model.schemas import InputProfile
103
+
104
+ >>> datetime_index = [
105
+ ... datetime(2023, 1, 1, 0, 0),
106
+ ... datetime(2023, 1, 1, 1, 0),
107
+ ... datetime(2023, 1, 1, 2, 0),
108
+ ... ]
109
+ >>> load_profile = [0.8, 0.9, 1.0]
110
+ >>> ambient_temperature_profile = [25.0, 24.5, 24.0]
111
+ >>> input_profile = InputProfile.create(
112
+ ... datetime_index=datetime_index,
113
+ ... load_profile=load_profile,
114
+ ... ambient_temperature_profile=ambient_temperature_profile,
115
+ ... )
116
+ >>> input_profile
117
+ InputProfile(datetime_index=array(['2023-01-01T00:00:00.000000',
118
+ '2023-01-01T01:00:00.000000', '2023-01-01T02:00:00.000000'],
119
+ dtype='datetime64[us]'), ambient_temperature_profile=array([25. , 24.5, 24. ]),
120
+ load_profile=array([0.8, 0.9, 1. ]))
121
+
122
+ ```
123
+
124
+ Example: Directly creating an InputProfile object using numpy arrays.
125
+ ```python
126
+ >>> import numpy as np
127
+ >>> from datetime import datetime
128
+ >>> from switchgear_thermal_model.schemas import InputProfile
129
+
130
+ >>> input_profile = InputProfile(
131
+ ... datetime_index=np.array(
132
+ ... [
133
+ ... datetime(2023, 1, 1, 0, 0),
134
+ ... datetime(2023, 1, 1, 1, 0),
135
+ ... datetime(2023, 1, 1, 2, 0)
136
+ ... ],
137
+ ... dtype=np.datetime64,
138
+ ... ),
139
+ ... load_profile=np.array([0.8, 0.9, 1.0], dtype=float),
140
+ ... ambient_temperature_profile=np.array([25.0, 24.5, 24.0], dtype=float)
141
+ ... )
142
+ >>> input_profile
143
+ InputProfile(datetime_index=array(['2023-01-01T00:00:00.000000',
144
+ '2023-01-01T01:00:00.000000', '2023-01-01T02:00:00.000000'],
145
+ dtype='datetime64[us]'), ambient_temperature_profile=array([25. , 24.5, 24. ]),
146
+ load_profile=array([0.8, 0.9, 1. ]))
147
+
148
+ ```
149
+ """
150
+ return cls(
151
+ datetime_index=np.array(datetime_index, dtype=np.datetime64),
152
+ load_profile=np.array(load_profile, dtype=float),
153
+ ambient_temperature_profile=np.array(ambient_temperature_profile, dtype=float),
154
+ )
155
+
156
+ @classmethod
157
+ def from_dataframe(cls, df: pd.DataFrame) -> Self:
158
+ """Create an InputProfile from a dataframe."""
159
+ required_columns = {"datetime_index", "load_profile", "ambient_temperature_profile"}
160
+ missing_columns = required_columns - set(df.columns)
161
+ if missing_columns:
162
+ raise ValueError(f"The dataframe is missing the following required columns: {', '.join(missing_columns)}")
163
+
164
+ return cls(
165
+ datetime_index=df["datetime_index"].to_numpy(),
166
+ load_profile=df["load_profile"].to_numpy(),
167
+ ambient_temperature_profile=df["ambient_temperature_profile"].to_numpy(),
168
+ )
169
+
170
+ @property
171
+ def load_profile_array(self) -> np.typing.NDArray[np.float64]:
172
+ """Return the single load profile for the switchgear."""
173
+ return self.load_profile
174
+
175
+ @property
176
+ def dt(self) -> np.ndarray:
177
+ """The time step between the data points in minutes, with zero for the first element.
178
+
179
+ Returns:
180
+ np.ndarray[np.timedelta64]: The time step between the data points in minutes.
181
+
182
+ """
183
+ return np.diff(self.datetime_index, prepend=self.datetime_index[0]).astype("timedelta64[m]").astype(float)
184
+
185
+ @property
186
+ def ambient_temperature_is_constant(self) -> bool:
187
+ """Check if the ambient temperature profile is constant up to a tolerance of 1e-6."""
188
+ tolerance = 1e-6
189
+ return self.ambient_temperature_profile.max() - self.ambient_temperature_profile.min() <= tolerance
190
+
191
+ def __len__(self) -> int:
192
+ """Return the length of the datetime index."""
193
+ return len(self.datetime_index)
@@ -0,0 +1,45 @@
1
+ # SPDX-FileCopyrightText: Contributors to the Switchgear Thermal Model project
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class Switchgear(BaseModel):
9
+ """A switchgear to model the thermal behavior of a switchgear component."""
10
+
11
+ rated_current: float = Field(..., description="Rated current of the switchgear in Amperes (A).")
12
+ measured_temperature_rise: float = Field(
13
+ ...,
14
+ description=(
15
+ "Measured temperature rise of the switchgear in Kelvin (K). "
16
+ "How many degrees the switchgear temperature rises above the ambient temperature "
17
+ "when operating its rated current."
18
+ ),
19
+ )
20
+ temp_rise_exp: float = Field(
21
+ ...,
22
+ description=(
23
+ "Temperature rise exponent for the switchgear. Determines how the temperature rise scales with the load. "
24
+ "A value of 1.0 indicates a linear relationship, while values greater than 1.0 indicate a nonlinear "
25
+ "relationship (e.g., quadratic or cubic)."
26
+ ),
27
+ )
28
+ thermal_time_constant: float = Field(
29
+ ...,
30
+ description=(
31
+ "Thermal time constant of the switchgear in minutes (m). "
32
+ "This determines how quickly the switchgear temperature responds to changes in load."
33
+ ),
34
+ )
35
+
36
+ def __init__(
37
+ self, rated_current: float, measured_temperature_rise: float, temp_rise_exp: float, thermal_time_constant: float
38
+ ) -> None:
39
+ """Initialize the switchgear with its thermal parameters."""
40
+ super().__init__(
41
+ rated_current=rated_current,
42
+ measured_temperature_rise=measured_temperature_rise,
43
+ temp_rise_exp=temp_rise_exp,
44
+ thermal_time_constant=thermal_time_constant,
45
+ )
@@ -0,0 +1,54 @@
1
+ # SPDX-FileCopyrightText: Contributors to the Switchgear Thermal Model project
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ from switchgear_thermal_model.schemas.input_profile import InputProfile
9
+ from switchgear_thermal_model.switchgear import Switchgear
10
+
11
+
12
+ def switchgear_temp(data: InputProfile, switchgear: Switchgear) -> pd.Series:
13
+ """Simulate the temperature profile of a circuit breaker component.
14
+
15
+ This function simulates the temperature profile of the most critical thermal
16
+ point of a certain circuit breaker for a specific ambient temperature
17
+ profile, load profile and circuit breaker specifications. Since the
18
+ switchgears still follow a minimal thermal model, the solution for any
19
+ component in the switchgears can be modeled similarly. Hence, the thermal
20
+ profile can be modeled for a circuit breaker, or disconnector. It just
21
+ depends on the provided specifications. In particular, the measured
22
+ temperature rise.
23
+
24
+ Args:
25
+ data (InputProfile): input profile containing the ambient temperature
26
+ profile and load profile, coupled to a datetime index.
27
+ switchgear (Switchgear): Switchgear object containing the rated current,
28
+ measured temperature rise, temperature rise exponent, and thermal time
29
+ constant.
30
+
31
+ Returns:
32
+ np.ndarray
33
+ array of modeled temperatures for the circuit breaker at each timestep
34
+ in the ambient temperature profile
35
+ """
36
+ initial_temperature = data.ambient_temperature_profile[0]
37
+ modeled_temperature = np.zeros(len(data.ambient_temperature_profile))
38
+
39
+ modeled_temperature[0] = initial_temperature
40
+ normalised_load = data.load_profile / switchgear.rated_current
41
+ temperature_asymptote = (
42
+ switchgear.measured_temperature_rise * (normalised_load**switchgear.temp_rise_exp)
43
+ + data.ambient_temperature_profile
44
+ )
45
+ exponent = 1 - np.exp(-data.dt / switchgear.thermal_time_constant)
46
+
47
+ for i in range(1, len(data.ambient_temperature_profile)):
48
+ new_temperature = (temperature_asymptote[i] - modeled_temperature[i - 1]) * exponent[i] + modeled_temperature[
49
+ i - 1
50
+ ]
51
+
52
+ modeled_temperature[i] = new_temperature
53
+
54
+ return pd.Series(modeled_temperature, index=data.datetime_index)