Skip to content

cable_soil

CableSoil

CableSoil(conductor: CableConductorProperties, layer_properties: dict[CableLayer, CableLayerProperties], layer_metrics: CableLayerMetrics, cable_type: CableType, grid_counts: dict[CableLayer, int])

Bases: Cable

Finite difference cable model with soil discretization.

Source code in cable_thermal_model/model/cables/cable.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def __init__(
    self,
    conductor: CableConductorProperties,
    layer_properties: dict[CableLayer, CableLayerProperties],
    layer_metrics: CableLayerMetrics,
    cable_type: CableType,
    grid_counts: dict[CableLayer, int],
) -> None:
    """Initialize the Cable with conductor properties, layer data, and grid resolution.

    Args:
        conductor (CableConductorProperties): Conductor properties of the cable.
        layer_properties (dict[CableLayer, CableLayerProperties]): Mapping of cable layers to their properties.
        layer_metrics (CableLayerMetrics): Geometric and calculated metrics for the cable layers.
        cable_type (CableType): The type of the cable.
        grid_counts (dict[CableLayer, int]): Number of grid points per cable layer.

    """
    super().__init__(conductor, layer_properties, layer_metrics, cable_type)

    self._validate_grid_counts(grid_counts)

    self._grid_counts = grid_counts
    self._radii_grid = np.array([], dtype=float)
    self._inter_radii_grid = np.array([], dtype=float)
    self._surface_area_grid = np.array([], dtype=float)
    self._capacity_grid = np.array([], dtype=float)
    self._rho_grid = np.array([], dtype=float)

    self._upper_diagonal = np.array([], dtype=float)
    self._base_diagonal = np.array([], dtype=float)
    self._lower_diagonal = np.array([], dtype=float)
    self._finite_difference_matrix_diagonals_outdated = True

    self._heating_vector = np.array([], dtype=float)

    self._set_calculated_fields()

integrate_timestep

integrate_timestep(previous_solution: ndarray, time_step: float, solution_at_boundary: float) -> ndarray

This method solves the finite difference approximation to the heat equation using the implicit Euler method.

For optimization purposes, the method uses the scipy.linalg.solve_banded method to solve the linear system. This means the three diagonals of finite difference matrix A are instead stored in a (3, N) array, where N is the length of the diagonal.

Parameters:

Name Type Description Default
previous_solution ndarray

The solution of the heat equation [°C] at the previous timestep (t).

required
time_step float

The size of the time steps [s] in the linearized time grid.

required
solution_at_boundary float

The solution at the boundary grid point [°C] used as a boundary condition.

required

Returns:

Type Description
ndarray

np.ndarray: The solution [°C] to the heat equation at the next timestep (t+1) for all grid points except the final grid point, at which a boundary condition is enforced.

Source code in cable_thermal_model/model/cables/cable_soil.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def integrate_timestep(
    self,
    previous_solution: np.ndarray,
    time_step: float,
    solution_at_boundary: float,
) -> np.ndarray:
    """This method solves the finite difference approximation to the heat equation using the implicit Euler method.

    For optimization purposes, the method uses the scipy.linalg.solve_banded method to solve the linear system.
    This means the three diagonals of finite difference matrix A are instead stored in a (3, N) array, where
    N is the length of the diagonal.

    Args:
        previous_solution (np.ndarray): The solution of the heat equation [°C] at the
            previous timestep (t).
        time_step (float): The size of the time steps [s] in the linearized
            time grid.
        solution_at_boundary (float): The solution at the boundary grid point [°C] used as a boundary condition.

    Returns:
        np.ndarray: The solution [°C] to the heat equation at the next timestep (t+1) for all grid points except
            the final grid point, at which a boundary condition is enforced.

    """
    A = self._get_processed_matrix(time_step=time_step)
    b = self._heating_vector.copy()
    b[-1] += self._upper_diagonal_last_element * solution_at_boundary
    b = self._capacity_grid[:-1] * previous_solution[:-1] + time_step * b

    return np.append(self._solve_system(A=A, b=b), solution_at_boundary)

update_soil_properties

update_soil_properties(soil_rho: float, soil_c: float, temperature_grid: ndarray, soil_drying: bool = False) -> None

This method updates the soil properties around a cable.

Parameters:

Name Type Description Default
soil_rho float

The thermal resistivity of the soil that is not dried out

required
soil_c float

The thermal capacity of the soil that is not dried out

required
temperature_grid ndarray

The temperature grid for the cable, as calculated for a given timestep.

required
soil_drying bool

Whether the scenario takes soil drying into account.

False
Source code in cable_thermal_model/model/cables/cable_soil.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def update_soil_properties(
    self, soil_rho: float, soil_c: float, temperature_grid: np.ndarray, soil_drying: bool = False
) -> None:
    """This method updates the soil properties around a cable.

    Args:
        soil_rho (float): The thermal resistivity of the soil that is not dried out
        soil_c (float): The thermal capacity of the soil that is not dried out
        temperature_grid (np.ndarray): The temperature grid for the cable, as calculated for a given timestep.
        soil_drying (bool): Whether the scenario takes soil drying into account.

    """
    dry_soil_radius = self._get_dry_soil_radius(temperature_grid=temperature_grid, soil_drying=soil_drying)

    self._update_soil_resistivity(
        soil_rho=soil_rho,
        dry_soil_radius=dry_soil_radius,
    )

    self._update_soil_capacity(soil_c=soil_c)

get_cable_copy_without_soil

get_cable_copy_without_soil() -> Self

This method returns a new CableSoil object with the soil layer removed.

Source code in cable_thermal_model/model/cables/cable_soil.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def get_cable_copy_without_soil(self) -> Self:
    """This method returns a new CableSoil object with the soil layer removed."""
    if CableLayer.SoilOne not in self.layers:
        raise ValueError("No soil layers detected!")

    non_soil_layers = [layer for layer in self.layers if layer not in CableLayer.soil_layers()]
    grid_count_for_cable_without_soil = {
        layer: grid_count for layer, grid_count in self._grid_counts.items() if layer in non_soil_layers
    }

    new_layer_properties = {layer: self.layer_properties[layer] for layer in non_soil_layers}

    return self._get_redefined_cable(
        layer_properties=new_layer_properties, grid_counts=grid_count_for_cable_without_soil
    )

from_cable_with_added_soil_layer classmethod

from_cable_with_added_soil_layer(cable: Cable, soil_rho: float, soil_capacity: float, soil_radius: float, logarithmic_soil_gridpoint_density: float) -> Self

Create a fresh copy of the current cable object this was run from, but with an extra added soil layer.

Parameters:

Name Type Description Default
cable Cable
The cable object to create a CableSoil instance from.
required
soil_rho float
The thermal resistivity of the soil layer to add.
required
soil_capacity float
The thermal capacity of the soil layer to add.
required
soil_radius float
The radius of the soil layer to add.
required
logarithmic_soil_gridpoint_density float
The density of grid points in the soil layer, this is used
to compute the number of grid points in the soil layer
based on its thickness. The density represents the number
of grid points per factor 2 increase in soil layer
thickness.
required

Returns:

Name Type Description
CableSoil Self
A completely new CableSoil instance based on the Cable object the method was called from, but with
the added soil layers.
Source code in cable_thermal_model/model/cables/cable_soil.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
@classmethod
def from_cable_with_added_soil_layer(
    cls,
    cable: Cable,
    soil_rho: float,
    soil_capacity: float,
    soil_radius: float,
    logarithmic_soil_gridpoint_density: float,
) -> Self:
    """Create a fresh copy of the current cable object this was run from, but with an extra added soil layer.

    Args:
        cable (Cable):
                The cable object to create a CableSoil instance from.
        soil_rho (float):
                The thermal resistivity of the soil layer to add.
        soil_capacity (float):
                The thermal capacity of the soil layer to add.
        soil_radius (float):
                The radius of the soil layer to add.
        logarithmic_soil_gridpoint_density (float):
                The density of grid points in the soil layer, this is used
                to compute the number of grid points in the soil layer
                based on its thickness. The density represents the number
                of grid points per factor 2 increase in soil layer
                thickness.

    Returns:
        CableSoil:
                A completely new CableSoil instance based on the Cable object the method was called from, but with
                the added soil layers.

    """
    # copy source data so we don't mutate the original cable
    layer_properties = deepcopy(cable.layer_properties)
    grid_counts = deepcopy(cable._grid_counts)

    outer_layer = cable.layers[-1]
    current_outer_radius = layer_properties[outer_layer].outer_radius
    if soil_radius <= current_outer_radius:
        raise ValueError("The soil radius must be larger than the outer radius of the current outer layer!")

    soil_layers = CableLayer.soil_layers()
    if outer_layer in soil_layers:
        if outer_layer == soil_layers[-1]:
            raise ValueError(
                "The current cable already has the maximum amount of soil layers! "
                "This method cannot be used to add more soil layers!"
            )
        new_layer = soil_layers[soil_layers.index(outer_layer) + 1]
    else:
        new_layer = soil_layers[0]

    layer_properties[new_layer] = CableLayerProperties(
        layer=new_layer,
        inner_radius=current_outer_radius,
        outer_radius=soil_radius,
        rho=soil_rho,
        capacity=soil_capacity,
    )

    radius_factor = soil_radius / current_outer_radius
    grid_counts[new_layer] = max(2, int(logarithmic_soil_gridpoint_density * np.log2(radius_factor)))
    new_cable_soil_cable = cls(
        conductor=deepcopy(cable.conductor),
        layer_properties=layer_properties,
        layer_metrics=deepcopy(cable.layer_metrics),
        cable_type=cable.cable_type,
        grid_counts=grid_counts,
    )
    new_cable_soil_cable.weighted_screen_impedance = cable.weighted_screen_impedance
    return new_cable_soil_cable