Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2ce22ab
chore: bump project version to 0.2.1
Harm1995 Sep 23, 2025
e877d79
refactor: move f1 and f2 calculations back to the for-loop to allow f…
Harm1995 Sep 25, 2025
927337c
feature: add the option for ONAF_switch
Harm1995 Sep 25, 2025
f0b25d8
feat: implement temperature dependent time constants for ONAF cooling…
Harm1995 Sep 25, 2025
22b7401
first split introduced
Harm1995 Sep 29, 2025
6f443a5
refactor: move all calculations to for-loop to allow for parameter up…
Harm1995 Sep 29, 2025
7e97e54
feature: implement the basis for switching between ONAN and ONAF
Harm1995 Sep 29, 2025
a767a74
refactor: switch to ONAF as default for ONAN/ONAF
Harm1995 Oct 2, 2025
4d8eb71
feature: implement fan_status option for ONAn/ONAF
Harm1995 Oct 2, 2025
302e4fa
fix: move hotspot calculations to the for loop
Harm1995 Oct 2, 2025
0e16eeb
tests: add first basic tests
Harm1995 Oct 2, 2025
2b9f742
fix: fix style issues
Harm1995 Oct 6, 2025
d786d94
feat: Enhance transformer cooling logic with ONAN/ONAF switch support
Harm1995 Oct 9, 2025
9519874
fix: make sure parameter names match regular expressions
Harm1995 Oct 9, 2025
1091465
fix: make sure the onan onaf switch also happens for the hotspot calc…
Harm1995 Oct 9, 2025
5ed3dba
fix: add comments and fix bug with deep copy
Harm1995 Oct 15, 2025
63a9348
Merge branch 'main' into feature/cooling_switch_temperature_based
Harm1995 Oct 15, 2025
c8ec520
docs: update test
Harm1995 Oct 15, 2025
c676755
docs: update onan onaf example with three winding transformer
Harm1995 Oct 16, 2025
b2ac192
fix: fixed some minor inconsistancies in the model
Harm1995 Oct 16, 2025
5fd9f8c
fix: replace duplicate strings with constant
Harm1995 Oct 16, 2025
0b38acd
fix: add tests and use clearer naming
Harm1995 Oct 20, 2025
5a28ec2
docs: add extra comments in the example
Harm1995 Oct 20, 2025
5f68107
docs: add documentation for cooler attribute
Harm1995 Oct 21, 2025
9ff6780
add documentation in about.md
Harm1995 Oct 21, 2025
2328446
docs: add extra documentation
Harm1995 Oct 22, 2025
4bca54b
info: add warning for outdated architecture
Harm1995 Oct 22, 2025
e3c057e
tests: add an ONAN/ONAF test with given top_oil_temp
Harm1995 Oct 22, 2025
bc65cb5
fix: prevent duplication and add test
Harm1995 Oct 22, 2025
eab98b9
fix: prevent duplication of ONAN parameters
Harm1995 Oct 22, 2025
9b46226
merge main
Harm1995 Oct 22, 2025
554ea34
refactor: rename ONAN/ONAF switches to more logical names
Harm1995 Oct 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ repos:

- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.1
rev: v0.14.2
hooks:
# Run the linter.
- id: ruff
Expand Down
3 changes: 3 additions & 0 deletions docs/api_reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ SPDX-License-Identifier: MPL-2.0

# The architecture of Transformer Thermal Model

> **⚠️ Warning:**
> This overview is partly outdated and will be updated soon.

Comment on lines +9 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not update now immediately? Otherwise it will be discoupled from this PR and probably forgotten.

Tip: I used CoPilot (as an agent) with a lot of this to set up. That really saved me a lot of work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a story on our backlog to update the architecture.md .

```mermaid
stateDiagram-v2
direction TB
Expand Down
9 changes: 9 additions & 0 deletions docs/api_reference/cooling_switch_controller.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!--
SPDX-FileCopyrightText: Contributors to the Transformer Thermal Model project

SPDX-License-Identifier: MPL-2.0
-->

# Cooling switch controller

::: transformer_thermal_model.transformer.cooling_switch_controller
563 changes: 563 additions & 0 deletions docs/examples/example_ONAN_ONAF_switch.ipynb

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions docs/examples/example_ONAN_ONAF_switch.ipynb.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SPDX-FileCopyrightText: Contributors to the Transformer Thermal Model project

SPDX-License-Identifier: MPL-2.0
6 changes: 3 additions & 3 deletions docs/examples/power_transformer_example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand All @@ -222,7 +222,7 @@
"ax.set_ylabel(\"Temperature [C]\")\n",
"ax.hlines(120, datetime_index[0], datetime_index[-1], linestyles=\"dashed\", label=\"Hot-spot limit\", color=\"blue\")\n",
"ax.hlines(105, datetime_index[0], datetime_index[-1], linestyles=\"dashed\", label=\"Top-oil limit\", color=\"green\")\n",
"ax.legend(loc=\"lower left\");"
"ax.legend(loc=\"lower left\")"
]
},
{
Expand Down Expand Up @@ -292,7 +292,7 @@
"ax.set_title(\n",
" f\"Aging profile with total aging of {round(total_aging, 2)} days over a\"\n",
" \" total of {len(set(aging_profile.index.date))} days.\"\n",
");"
")"
]
}
],
Expand Down
244 changes: 244 additions & 0 deletions docs/get_started/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,250 @@ hot_spot_temp_profile = results.hot_spot_temp_profile
Note, how the top oil temperature we receive as the output `results.top_oil_temp_profile` exactly matches
the top oil temperature we provided as the input.

### Model a transformer that switches between ONAN and ONAF

Some transformers operate with forced cooling (ONAF) only when needed. When the cooling fans are OFF, the transformer
is in ONAN mode. When the fans are ON, the transformer is in ONAF mode. The library supports modelling a transformer
that dynamically switches between these modes using the `CoolingSwitchController` and an `CoolingSwitchSettings` configuration
object.

You can configure switching in two mutually exclusive ways:

1. **Fan status schedule** (historical or planned operation): provide a list of booleans (`fans_status`) indicating
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would recommend a more descriptive name e.g. fan_on, that directly tells you that True stands for fan_on. fans_status is a bit vague, not sure if True means on or off.

for each time step whether the fans are ON (`True`) or OFF (`False`).
2. **Temperature threshold control**: provide activation and deactivation temperatures. The fans turn ON when the
top‑oil temperature reaches the activation temperature and turn OFF when it falls below the deactivation temperature.

In both cases, you must also supply a set of ONAN parameters (`onan_parameters`) describing the specification values
that differ when the fans are OFF (such as nominal load, winding oil gradient, hot‑spot factor, losses, and time
constants).

The `CoolingSwitchController` keeps a deep copy of the original ONAF specifications and, whenever a switch event
occurs, applies either the ONAN parameters or restores the original ONAF specifications. Switching is evaluated at
each time step during the thermal model run, so temperature profiles reflect the current cooling mode immediately.

#### 1. Switching using a fan status schedule

```python
import pandas as pd

from transformer_thermal_model.cooler import CoolerType
from transformer_thermal_model.model import Model
from transformer_thermal_model.schemas import InputProfile, UserTransformerSpecifications
from transformer_thermal_model.schemas.thermal_model import CoolingSwitchSettings, ONANParameters
from transformer_thermal_model.transformer import PowerTransformer

# User (ONAF) specifications
user_specs = UserTransformerSpecifications(
load_loss=1000,
nom_load_sec_side=1500,
no_load_loss=200,
amb_temp_surcharge=20,
time_const_windings=10,
)

# ONAN specifications used when fans are OFF (values that differ from ONAF mode)
onan_params = ONANParameters(
top_oil_temp_rise=65, # Different top‑oil rise
time_const_oil=8, # Different oil time constant
time_const_windings=10,
load_loss=1000,
nom_load_sec_side=1200, # Lower nominal current in ONAN mode
winding_oil_gradient=20,
hot_spot_fac=1.3,
)

# Create a schedule: first half OFF (ONAN), second half ON (ONAF)
fans_status = [False]*2*24*7 + [True]*2*24*7 # Example for a week with 15-min intervals

onaf_switch = CoolingSwitchSettings(
fans_status=fans_status,
onan_parameters=onan_params,
)

transformer = PowerTransformer(
user_specs=user_specs,
cooling_type=CoolerType.ONAF, # Overall transformer supports ONAF
cooling_switch_settings=onaf_switch,
)
one_week = 4*24*7
datetime_index = pd.date_range("2020-01-01", periods=one_week, freq="15min")

nominal_load = 100
load_points = pd.Series([nominal_load] * one_week, index=datetime_index)
ambient_temp = 21
temperature_points = pd.Series([ambient_temp] * one_week, index=datetime_index)

profile_input = InputProfile.create(
datetime_index = datetime_index,
load_profile = load_points,
ambient_temperature_profile = temperature_points,
)

model = Model(transformer=transformer, temperature_profile=profile_input)
results = model.run()
```

During the run the model applies ONAN parameters for the initial OFF section,
then switches to the original ONAF specs when `fans_status` becomes `True`.

##### 2. Switching using temperature thresholds

```python
import pandas as pd

from transformer_thermal_model.cooler import CoolerType
from transformer_thermal_model.model import Model
from transformer_thermal_model.schemas import InputProfile, UserTransformerSpecifications
from transformer_thermal_model.schemas.thermal_model import CoolingSwitchConfig, CoolingSwitchSettings, ONANParameters
from transformer_thermal_model.transformer import PowerTransformer

# User (ONAF) specifications
user_specs = UserTransformerSpecifications(
load_loss=1000,
nom_load_sec_side=1500,
no_load_loss=200,
amb_temp_surcharge=20,
time_const_windings=10,
)

onan_params = ONANParameters(
top_oil_temp_rise=65,
time_const_oil=8,
time_const_windings=10,
load_loss=1000,
nom_load_sec_side=1200,
winding_oil_gradient=20,
hot_spot_fac=1.3,
)

# Fans turn ON at 70 °C and OFF again at 60 °C
threshold = CoolingSwitchConfig(activation_temp=70, deactivation_temp=60)

onaf_switch = CoolingSwitchSettings(
temperature_threshold=threshold,
onan_parameters=onan_params,
)

transformer = PowerTransformer(
user_specs=user_specs,
cooling_type=CoolerType.ONAF
,
cooling_switch_settings=onaf_switch,
)
one_week = 4*24*7
datetime_index = pd.date_range("2020-01-01", periods=one_week, freq="15min")

nominal_load = 100
load_points = pd.Series([nominal_load] * one_week, index=datetime_index)
ambient_temp = 21
temperature_points = pd.Series([ambient_temp] * one_week, index=datetime_index)


profile_input = InputProfile.create(
datetime_index = datetime_index,
load_profile = load_points,
ambient_temperature_profile = temperature_points,
)

results = Model(transformer=transformer, temperature_profile=profile_input).run()
```

Each time step the controller compares the previous and current top‑oil temperature to the
activation / deactivation thresholds and switches mode accordingly.

#### Three‑winding transformers

For three‑winding transformers a dedicated `ThreeWindingCoolingSwitchSettings` and `ThreeWindingONANParameters`
are available, allowing you to specify ONAN parameters per winding (LV, MV, HV) and the separate load losses.
Usage is analogous:

```python
import pandas as pd

from transformer_thermal_model.cooler import CoolerType
from transformer_thermal_model.model import Model
from transformer_thermal_model.schemas import (
ThreeWindingInputProfile,
UserThreeWindingTransformerSpecifications,
WindingSpecifications,
)
from transformer_thermal_model.schemas.thermal_model.onaf_switch import (
ThreeWindingCoolingSwitchSettings,
ThreeWindingONANParameters,
)
from transformer_thermal_model.transformer import ThreeWindingTransformer

onan_parameters = ThreeWindingONANParameters(
lv_winding=WindingSpecifications(
nom_load=900, winding_oil_gradient=20, hot_spot_fac=1.2,
time_const_winding=1, nom_power=1000),
mv_winding=WindingSpecifications(
nom_load=900, winding_oil_gradient=20, hot_spot_fac=1.2,
time_const_winding=1, nom_power=1000),
hv_winding=WindingSpecifications(
nom_load=1800, winding_oil_gradient=20, hot_spot_fac=1.2,
time_const_winding=1, nom_power=2000),
top_oil_temp_rise=60,
time_const_oil=150,
load_loss_mv_lv=100,
load_loss_hv_lv=100,
load_loss_hv_mv=100,
)

switch_cfg = ThreeWindingCoolingSwitchSettings(
fans_status=[False]*144 + [True]*144,
onan_parameters=onan_parameters,
)

user_specs = UserThreeWindingTransformerSpecifications(
no_load_loss=20,
amb_temp_surcharge=10,
lv_winding=WindingSpecifications(
nom_load=1000, winding_oil_gradient=20, hot_spot_fac=1.2,
time_const_winding=1, nom_power=1000),
mv_winding=WindingSpecifications(
nom_load=1000, winding_oil_gradient=20, hot_spot_fac=1.2,
time_const_winding=1, nom_power=1000),
hv_winding=WindingSpecifications(
nom_load=2000, winding_oil_gradient=20, hot_spot_fac=1.2,
time_const_winding=1, nom_power=2000),
load_loss_hv_lv=100,
load_loss_hv_mv=100,
load_loss_mv_lv=100,
)

transformer = ThreeWindingTransformer(
user_specs=user_specs,
cooling_type=CoolerType.ONAF,
cooling_switch_settings=switch_cfg,
)

# Create the input profile for the three-winding transformer
datetime_index = [pd.to_datetime("2025-07-01 00:00:00") + pd.Timedelta(minutes=15 * i) for i in range(0, 288)]
profile_input = ThreeWindingInputProfile.create(
datetime_index=datetime_index,
ambient_temperature_profile=pd.Series(data=900, index=datetime_index),
load_profile_high_voltage_side=pd.Series(data=500, index=datetime_index),
load_profile_middle_voltage_side=pd.Series(data=500, index=datetime_index),
load_profile_low_voltage_side=pd.Series(data=300, index=datetime_index),
)

results = Model(transformer=transformer, temperature_profile=profile_input).run()
```

#### When should you use switching?

Use dynamic switching when:

- You want more realistic temperature profiles under partial cooling operation.
- You need to assess thermal limits or aging for both fan operating states.
- You are performing what‑if analyses on fan activation strategy.

If your transformer always runs with fans engaged (forced cooling), simply use `CoolerType.ONAF` without a switch configuration.

See the example notebook `examples/example_ONAN_ONAF_switch.ipynb` for a full demonstration with plots.

## License

This project is licensed under the Mozilla Public License, version 2.0 - see
Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ nav:
- Aging: api_reference/aging.md
- Components: api_reference/components.md
- Cooler: api_reference/cooler.md
- ONAN/ONAF Switch: api_reference/cooling_switch_controller.md
- Toolbox: api_reference/toolbox.md
- Examples:
- Quick start: examples/quickstart.ipynb
Expand All @@ -65,6 +66,7 @@ nav:
- A three winding transformer: examples/three-winding-calculation.ipynb
- Hot-spot factor calibration: examples/hot-spot_calibration.ipynb
- Modelling with an initial state: examples/example_initial_state.ipynb
- Switch between ONAN and ONAF state: examples/example_ONAN_ONAF_switch.ipynb
- Theoretical Documentation:
- Overview: theoretical_documentation/overview.md
- Transformer definition: theoretical_documentation/transformer_definition.md
Expand Down
Loading
Loading