Skip to content

cable_input_schemas

CableConstructionalInputSchema

Bases: BaseModel

Input schema for complete cable constructional data.

The schema validates layer-level and cross-layer geometric consistency for a full cable definition.

cable_type property

cable_type: CableType

Infer cable type from the insulation material.

Returns:

Name Type Description
CableType CableType

One of XLPE, PILC, or OilPressure.

Raises:

Type Description
ValueError

If the insulation material cannot be mapped to a known cable type.

layers property

Return configured cable layers keyed by cable layer enum.

get_and_validate_radii

get_and_validate_radii() -> list[float]

Resolve and validate all layer interface radii.

Returns:

Type Description
list[float]

list[float]: Fully resolved list of interface radii including the conductor center radius at index 0.

Raises:

Type Description
ValueError

If dimensions are inconsistent or insufficient.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def get_and_validate_radii(self) -> list[float]:
    """Resolve and validate all layer interface radii.

    Returns:
        list[float]: Fully resolved list of interface radii including the
            conductor center radius at index 0.

    Raises:
        ValueError: If dimensions are inconsistent or insufficient.

    """
    radii = [0.0] + [None] * len(self.layers)

    made_progress = True
    while made_progress:
        made_progress = False
        for index, layer_input in enumerate(self.layers.values()):
            made_progress |= self._set_and_validate_radius(radii, index, layer_input.inner_radius)
            made_progress |= self._set_and_validate_radius(radii, index + 1, layer_input.outer_radius)
            layer_input.inner_radius = radii[index]
            layer_input.outer_radius = radii[index + 1]

            if (
                isinstance(layer_input, ThreeCoreCableInsulationInputSchema)
                and layer_input.outer_radius is not None
                and layer_input.insulation_equivalent_radius_ratio is None
            ):
                layer_input.insulation_equivalent_radius_ratio = (
                    self.get_or_compute_insulation_equivalent_radius_ratio()
                )
                made_progress |= layer_input.insulation_equivalent_radius_ratio is not None

    resolved_radii = [radius for radius in radii if radius is not None]
    if len(resolved_radii) != len(radii):
        raise ValueError("Insufficient dimension information: could not determine all layer radii.")

    return resolved_radii

get_outer_radii

get_outer_radii() -> dict[CableLayer, float]

Get the outer radii of all cable layers.

Returns a dictionary mapping each cable layer to its outer radius. If any outer radius is missing, this method first validates and derives all layer radii.

Returns:

Type Description
dict[CableLayer, float]

dict[CableLayer, float]: Dictionary mapping cable layers to their outer radii in meters.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def get_outer_radii(self) -> dict[CableLayer, float]:
    """Get the outer radii of all cable layers.

    Returns a dictionary mapping each cable layer to its outer radius.
    If any outer radius is missing, this method first validates and derives
    all layer radii.

    Returns:
        dict[CableLayer, float]: Dictionary mapping cable layers to their
            outer radii in meters.

    """
    outer_radii = {
        layer: layer_input.outer_radius
        for layer, layer_input in self.layers.items()
        if layer_input.outer_radius is not None
    }
    if len(outer_radii) != len(self.layers):
        self.get_and_validate_radii()
        return self.get_outer_radii()

    return outer_radii

get_inner_radii

get_inner_radii() -> dict[CableLayer, float]

Get the inner radii of all cable layers.

Returns a dictionary mapping each cable layer to its inner radius. If any inner radius is missing, this method first validates and derives all layer radii.

Returns:

Type Description
dict[CableLayer, float]

dict[CableLayer, float]: Dictionary mapping cable layers to their inner radii in meters.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def get_inner_radii(self) -> dict[CableLayer, float]:
    """Get the inner radii of all cable layers.

    Returns a dictionary mapping each cable layer to its inner radius.
    If any inner radius is missing, this method first validates and derives
    all layer radii.

    Returns:
        dict[CableLayer, float]: Dictionary mapping cable layers to their
            inner radii in meters.

    """
    inner_radii = {
        layer: layer_input.inner_radius
        for layer, layer_input in self.layers.items()
        if layer_input.inner_radius is not None
    }
    if len(inner_radii) != len(self.layers):
        self.get_and_validate_radii()
        return self.get_inner_radii()

    return inner_radii

validate_cable_specs

validate_cable_specs() -> Self

Run global cable-level validations after model creation.

Returns:

Name Type Description
CableConstructionalInputSchema Self

The validated model instance.

Raises:

Type Description
ValueError

If number of conductors is unsupported.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
@model_validator(mode="after")
def validate_cable_specs(self) -> Self:
    """Run global cable-level validations after model creation.

    Returns:
        CableConstructionalInputSchema: The validated model instance.

    Raises:
        ValueError: If number of conductors is unsupported.

    """
    if self.number_of_conductors == CableConductorCount.Three:
        self.validate_three_core_cable_specs()
    elif self.number_of_conductors == CableConductorCount.One:
        self.validate_single_core_cable_specs()
    else:
        raise ValueError(f"Unsupported number of conductors: {self.number_of_conductors.value}")

    self.get_and_validate_radii()
    return self

validate_three_core_cable_specs

validate_three_core_cable_specs()

Validate and finalize constraints specific to three-core cables.

Returns:

Name Type Description
CableConstructionalInputSchema

The validated model instance.

Raises:

Type Description
NotImplementedError

If unsupported screen layers are provided for three-core cables.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
def validate_three_core_cable_specs(self):
    """Validate and finalize constraints specific to three-core cables.

    Returns:
        CableConstructionalInputSchema: The validated model instance.

    Raises:
        NotImplementedError: If unsupported screen layers are provided for
            three-core cables.

    """
    self.validate_three_core_cable_insulation()

    if self.conductor_input.single_conductor_radius is None:
        self.conductor_input.single_conductor_radius = (
            self.compute_single_conductor_radius_from_conducting_surface_area()
        )

    if self.conductor_screen_input is not None or self.insulation_screen_input is not None:
        raise NotImplementedError(
            "Conductor / insulation screen layer is not supported for three core cables. For oil-filled cables, "
            "use the conductor_screen_material and conductor_screen_thickness "
            "fields in the insulation input schema "
            "to specify the conductor screen properties."
        )

    self.insulation_input.insulation_equivalent_radius_ratio = (
        self.get_or_compute_insulation_equivalent_radius_ratio()
    )

    return self

compute_single_conductor_radius_from_conducting_surface_area

compute_single_conductor_radius_from_conducting_surface_area() -> (
    float
)

Compute single-conductor radius from conducting area.

Returns:

Name Type Description
float float

Radius in meters assuming a circular equivalent conductor.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
278
279
280
281
282
283
284
285
def compute_single_conductor_radius_from_conducting_surface_area(self) -> float:
    """Compute single-conductor radius from conducting area.

    Returns:
        float: Radius in meters assuming a circular equivalent conductor.

    """
    return np.sqrt(self.conductor_input.conducting_surface_area / np.pi)

get_or_compute_insulation_equivalent_radius_ratio

get_or_compute_insulation_equivalent_radius_ratio() -> (
    float | None
)

Compute equivalent insulation radius ratio for three-core cables.

If insulation_equivalent_radius_ratio is already set, this method returns the existing value.

Returns:

Type Description
float | None

float | None: Equivalent radius ratio, or None when required geometry is not yet available.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
def get_or_compute_insulation_equivalent_radius_ratio(self) -> float | None:
    """Compute equivalent insulation radius ratio for three-core cables.

    If insulation_equivalent_radius_ratio is already set, this method returns the existing value.

    Returns:
        float | None: Equivalent radius ratio, or `None` when required
            geometry is not yet available.

    """
    insulation_input = self.validate_three_core_cable_insulation()
    if insulation_input.insulation_equivalent_radius_ratio is not None:
        return insulation_input.insulation_equivalent_radius_ratio

    normalized_T1 = self.compute_normalized_lumped_sum_thermal_resistance_insulation()
    if normalized_T1 is None:
        return None

    return np.exp((normalized_T1 * 2 * np.pi) / 3)

validate_three_core_cable_insulation

validate_three_core_cable_insulation() -> (
    ThreeCoreCableInsulationInputSchema
)

Validate and return three-core insulation input schema.

Returns:

Name Type Description
ThreeCoreCableInsulationInputSchema ThreeCoreCableInsulationInputSchema

Typed insulation input.

Raises:

Type Description
ValueError

If insulation input is not the expected three-core schema.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
def validate_three_core_cable_insulation(self) -> ThreeCoreCableInsulationInputSchema:
    """Validate and return three-core insulation input schema.

    Returns:
        ThreeCoreCableInsulationInputSchema: Typed insulation input.

    Raises:
        ValueError: If insulation input is not the expected three-core
            schema.

    """
    if not isinstance(self.insulation_input, ThreeCoreCableInsulationInputSchema):
        raise ValueError(
            "Insulation input must be of type "
            "ThreeCoreCableInsulationInputSchema to validate three core "
            "cable insulation specifications."
        )
    return self.insulation_input

compute_normalized_lumped_sum_thermal_resistance_insulation

compute_normalized_lumped_sum_thermal_resistance_insulation() -> (
    float | None
)

Compute normalized lumped insulation thermal resistance for three-core cables.

Dispatches to cable-type-specific equations for the normalized lumped thermal resistance \(T_1\) between conductor and sheath/screen, following NEN-IEC 60287-2-1 and Anders (1997).

Returns:

Type Description
float | None

float | None: Normalized \(T_1\). Returns None when required insulation geometry is not yet available.

Raises:

Type Description
NotImplementedError

If the cable configuration is not supported by the currently implemented equations.

ValueError

If input data is invalid for the selected cable type.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
def compute_normalized_lumped_sum_thermal_resistance_insulation(self) -> float | None:
    """Compute normalized lumped insulation thermal resistance for three-core cables.

    Dispatches to cable-type-specific equations for the normalized lumped
    thermal resistance $T_1$ between conductor and sheath/screen, following
    NEN-IEC 60287-2-1 and Anders (1997).

    Returns:
        float | None: Normalized $T_1$. Returns `None` when required
            insulation geometry is not yet available.

    Raises:
        NotImplementedError: If the cable configuration is not supported by
            the currently implemented equations.
        ValueError: If input data is invalid for the selected cable type.

    """
    insulation_input = self.validate_three_core_cable_insulation()

    if insulation_input.outer_radius is None:
        return None

    t1 = insulation_input.single_conductor_insulation_thickness
    dx = self.compute_single_conductor_radius_from_conducting_surface_area() * 2
    dc = (
        self.conductor_input.single_conductor_radius * 2
        if self.conductor_input.single_conductor_radius is not None
        else dx
    )

    if self.cable_type == CableType.OilPressure:
        return self.compute_normalized_lumped_sum_thermal_resistance_oil_pressure(
            insulation_input=insulation_input,
            t1=t1,
            dc=dc,
        )

    # SL-type XLPE cables
    if self.screen_input and self.screen_input.screen_type == CableScreenType.SL:
        return self.compute_normalized_lumped_sum_thermal_resistance_xlpe_sl(
            t1=t1,
            dc=dc,
        )

    # PILC and XLPE cables are assumed to be belted.
    # See section 4.1.2.2 in NEN-IEC 60287-2-1 (2015)
    da = insulation_input.outer_radius * 2  # Diameter over insulation
    r1 = (da / 2) - t1  # Radius of the circle circumscribing the conductors
    t_belt = (da - insulation_input.diameter_over_stranded_conductors) / 2  # Thickness of belt insulation
    t_cond = t1 - t_belt  # Thickness of conductor insulation
    t = 2 * t_cond  # Thickness of insulation between 2 conductors

    if self.cable_type == CableType.PILC:
        return self.compute_normalized_lumped_sum_thermal_resistance_pilc(
            dx=dx,
            da=da,
            r1=r1,
            t=t,
        )

    # Common screen or unscreened XLPE Cables
    if (
        self.cable_type == CableType.XLPE
        and (self.screen_input is None or self.screen_input.screen_type == CableScreenType.Common)
        and self.conductor_input.shape == CableConductorShape.Round
    ):
        return self.compute_normalized_lumped_sum_thermal_resistance_xlpe_common_or_unscreened(
            t1=t1,
            dc=dc,
            t=t,
        )

    # Raise a not implemented error if the cable type is not recognized
    raise NotImplementedError(
        f"Thermal resistance calculation for cable with sheath configuration "
        f"{self.screen_input.screen_type if self.screen_input is not None else None} "
        f"and conductor shape {self.conductor_input.shape} is not implemented."
    )

compute_normalized_lumped_sum_thermal_resistance_oil_pressure

compute_normalized_lumped_sum_thermal_resistance_oil_pressure(
    insulation_input: ThreeCoreCableInsulationInputSchema,
    t1: float,
    dc: float,
) -> float

Compute normalized \(T_1\) for three-core oil-pressure cables.

Parameters:

Name Type Description Default
insulation_input ThreeCoreCableInsulationInputSchema

Three-core insulation input including optional conductor screen data.

required
t1 float

Single-conductor insulation thickness in meters.

required
dc float

Conductor diameter in meters.

required

Returns:

Name Type Description
float float

Normalized lumped thermal resistance for the oil-pressure branch.

Raises:

Type Description
ValueError

If conductor screen material is not supported for oil-pressure cable equations.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
def compute_normalized_lumped_sum_thermal_resistance_oil_pressure(
    self,
    insulation_input: ThreeCoreCableInsulationInputSchema,
    t1: float,
    dc: float,
) -> float:
    """Compute normalized $T_1$ for three-core oil-pressure cables.

    Args:
        insulation_input: Three-core insulation input including optional
            conductor screen data.
        t1: Single-conductor insulation thickness in meters.
        dc: Conductor diameter in meters.

    Returns:
        float: Normalized lumped thermal resistance for the oil-pressure
            branch.

    Raises:
        ValueError: If conductor screen material is not supported for
            oil-pressure cable equations.

    """
    delta = (
        insulation_input.conductor_screen_thickness
        if insulation_input.conductor_screen_thickness is not None
        else 0.0
    )
    ti = t1 + delta

    if insulation_input.conductor_screen_material == CableConductorInsulationScreenMaterial.MetalizedPaper:
        # Eqn. (9.9) in Anders for oil pressure cables with metalized paper screen.
        return 0.385 * ((2 * ti) / (dc + 2 * ti))

    if insulation_input.conductor_screen_material == CableConductorInsulationScreenMaterial.MetalTape:
        # NEN-IEC60287-2-1 (2015), par (4.1.2.4.2) for metal tape screens.
        # NOTE: Formula 9.10 in Anders is incorrect.
        return 0.35 * (0.923 - (dc / (dc + 2 * ti)))

    raise ValueError(
        f"Invalid conductor screen material for oil pressure cable: "
        f"{insulation_input.conductor_screen_material}. Expected either "
        "MetalizedPaper or MetalTape."
    )

compute_normalized_lumped_sum_thermal_resistance_xlpe_sl

compute_normalized_lumped_sum_thermal_resistance_xlpe_sl(
    t1: float, dc: float
) -> float

Compute normalized \(T_1\) for SL-type XLPE cables.

Parameters:

Name Type Description Default
t1 float

Single-conductor insulation thickness in meters.

required
dc float

Conductor diameter in meters.

required

Returns:

Name Type Description
float float

Normalized lumped thermal resistance for SL-type XLPE.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
450
451
452
453
454
455
456
457
458
459
460
461
def compute_normalized_lumped_sum_thermal_resistance_xlpe_sl(self, t1: float, dc: float) -> float:
    """Compute normalized $T_1$ for SL-type XLPE cables.

    Args:
        t1: Single-conductor insulation thickness in meters.
        dc: Conductor diameter in meters.

    Returns:
        float: Normalized lumped thermal resistance for SL-type XLPE.

    """
    return 1 / (2 * np.pi) * np.log(1 + (2 * t1) / dc)

compute_normalized_lumped_sum_thermal_resistance_pilc

compute_normalized_lumped_sum_thermal_resistance_pilc(
    dx: float, da: float, r1: float, t: float
) -> float

Compute normalized \(T_1\) for PILC cables.

Parameters:

Name Type Description Default
dx float

Equivalent diameter of one conductor in meters.

required
da float

Diameter over insulation in meters.

required
r1 float

Radius of the circle circumscribing the conductors in meters.

required
t float

Insulation thickness between two conductors in meters.

required

Returns:

Name Type Description
float float

Normalized lumped thermal resistance for the PILC branch.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def compute_normalized_lumped_sum_thermal_resistance_pilc(
    self,
    dx: float,
    da: float,
    r1: float,
    t: float,
) -> float:
    """Compute normalized $T_1$ for PILC cables.

    Args:
        dx: Equivalent diameter of one conductor in meters.
        da: Diameter over insulation in meters.
        r1: Radius of the circle circumscribing the conductors in meters.
        t: Insulation thickness between two conductors in meters.

    Returns:
        float: Normalized lumped thermal resistance for the PILC branch.

    """
    # PILC cables are assumed to have sector shaped conductors.
    # The T1 is calculated using section (4.1.2.2.6) in NEN-IEC 60287-2-1 (2015)
    F2 = 1 + ((3 * t) / (2 * np.pi * (dx + t) - t))
    G = 3 * F2 * np.log(da / (2 * r1))
    return G / (2 * np.pi)

compute_normalized_lumped_sum_thermal_resistance_xlpe_common_or_unscreened

compute_normalized_lumped_sum_thermal_resistance_xlpe_common_or_unscreened(
    t1: float, dc: float, t: float
) -> float

Compute normalized \(T_1\) for common-screen or unscreened XLPE cables.

Uses the quadratic approximation for the geometric factor in NEN-IEC 60287-2-1 (2015), section 5.3.

Parameters:

Name Type Description Default
t1 float

Single-conductor insulation thickness in meters.

required
dc float

Conductor diameter in meters.

required
t float

Insulation thickness between two conductors in meters.

required

Returns:

Name Type Description
float float

Normalized lumped thermal resistance for the XLPE common/unscreened branch.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
def compute_normalized_lumped_sum_thermal_resistance_xlpe_common_or_unscreened(
    self,
    t1: float,
    dc: float,
    t: float,
) -> float:
    """Compute normalized $T_1$ for common-screen or unscreened XLPE cables.

    Uses the quadratic approximation for the geometric factor in
    NEN-IEC 60287-2-1 (2015), section 5.3.

    Args:
        t1: Single-conductor insulation thickness in meters.
        dc: Conductor diameter in meters.
        t: Insulation thickness between two conductors in meters.

    Returns:
        float: Normalized lumped thermal resistance for the XLPE
            common/unscreened branch.

    """
    # Use the quadratic approximation method to determine the geometric
    # factor as described in section 5.3 of NEN-IEC60287-2-1 (2015).
    X = t1 / dc
    Y = (2 * t1 / t) - 1

    Z = (2 / np.sqrt(3)) * (1 + 2 * X / (1 + Y))

    alpha = 1 / (1 + 2 * X / (1 + Z)) ** 3
    beta = alpha * (Z - 3) / (Z + 3)

    M = np.log((1 - alpha * beta + ((1 - alpha**2) * (1 - beta**2)) ** 0.5) / (alpha - beta))

    a = 1.09414 - 0.0944045 * X + 0.0234464 * X**2
    b = 1.09605 - 0.0801857 * X + 0.0176917 * X**2
    c = 1.09831 - 0.0720631 * X + 0.0145909 * X**2

    G = M * (a + (-3 * a + 4 * b - c) * Y + (2 * a - 4 * b + 2 * c) * Y**2)

    # The T1 is then calculated using section (4.1.2.2.4) in NEN-IEC 60287-2-1 (2015)
    # NOTE: Here the thermal resistivity of the filler material is assumed
    # to be equal to the thermal resistivity of the insulation material.
    return G / (2 * np.pi)

validate_single_core_cable_specs

validate_single_core_cable_specs()

Validate constraints specific to single-core cable definitions.

Raises:

Type Description
ValueError

If single-conductor radius and conductor outer radius are inconsistent, or if sector conductors are provided.

NotImplementedError

If armour is present on single-core cables.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
def validate_single_core_cable_specs(self):
    """Validate constraints specific to single-core cable definitions.

    Raises:
        ValueError: If single-conductor radius and conductor outer radius
            are inconsistent, or if sector conductors are provided.
        NotImplementedError: If armour is present on single-core cables.

    """
    self.validate_single_core_cable_insulation()

    if self.conductor_input.single_conductor_radius is not None and not np.isclose(
        self.conductor_input.single_conductor_radius, self.conductor_input.outer_radius
    ):
        raise ValueError("For single core cables, single_conductor_radius must equal the conductor outer_radius.")

    if self.conductor_input.shape == CableConductorShape.Sector:
        raise ValueError("Single-core cables can not have sector shaped conductors.")

    if self.armour_input is not None:
        raise NotImplementedError(
            "Armour losses are not accounted for in the FD model. "
            "Therefore, armoured cables are not supported for single-core cables."
        )

validate_single_core_cable_insulation

validate_single_core_cable_insulation() -> (
    InsulationInputSchema
)

Validate and return single-core insulation input schema.

Returns:

Name Type Description
InsulationInputSchema InsulationInputSchema

Typed insulation input.

Raises:

Type Description
ValueError

If insulation input is not the expected single-core schema.

Source code in cable_thermal_model/cable/schemas/cable_input_schemas.py
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
def validate_single_core_cable_insulation(self) -> InsulationInputSchema:
    """Validate and return single-core insulation input schema.

    Returns:
        InsulationInputSchema: Typed insulation input.

    Raises:
        ValueError: If insulation input is not the expected single-core
            schema.

    """
    if not isinstance(self.insulation_input, InsulationInputSchema):
        raise ValueError(
            "Insulation input must be of type InsulationInputSchema to "
            "validate single core cable insulation specifications."
        )
    return self.insulation_input