-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Below is a detailed description and design suggestion of implementing an optional variable tap position into the model, which was a requested feature. If you are interested in contributing to TTM, feel free to take up this feature request! the design proposal below should help you to get started.
Introduction and description of the problem:
The goal of this feature is to be able to take the tap position of the transformer into account when doing a thermal modelling. Currently a single load_loss is required as input, and it is assumed that this load_loss is always valid under nominal conditions. In truth, the load loss is dependent on the tap position and which will change when the transformer is in operation to deal with voltage variations in the grid.
When the tap position of a transformer is monitored, it is possible not only have a time series of the load, but also the tap position. When this combination is available it can be beneficial for model accuracy to make the load loss tap-position-dependent to make it more accurate, and with it, create a more accurate thermal model.
To do this a few things are required from the user:
- A tap position time series must be available
- At least three load loss measurements at different tap positions must be known for the transformer in question. For power transformers, these are mostly available in FAT reports, and sometimes even more than three load losses are measured.
When a few load loss measurements are available, the load loss at other tap positions can be estimated with a linear interpolation as suggested in the loading guide (IEC 60076-7, Figure 15).
Feature design
Specifications
The suggestion is to take a similar approach as is done with the variable cooling stage. First of all, we define a new class which can be used to combine a tap position and its corresponding load loss:
class TapPosition(BaseModel):
"""A class that defines a tap position and the corresponding load loss."""
tap_position: int
load_loss: float | NoneWe can then define a new class, similar to the ONAFSwitch class:
class TapSwitch:
"""A class defining the necessary inputs for the variable tap position
- tap_status: a list of integers, which denote that tap position at different
points in time. This list must have the same length as the load profile
- tap_losses: a list op TapPositions, which known and unknown load losses
"""
tap_status: list[int]
tap_losses: list[TapPosition]After the user has created a TapSwitch object. We should have a property function of this class which sorts the list based on the tap_position, for example:
@property
def sort_load_loss(self) -> None:
self.tap_losses = sorted(self.tap_losses, key = lambda tap_position: tap_position.tap_position)With the list sorted, another property function should be used to calculate missing load_losses in the tap_losses list.
@property
def interpolate_load_losses(self) -> None:
# write function here that performs the interpolation of load_losses for all
# values of the tap_losses list, where the TapPosition.load_loss is None.
self.tap_losses = updated_tap_lossesfor each missing load loss, this interpolation can be performed as:
Lastly, we want one more class that can control the switching logic between load_losses during the model run. TapSwitchController will therefore need an init function and a function that determines the current load_loss based on the tap position:
class TapswitchController:
def __init__(
self,
tap_switch: TapSwitch,
):
self.tap_switch = tap_switch
def check_switch_and_get_new_specs(
self,
index: int # time series index
):
# read the current tap position
current_tap = tap_switch.tap_status[index]
current_loss = tap_switch.tap_losses[index of current_tap].tap_losses # something like this
return current_lossThis class should be initialized as an attribute to the transformer class if a TapSwitch is given in the initialization, similar to the ONAN/ONAF functionality. Note that we should also build a test where we check that the tap_status list only contains tap positions which are defined in the tap_losses list of the TapSwitch class.
NOTE:: It might be a good idea to, instead of making a TapSwitch and TapSwitchController class, combine all functionality in the TapSwitch class. As the only input for the controller class here is the TapSwitch
Running the model
As is done with the variable cooling stage, we can add the TapSwitch class as an optional variable to the PowerTransformer class. When given to the power transformer class, a Model class initialized with a PowerTransformer with a TapSwitch should indicate that a variable tap position is used. Using the model should then work something like this:
# create some form of tap position data
tap_positions = [1, 2, 3, 5, 6]
corresponding_load_losses = [1000, None, 800, None, 600]
# load tap time series data
tap_time_series = load_some_tap_time_series()
# create a list of TapPosition objects
tap_losses = []
for i in range(len(tap_positions)):
tap_losses.append(TapPosition(
tap_position = tap_positions[i],
load_loss = corresponding_load_losses[i]))
# initialize a TapSwitch object
tap_switch = TapSwitch(tap_status = tap_time_series, tap_losses = tap_losses)
# initialize a Transformer object with the tap switch
my_transformer = PowerTransformer(
user_specs = normal_transformer_specs,
cooling_type = CoolerType.ONAN, #or ONAF
tap_switch_settings = tap_switch # should be a new optional variable for PowerTransformer only
)Some adjustments should be made to the check_switch_and_get_new_specs() function in the Transformer class. Currently, it checks the onan onaf switch, but we can also add a check for TapSwitch here, where the check_switch_and_get_new_specs() function of the TapSwitchController class can be checked if the transformer has this object as an attribute.
Threewinding model complications
As these things are more complicated for three-winding transformer, this needs to be taken into account. A suggested way to deal with this is to define multiple TapSwitch objects:
- HV-MV losses
- MV-LV losses
- HV-LV losses
then, we need to make this an optional input to the ThreeWindingTransformer class. One way to do this is to make the class expect optionally not a TapSwitch as we did for 2 winding, but a list or tuple of TapSwitch classes.
For the TapSwitchController, we need to somehow differentiatie how it handles logic when it is part of a ThreeWindingTransformer or a PowerTransformer. This can for example be done by adding a three_winding_flag: bool boolean that tells it whether it needs to use threewinding logic or twowinding logic. Another way to do it, is to make a base TapSwitchController class with two child classes.
Choosing the first option, we could update the class as:
class TapswitchController:
def __init__(
self,
tap_switch: tuple(TapSwitch),
three_winding_flag: bool
):
self.tap_switch = tap_switch
def check_switch_and_get_new_specs(
self,
index: int # time series index
):
current_tap = tap_switch.tap_status[index]
if three_winding_flag:
current_hv_mv_loss = tap_switch[0].tap_losses[index of current_tap].tap_losses
current_hv_lv_loss = tap_switch[1].tap_losses[index of current_tap].tap_losses
current_mv_lv_loss = tap_switch[2].tap_losses[index of current_tap].tap_losses
return current_hv_mv_loss, current_hv_lv_loss, current_mv_lv_loss
else:
current_loss = tap_switch[0].tap_losses[index of current_tap].tap_losses # something like this
return current_lossAfter calling this function and getting the losses, note that they must be inserted into the TransformerSpecifications class. For the three winding transformer, note that also the load losses for each winding must be recalcuted using the _get_loss_hc(), _get_loss_mc() and get_loss_lc() functions.