Skip to content

span

CatenarySpan

CatenarySpan(
    span_length: ndarray,
    elevation_difference: ndarray,
    parameter: ndarray,
    load_coefficient: ndarray | None = None,
    linear_weight: float64 | None = None,
    span_index: ndarray | None = None,
    span_type: ndarray | None = None,
    **_,
)

Bases: ISpan

Implementation of a span cable model according to the catenary equation.

The coordinates are expressed in the cable frame.

Source code in src/mechaphlowers/core/models/cable/span.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def __init__(
    self,
    span_length: np.ndarray,
    elevation_difference: np.ndarray,
    parameter: np.ndarray,
    load_coefficient: np.ndarray | None = None,
    linear_weight: np.float64 | None = None,
    span_index: np.ndarray | None = None,
    span_type: np.ndarray | None = None,
    **_,
) -> None:
    # TODO: check the vectors have the same size
    # TODO: check that span_type == 0,1,2

    self.span_length = span_length
    self.elevation_difference = elevation_difference
    self.parameter = parameter
    self.linear_weight = linear_weight
    if load_coefficient is None:
        self.load_coefficient = np.ones_like(span_length)
    else:
        self.load_coefficient = load_coefficient
    # span_index refers to the index of the span:
    # is [0, 1, 2, ...], if there is no loads
    # is [0, 1, 1, 2 ...], if there is a load in span number 1, and that ISpan represents this span by two values in the arrays
    if span_index is None:
        self.span_index = np.arange(len(span_length))
    else:
        self.span_index = span_index
    # span_type value of 0, 1 or 2 depending on the relationship with the load on the span:
    # - 0 if default span
    # - 1 if semi-span to the left of a load
    # - 2 if semi-span to the right of a load
    if span_type is None:
        self.span_type = np.full_like(span_length, 0)
    else:
        self.span_type = span_type
    self.compute_values()
    # loads_indices stores data about the loads:
    # - span indices where loads are located. Example: [0,2] means that spans number 0 and 2 have loads.
    # - indices of the load points, for each spans coordinates.
    # Example: [0,2], [6,12] means that point number 6 in the span number 0 is a load,
    # as well as point number 12 in span number 2
    # This tuple is used to retrieve load coords after plotting.
    self.loads_indices: tuple[np.ndarray, np.ndarray] = (
        np.array([], dtype=np.int64),
        np.array([], dtype=np.int64),
    )

T_mean

T_mean() -> ndarray

Return the mean tension along the whole cable. Used in deformation model to compute mechanical deformation. Warning: this method uses stored values of x_m, x_n and L. If any attribute has been updated, compute_values() should be called before calling this method.

Source code in src/mechaphlowers/core/models/cable/span.py
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
def T_mean(self) -> np.ndarray:
    """Return the mean tension along the whole cable. Used in deformation model to compute mechanical deformation.
    Warning: this method uses stored values of x_m, x_n and L.
    If any attribute has been updated, compute_values() should be called before calling this method.

    """
    p = self.parameter
    k_load = self.load_coefficient
    lambd = self.linear_weight
    a = self._x_n - self._x_m
    return (
        p
        * k_load
        * lambd
        * (
            a
            + (np.sinh(2 * self._x_n / p) - np.sinh(2 * self._x_m / p))
            * p
            / 2
        )
        / self._L
        / 2
    )

compute_L

compute_L() -> ndarray

Total length of the cable.

Source code in src/mechaphlowers/core/models/cable/span.py
579
580
581
582
583
584
def compute_L(self) -> np.ndarray:
    """Total length of the cable."""
    p = self.parameter
    return p * (
        np.sinh(self.compute_x_n() / p) - np.sinh(self.compute_x_m() / p)
    )

compute_partial_L

compute_partial_L(new_a: ndarray) -> ndarray

Cable length from left hanging point to point corresponding to span length new_a.

Source code in src/mechaphlowers/core/models/cable/span.py
586
587
588
589
590
def compute_partial_L(self, new_a: np.ndarray) -> np.ndarray:
    """Cable length from left hanging point to point corresponding to span length new_a."""
    p = self.parameter
    x = new_a + self._x_m
    return p * (np.sinh(x / p) - np.sinh(self._x_m / p))

compute_values

compute_values()

Compute and store values for x_m, x_n and L based on current attributes. T_mean depends on these values, so this method should be called before calling T_mean(), especially if an attribute has been updated.

The goal of this implementation is to reduce the number of times compute_x_m, compute_x_n and compute_L are called during solver iterations.

Source code in src/mechaphlowers/core/models/cable/span.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def compute_values(self):
    """Compute and store values for x_m, x_n and L based on current attributes.
    T_mean depends on these values, so this method should be called before calling T_mean(),
    especially if an attribute has been updated.

    The goal of this implementation is to reduce the number of times compute_x_m, compute_x_n and compute_L
    are called during solver iterations.
    """
    self._x_m = self.compute_x_m()
    # Optimisation: not using compute_x_n() and compute_L()
    # to avoid calling compute_x_m() many times
    self._x_n = self.span_length + self._x_m
    p = self.parameter
    self._L = p * (np.sinh(self._x_n / p) - np.sinh(self._x_m / p))

compute_x_n

compute_x_n() -> ndarray

Distance between the lowest point of the cable and the right hanging point, projected on the horizontal axis.

In other words: abscissa of the right hanging point.

Source code in src/mechaphlowers/core/models/cable/span.py
280
281
282
283
284
285
286
def compute_x_n(self) -> np.ndarray:
    """Distance between the lowest point of the cable and the right hanging point, projected on the horizontal axis.

    In other words: abscissa of the right hanging point.
    """
    a = self.span_length
    return a + self.compute_x_m()

get_coords

get_coords(resolution: int) -> tuple[ndarray, ndarray]

Get x and z coordinates for catenary generation in cable frame.

This method handles different span types in case of virtual nodes and produces an output of the same size than the real number of spans.

Updates the loads_indices attribute to store the positions of the loads.

Parameters:

Name Type Description Default

resolution

int

Number of points to generate between supports.

required

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: x and z coordinates of the cable

Source code in src/mechaphlowers/core/models/cable/span.py
487
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
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
def get_coords(self, resolution: int) -> tuple[np.ndarray, np.ndarray]:
    """Get x and z coordinates for catenary generation in cable frame.

    This method handles different span types in case of virtual nodes and produces an output of the same size than the real number of spans.

    Updates the `loads_indices` attribute to store the positions of the loads.

    Args:
        resolution (int): Number of points to generate between supports.

    Returns:
        tuple[np.ndarray, np.ndarray]: x and z coordinates of the cable
    """

    start_points = self.compute_x_m()
    end_points = self.compute_x_n()

    if np.all(self.span_type == 0):
        x = np.linspace(start_points, end_points, resolution)
        z = self.z_many_points_local(x, self.parameter)
        return x, z
    start_points_0 = start_points[self.span_type == 0]
    end_points_0 = end_points[self.span_type == 0]

    start_points_left = start_points[self.span_type == 1]
    start_points_right = start_points[self.span_type == 2]
    end_points_left = end_points[self.span_type == 1]
    end_points_right = end_points[self.span_type == 2]

    x_left = np.linspace(start_points_left, end_points_left, resolution)
    x_right = np.linspace(start_points_right, end_points_right, resolution)
    x_0 = np.linspace(start_points_0, end_points_0, resolution)

    z_left = self.z_many_points_local(
        x_left, self.parameter[self.span_type == 1]
    )
    z_right = self.z_many_points_local(
        x_right, self.parameter[self.span_type == 2]
    )
    z_0 = self.z_many_points_local(
        x_0, self.parameter[self.span_type == 0]
    )

    # join
    x_load = np.concatenate(
        (x_left, x_right + end_points_left - start_points_right), axis=0
    )
    z_load = np.concatenate(
        (z_left, z_right + z_left[-1, :] - z_right[0, :]), axis=0
    )

    # interpolate
    new_x = np.linspace(
        np.min(x_load, axis=0), np.max(x_load, axis=0), resolution
    )
    new_z = self._interpolation(new_x.T, x_load.T, z_load.T).T

    # we have to replace the endpoints left after interpolation
    load_idx_in_coords = np.abs(new_x - end_points_left).argmin(axis=0)

    new_x[load_idx_in_coords, np.arange(load_idx_in_coords.shape[0])] = (
        end_points_left
    )
    new_z[load_idx_in_coords, np.arange(load_idx_in_coords.shape[0])] = (
        z_left[-1, :]
    )

    mask_output = np.logical_or(self.span_type == 1, self.span_type == 0)
    x = np.full((resolution, self.span_type.shape[0]), np.nan, dtype=float)
    z = np.full((resolution, self.span_type.shape[0]), np.nan, dtype=float)

    x[:, self.span_type == 1] = new_x
    x[:, self.span_type == 0] = x_0

    z[:, self.span_type == 1] = new_z
    z[:, self.span_type == 0] = z_0

    self.loads_indices = (
        self.span_index[self.span_type == 1],
        load_idx_in_coords,
    )

    return x[:, mask_output], z[:, mask_output]

mirror

mirror(span_model: Self) -> None

Copy attributes from an other ISpan object. This method is useful for copying values and keeping the reference of the current object.

Parameters:

Name Type Description Default

span_model

Self

other ISpan object to copy attribute from

required

Examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>>> span1 = CatenarySpan(
...     span_length=np.array([500, 600]),
...     elevation_difference=np.array([10, 20]),
...     parameter=np.array([2000, 1500]),
...)
>>> span2 = CatenarySpan(
...     span_length=np.array([100, 100]),
...     elevation_difference=np.array([0, 0]),
...     parameter=np.array([500, 500]),
... )
>>> span2.mirror(span1)
# now span2 has the same attributes as span1
>>> span2.span_length
array([500, 600])
Source code in src/mechaphlowers/core/models/cable/span.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def mirror(self, span_model: Self) -> None:
    """Copy attributes from an other ISpan object.
    This method is useful for copying values and keeping the reference of the current object.

    Args:
        span_model (Self): other ISpan object to copy attribute from

    Examples:
            >>> span1 = CatenarySpan(
            ...     span_length=np.array([500, 600]),
            ...     elevation_difference=np.array([10, 20]),
            ...     parameter=np.array([2000, 1500]),
            ...)
            >>> span2 = CatenarySpan(
            ...     span_length=np.array([100, 100]),
            ...     elevation_difference=np.array([0, 0]),
            ...     parameter=np.array([500, 500]),
            ... )
            >>> span2.mirror(span1)
            # now span2 has the same attributes as span1
            >>> span2.span_length
            array([500, 600])
    """
    self.span_length = span_model.span_length
    self.elevation_difference = span_model.elevation_difference
    self.parameter = span_model.parameter
    self.linear_weight = span_model.linear_weight
    self.load_coefficient = span_model.load_coefficient
    self.span_index = span_model.span_index
    self.span_type = span_model.span_type

sag

sag() -> ndarray

Sag of the cable span (s1 formula).

The sag is the maximum perpendicular distance between the cable and the chord connecting both attachment points.

Returns:

Type Description
ndarray

np.ndarray: sag value for each span.

Source code in src/mechaphlowers/core/models/cable/span.py
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
def sag(self) -> np.ndarray:
    """Sag of the cable span (s1 formula).

    The sag is the maximum perpendicular distance between the cable and the chord
    connecting both attachment points.

    Returns:
        np.ndarray: sag value for each span.
    """
    a = self.span_length
    b = self.elevation_difference
    p = self.parameter
    # x0g: horizontal distance from left support to the lowest point of the cable
    x0g = -self.compute_x_m()
    # x0: abscissa corresponding to the inclined chord reference
    x0 = p * np.arcsinh(b / a)
    return (x0g + x0) / a * b + p * (np.cosh(x0g / p) - np.cosh(x0 / p))

sag_s2

sag_s2() -> ndarray

Sag of the cable span, computed with the s2 formula.

The sag s2 is the maximum perpendicular distance between the lowest point of the cable and the lowest hanging point.

Returns:

Type Description
ndarray

np.ndarray: sag s2 value for each span.

Source code in src/mechaphlowers/core/models/cable/span.py
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
def sag_s2(self) -> np.ndarray:
    """Sag of the cable span, computed with the s2 formula.

    The sag s2 is the maximum perpendicular distance between the lowest point of the cable and the lowest hanging point.

    Returns:
        np.ndarray: sag s2 value for each span.
    """

    self.compute_values()
    mask = (self.x_m >= 0) | (self.x_n <= 0)

    z_left = self.z_one_point(self._x_m)
    z_right = self.z_one_point(self._x_n)
    z_lowest_hanging_point = np.minimum(z_left, z_right)
    z_lowest_hanging_point[mask] = 0

    return abs(z_lowest_hanging_point)

set_lengths

set_lengths(
    span_length: ndarray, elevation_difference: ndarray
)

Set value of span_length and elevation_difference and compute x_m, x_n and L.

Parameters:

Name Type Description Default

span_length

ndarray

new value for span_length parameter.

required

elevation_difference

ndarray

new value for elevation_difference parameter.

required
Source code in src/mechaphlowers/core/models/cable/span.py
122
123
124
125
126
127
128
129
130
131
132
133
def set_lengths(
    self, span_length: np.ndarray, elevation_difference: np.ndarray
):
    """Set value of span_length and elevation_difference and compute x_m, x_n and L.

    Args:
        span_length (np.ndarray): new value for span_length parameter.
        elevation_difference (np.ndarray): new value for elevation_difference parameter.
    """
    self.span_length = span_length
    self.elevation_difference = elevation_difference
    self.compute_values()

set_parameter

set_parameter(parameter: ndarray)

Set value of sagging parameter and compute x_m, x_n and L.

Parameters:

Name Type Description Default

parameter

ndarray

new value for sagging parameter.

required
Source code in src/mechaphlowers/core/models/cable/span.py
135
136
137
138
139
140
141
142
def set_parameter(self, parameter: np.ndarray):
    """Set value of sagging parameter and compute x_m, x_n and L.

    Args:
        parameter (np.ndarray): new value for sagging parameter.
    """
    self.parameter = parameter
    self.compute_values()

slope

slope(side: Literal['left', 'right']) -> ndarray

Slope angle at the supports in radians.

Note that the left side corresponds to the slope for support_index 0 to N-1 and the right side corresponds to the slope for support_index 1 to N.

Parameters:

Name Type Description Default

side

Literal['left', 'right']

side regarding the span, in order to select the correct support to compute the slope.

required

Returns: np.ndarray: slope angle at each support in radians.

Source code in src/mechaphlowers/core/models/cable/span.py
649
650
651
652
653
654
655
656
657
658
659
660
661
def slope(self, side: Literal['left', 'right']) -> np.ndarray:
    """Slope angle at the supports in radians.

    Note that the left side corresponds to the slope for support_index 0 to N-1 and the right side corresponds to the slope for support_index 1 to N.

    Args:
        side (Literal['left', 'right']): side regarding the span, in order to select the correct support to compute the slope.
    Returns:
        np.ndarray: slope angle at each support in radians.
    """
    # values are signed: T_h is negative, T_v can be either positive of negative depending on side
    x_extremum = self._x_m if side == 'left' else self._x_n
    return np.atan2(self.T_v(x_extremum), self.T_h())

tensions_sup_inf

tensions_sup_inf() -> tuple[ndarray, ndarray]

Cable tensions at attachment points, x_m and x_n.

The two attachments have different values, the highest value is the one with the higher altitude.

Returns (T_high, T_low), T_high being the higher tension of the two values.

Source code in src/mechaphlowers/core/models/cable/span.py
385
386
387
388
389
390
391
392
393
394
def tensions_sup_inf(self) -> tuple[np.ndarray, np.ndarray]:
    """Cable tensions at attachment points, x_m and x_n.

    The two attachments have different values, the highest value is the one with the higher altitude.

    Returns (T_high, T_low), T_high being the higher tension of the two values.
    """
    x_m, x_n = self.x_m, self.x_n
    Tm_Tn = np.array([self.T(x_m), self.T(x_n)])
    return (np.max(Tm_Tn, axis=0), np.min(Tm_Tn, axis=0))

update_from_dict

update_from_dict(data: dict) -> None

Update the span model with new data.

Parameters:

Name Type Description Default

data

dict

Dictionary containing the new data.

required
Source code in src/mechaphlowers/core/models/cable/span.py
218
219
220
221
222
223
224
225
226
def update_from_dict(self, data: dict) -> None:
    """Update the span model with new data.

    Args:
            data (dict): Dictionary containing the new data.
    """
    for key, value in data.items():
        if hasattr(self, key):
            setattr(self, key, value)

x

x(resolution: int = 10) -> ndarray

x_coordinate for catenary generation in cable frame

Args: resolution (int, optional): Number of point to generation between supports. Defaults to 10.

Returns: np.ndarray: points generated x number of rows in SectionArray. Last column is nan due to the non-definition of last span.

Source code in src/mechaphlowers/core/models/cable/span.py
454
455
456
457
458
459
460
461
462
463
464
465
466
467
def x(self, resolution: int = 10) -> np.ndarray:
    """x_coordinate for catenary generation in cable frame

    Args:
    resolution (int, optional): Number of point to generation between supports. Defaults to 10.

    Returns:
    np.ndarray: points generated x number of rows in SectionArray. Last column is nan due to the non-definition of last span.
    """

    start_points = self.compute_x_m()
    end_points = self.compute_x_n()

    return np.linspace(start_points, end_points, resolution)

z_many_points

z_many_points(x: ndarray) -> ndarray

Altitude of cable points depending on the abscissa. Many points per spans, used for graphs.

Source code in src/mechaphlowers/core/models/cable/span.py
439
440
441
def z_many_points(self, x: np.ndarray) -> np.ndarray:
    """Altitude of cable points depending on the abscissa. Many points per spans, used for graphs."""
    return self.z_many_points_local(x, self.parameter)

z_many_points_local

z_many_points_local(x: ndarray, p: ndarray) -> ndarray

Altitude of cable points depending on the abscissa. Many points per spans, used for graphs.

Source code in src/mechaphlowers/core/models/cable/span.py
424
425
426
427
428
429
430
431
432
433
434
435
436
437
def z_many_points_local(self, x: np.ndarray, p: np.ndarray) -> np.ndarray:
    """Altitude of cable points depending on the abscissa. Many points per spans, used for graphs."""

    # repeating value to perform multidim operation
    xx = x.T
    # self.p is a vector of size (nb support, ). I need to convert it in a matrix (nb support, 1) to perform matrix operation after.
    # Ex: self.p = array([20,20,20,20]) -> self.p([:,new_axis]) = array([[20],[20],[20],[20]])
    pp = p[:, np.newaxis]
    # pp = Th / (load_coef * linear_weight) ?

    rr = pp * (np.cosh(xx / pp) - 1)

    # reshaping back to p,x -> (vertical, horizontal)
    return rr.T

ISpan

ISpan(
    span_length: ndarray,
    elevation_difference: ndarray,
    parameter: ndarray,
    load_coefficient: ndarray | None = None,
    linear_weight: float64 | None = None,
    span_index: ndarray | None = None,
    span_type: ndarray | None = None,
    **_,
)

Bases: ABC

Abstract base class for cable span models in the cable frame.

Because the coordinates are in the cable frame, there is no need to factor in wind or angle, so we work under the following simplifying assumptions:

  • a = a' = span_length
  • b = b' = elevation_difference

The class handles both simple regular spans and spans with point loads. When factoring in point loads, virtual nodes are created, and the span is divided into semi-spans, thus modifiying the number of spans.

Attributes:

Name Type Description
span_length ndarray

Horizontal length of each span.

elevation_difference ndarray

Vertical difference between support points for each span.

parameter ndarray

Parameter controlling the cable sag (model-dependent).

linear_weight float64

Linear weight of the cable per unit length.

load_coefficient ndarray

Coefficient applied to loads for each span (defaults to ones).

span_index ndarray

Index identifying each span, with duplicates for spans with loads.

span_type ndarray

Type indicator for each span:

  • 0: default span (no load or full span)
  • 1: semi-span to the left of a point load
  • 2: semi-span to the right of a point load
loads_indices tuple[ndarray, ndarray]

tuple containing:

  • Array of span indices where loads are located
  • Array of point indices within those spans where loads occur
Notes
  • The class uses caching for computed values (x_m, x_n, L) to optimize performance during iterative solving.
  • Therefore, if parameter, span_length, or elevation_difference are updated, it should be done by calling set_parameter or set_lengths methods to ensure consistency.
  • Arrays are vectorized to handle multiple spans simultaneously.

Examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>>> span = CatenarySpan(
...     span_length=np.array([500, 600]),
...     elevation_difference=np.array([10, 20]),
...     parameter=np.array([2000, 1500]),
...     linear_weight=9.5,
... )
>>> span_with_load = CatenarySpan(
...     span_length=np.array([500, 200, 400]),
...     elevation_difference=np.array([10, -10, 30]),
...     parameter=np.array([2000, 1500, 1500]),
...     linear_weight=9.5
...     span_index=np.array([0, 1, 1]),
...     span_type=np.array([0, 1, 2]),
... )  # point load in span 1, split into two semi-spans
Source code in src/mechaphlowers/core/models/cable/span.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def __init__(
    self,
    span_length: np.ndarray,
    elevation_difference: np.ndarray,
    parameter: np.ndarray,
    load_coefficient: np.ndarray | None = None,
    linear_weight: np.float64 | None = None,
    span_index: np.ndarray | None = None,
    span_type: np.ndarray | None = None,
    **_,
) -> None:
    # TODO: check the vectors have the same size
    # TODO: check that span_type == 0,1,2

    self.span_length = span_length
    self.elevation_difference = elevation_difference
    self.parameter = parameter
    self.linear_weight = linear_weight
    if load_coefficient is None:
        self.load_coefficient = np.ones_like(span_length)
    else:
        self.load_coefficient = load_coefficient
    # span_index refers to the index of the span:
    # is [0, 1, 2, ...], if there is no loads
    # is [0, 1, 1, 2 ...], if there is a load in span number 1, and that ISpan represents this span by two values in the arrays
    if span_index is None:
        self.span_index = np.arange(len(span_length))
    else:
        self.span_index = span_index
    # span_type value of 0, 1 or 2 depending on the relationship with the load on the span:
    # - 0 if default span
    # - 1 if semi-span to the left of a load
    # - 2 if semi-span to the right of a load
    if span_type is None:
        self.span_type = np.full_like(span_length, 0)
    else:
        self.span_type = span_type
    self.compute_values()
    # loads_indices stores data about the loads:
    # - span indices where loads are located. Example: [0,2] means that spans number 0 and 2 have loads.
    # - indices of the load points, for each spans coordinates.
    # Example: [0,2], [6,12] means that point number 6 in the span number 0 is a load,
    # as well as point number 12 in span number 2
    # This tuple is used to retrieve load coords after plotting.
    self.loads_indices: tuple[np.ndarray, np.ndarray] = (
        np.array([], dtype=np.int64),
        np.array([], dtype=np.int64),
    )

L_m abstractmethod

L_m() -> ndarray

Length of the left portion of the cable. The left portion refers to the portion from the left point to lowest point of the cables

Source code in src/mechaphlowers/core/models/cable/span.py
299
300
301
302
@abstractmethod
def L_m(self) -> np.ndarray:
    """Length of the left portion of the cable.
    The left portion refers to the portion from the left point to lowest point of the cables"""

L_n abstractmethod

L_n() -> ndarray

Length of the right portion of the cable. The right portion refers to the portion from the right point to lowest point of the cables

Source code in src/mechaphlowers/core/models/cable/span.py
304
305
306
307
@abstractmethod
def L_n(self) -> np.ndarray:
    """Length of the right portion of the cable.
    The right portion refers to the portion from the right point to lowest point of the cables"""

T abstractmethod

T(x_one_per_span: ndarray) -> ndarray

Norm of the tension on the cable. Same as T_v, x_one_per_span must of same length as the number of spans.

Args: x_one_per_span: array of abscissa, one abscissa per span

Source code in src/mechaphlowers/core/models/cable/span.py
356
357
358
359
360
361
362
363
@abstractmethod
def T(self, x_one_per_span: np.ndarray) -> np.ndarray:
    """Norm of the tension on the cable.
    Same as T_v, x_one_per_span must of same length as the number of spans.

    Args:
    x_one_per_span: array of abscissa, one abscissa per span
    """

T_h abstractmethod

T_h() -> ndarray

Horizontal tension on the cable. Right now, this tension is constant all along the cable, but that might not be true for elastic catenary model.

Raises:

Type Description
AttributeError

linear_weight is required

Source code in src/mechaphlowers/core/models/cable/span.py
326
327
328
329
330
331
332
333
@abstractmethod
def T_h(self) -> np.ndarray:
    """Horizontal tension on the cable.
    Right now, this tension is constant all along the cable, but that might not be true for elastic catenary model.

    Raises:
            AttributeError: linear_weight is required
    """

T_mean abstractmethod

T_mean() -> ndarray

Mean tension along the whole cable.

Source code in src/mechaphlowers/core/models/cable/span.py
373
374
375
@abstractmethod
def T_mean(self) -> np.ndarray:
    """Mean tension along the whole cable."""

T_mean_m abstractmethod

T_mean_m() -> ndarray

Mean tension of the left portion of the cable.

Source code in src/mechaphlowers/core/models/cable/span.py
365
366
367
@abstractmethod
def T_mean_m(self) -> np.ndarray:
    """Mean tension of the left portion of the cable."""

T_mean_n abstractmethod

T_mean_n() -> ndarray

Mean tension of the right portion of the cable.

Source code in src/mechaphlowers/core/models/cable/span.py
369
370
371
@abstractmethod
def T_mean_n(self) -> np.ndarray:
    """Mean tension of the right portion of the cable."""

T_v abstractmethod

T_v(x_one_per_span: ndarray) -> ndarray

Vertical tension on the cable, depending on the abscissa.

Args: x_one_per_span: array of abscissa, one abscissa per span: should be at the same length as span_length/elevation_difference/p

Example with 3 spans, named a, b, c:

span_length = [500, 600, 700]

p = [2_000, 1_500, 1_000]

Then, x_one_per_span must be of size 3. Each element refers to one span:

x_one_per_span = [x_a, x_b, x_c]

Then, the output is: T_v = [T_v(x_a), T_v(x_b), T_v(x_c)]

Source code in src/mechaphlowers/core/models/cable/span.py
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
@abstractmethod
def T_v(self, x_one_per_span: np.ndarray) -> np.ndarray:
    """Vertical tension on the cable, depending on the abscissa.

    Args:
    x_one_per_span: array of abscissa, one abscissa per span: should be at the same length as span_length/elevation_difference/p

    Example with 3 spans, named a, b, c:

    `span_length = [500, 600, 700]`

    `p = [2_000, 1_500, 1_000]`

    Then, x_one_per_span must be of size 3. Each element refers to one span:

    `x_one_per_span = [x_a, x_b, x_c]`

    Then, the output is:
    `T_v = [T_v(x_a), T_v(x_b), T_v(x_c)]`
    """

compute_L abstractmethod

compute_L() -> ndarray

Total length of the cable. Should be called after calling compute_x_m and compute_x_n if x_n or x_m have changed

Source code in src/mechaphlowers/core/models/cable/span.py
309
310
311
312
@abstractmethod
def compute_L(self) -> np.ndarray:
    """Total length of the cable.
    Should be called after calling compute_x_m and compute_x_n if x_n or x_m have changed"""

compute_partial_L abstractmethod

compute_partial_L(new_a: ndarray) -> ndarray

Cable length from left hanging point to point corresponding to span length new_a.

Source code in src/mechaphlowers/core/models/cable/span.py
314
315
316
@abstractmethod
def compute_partial_L(self, new_a: np.ndarray) -> np.ndarray:
    """Cable length from left hanging point to point corresponding to span length new_a."""

compute_values

compute_values()

Compute and store values for x_m, x_n and L based on current attributes. T_mean depends on these values, so this method should be called before calling T_mean(), especially if an attribute has been updated.

The goal of this implementation is to reduce the number of times compute_x_m, compute_x_n and compute_L are called during solver iterations.

Source code in src/mechaphlowers/core/models/cable/span.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def compute_values(self):
    """Compute and store values for x_m, x_n and L based on current attributes.
    T_mean depends on these values, so this method should be called before calling T_mean(),
    especially if an attribute has been updated.

    The goal of this implementation is to reduce the number of times compute_x_m, compute_x_n and compute_L
    are called during solver iterations.
    """
    self._x_m = self.compute_x_m()
    # Optimisation: not using compute_x_n() and compute_L()
    # to avoid calling compute_x_m() many times
    self._x_n = self.span_length + self._x_m
    p = self.parameter
    self._L = p * (np.sinh(self._x_n / p) - np.sinh(self._x_m / p))

compute_x_m abstractmethod

compute_x_m() -> ndarray

Distance between the lowest point of the cable and the left hanging point, projected on the horizontal axis.

In other words: opposite of the abscissa of the left hanging point.

Source code in src/mechaphlowers/core/models/cable/span.py
273
274
275
276
277
278
@abstractmethod
def compute_x_m(self) -> np.ndarray:
    """Distance between the lowest point of the cable and the left hanging point, projected on the horizontal axis.

    In other words: opposite of the abscissa of the left hanging point.
    """

compute_x_n

compute_x_n() -> ndarray

Distance between the lowest point of the cable and the right hanging point, projected on the horizontal axis.

In other words: abscissa of the right hanging point.

Source code in src/mechaphlowers/core/models/cable/span.py
280
281
282
283
284
285
286
def compute_x_n(self) -> np.ndarray:
    """Distance between the lowest point of the cable and the right hanging point, projected on the horizontal axis.

    In other words: abscissa of the right hanging point.
    """
    a = self.span_length
    return a + self.compute_x_m()

get_coords abstractmethod

get_coords(resolution: int) -> tuple[ndarray, ndarray]

Get x and z coordinates for catenary generation in cable frame

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: x and z coordinates of the cable

Source code in src/mechaphlowers/core/models/cable/span.py
318
319
320
321
322
323
324
@abstractmethod
def get_coords(self, resolution: int) -> tuple[np.ndarray, np.ndarray]:
    """Get x and z coordinates for catenary generation in cable frame

    Returns:
        tuple[np.ndarray, np.ndarray]: x and z coordinates of the cable
    """

mirror

mirror(span_model: Self) -> None

Copy attributes from an other ISpan object. This method is useful for copying values and keeping the reference of the current object.

Parameters:

Name Type Description Default

span_model

Self

other ISpan object to copy attribute from

required

Examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>>> span1 = CatenarySpan(
...     span_length=np.array([500, 600]),
...     elevation_difference=np.array([10, 20]),
...     parameter=np.array([2000, 1500]),
...)
>>> span2 = CatenarySpan(
...     span_length=np.array([100, 100]),
...     elevation_difference=np.array([0, 0]),
...     parameter=np.array([500, 500]),
... )
>>> span2.mirror(span1)
# now span2 has the same attributes as span1
>>> span2.span_length
array([500, 600])
Source code in src/mechaphlowers/core/models/cable/span.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def mirror(self, span_model: Self) -> None:
    """Copy attributes from an other ISpan object.
    This method is useful for copying values and keeping the reference of the current object.

    Args:
        span_model (Self): other ISpan object to copy attribute from

    Examples:
            >>> span1 = CatenarySpan(
            ...     span_length=np.array([500, 600]),
            ...     elevation_difference=np.array([10, 20]),
            ...     parameter=np.array([2000, 1500]),
            ...)
            >>> span2 = CatenarySpan(
            ...     span_length=np.array([100, 100]),
            ...     elevation_difference=np.array([0, 0]),
            ...     parameter=np.array([500, 500]),
            ... )
            >>> span2.mirror(span1)
            # now span2 has the same attributes as span1
            >>> span2.span_length
            array([500, 600])
    """
    self.span_length = span_model.span_length
    self.elevation_difference = span_model.elevation_difference
    self.parameter = span_model.parameter
    self.linear_weight = span_model.linear_weight
    self.load_coefficient = span_model.load_coefficient
    self.span_index = span_model.span_index
    self.span_type = span_model.span_type

sag abstractmethod

sag() -> ndarray

Sag of the cable span (s1 formula).

The sag is the maximum perpendicular distance between the cable and the chord connecting both attachment points.

Returns:

Type Description
ndarray

np.ndarray: sag value for each span.

Source code in src/mechaphlowers/core/models/cable/span.py
396
397
398
399
400
401
402
403
404
405
@abstractmethod
def sag(self) -> np.ndarray:
    """Sag of the cable span (s1 formula).

    The sag is the maximum perpendicular distance between the cable and the chord
    connecting both attachment points.

    Returns:
        np.ndarray: sag value for each span.
    """

sag_s2 abstractmethod

sag_s2() -> ndarray

Sag of the cable span, computed with the s2 formula.

The sag s2 is the maximum perpendicular distance between the lowest point of the cable and the lowest hanging point.

Returns:

Type Description
ndarray

np.ndarray: sag s2 value for each span.

Source code in src/mechaphlowers/core/models/cable/span.py
407
408
409
410
411
412
413
414
415
@abstractmethod
def sag_s2(self) -> np.ndarray:
    """Sag of the cable span, computed with the s2 formula.

    The sag s2 is the maximum perpendicular distance between the lowest point of the cable and the lowest hanging point.

    Returns:
        np.ndarray: sag s2 value for each span.
    """

set_lengths

set_lengths(
    span_length: ndarray, elevation_difference: ndarray
)

Set value of span_length and elevation_difference and compute x_m, x_n and L.

Parameters:

Name Type Description Default

span_length

ndarray

new value for span_length parameter.

required

elevation_difference

ndarray

new value for elevation_difference parameter.

required
Source code in src/mechaphlowers/core/models/cable/span.py
122
123
124
125
126
127
128
129
130
131
132
133
def set_lengths(
    self, span_length: np.ndarray, elevation_difference: np.ndarray
):
    """Set value of span_length and elevation_difference and compute x_m, x_n and L.

    Args:
        span_length (np.ndarray): new value for span_length parameter.
        elevation_difference (np.ndarray): new value for elevation_difference parameter.
    """
    self.span_length = span_length
    self.elevation_difference = elevation_difference
    self.compute_values()

set_parameter

set_parameter(parameter: ndarray)

Set value of sagging parameter and compute x_m, x_n and L.

Parameters:

Name Type Description Default

parameter

ndarray

new value for sagging parameter.

required
Source code in src/mechaphlowers/core/models/cable/span.py
135
136
137
138
139
140
141
142
def set_parameter(self, parameter: np.ndarray):
    """Set value of sagging parameter and compute x_m, x_n and L.

    Args:
        parameter (np.ndarray): new value for sagging parameter.
    """
    self.parameter = parameter
    self.compute_values()

slope abstractmethod

slope(side: Literal['left', 'right']) -> ndarray

Slope angle at the supports in radians.

Returns:

Type Description
ndarray

np.ndarray: slope angle at each support in radians.

Source code in src/mechaphlowers/core/models/cable/span.py
377
378
379
380
381
382
383
@abstractmethod
def slope(self, side: Literal['left', 'right']) -> np.ndarray:
    """Slope angle at the supports in radians.

    Returns:
        np.ndarray: slope angle at each support in radians.
    """

tensions_sup_inf

tensions_sup_inf() -> tuple[ndarray, ndarray]

Cable tensions at attachment points, x_m and x_n.

The two attachments have different values, the highest value is the one with the higher altitude.

Returns (T_high, T_low), T_high being the higher tension of the two values.

Source code in src/mechaphlowers/core/models/cable/span.py
385
386
387
388
389
390
391
392
393
394
def tensions_sup_inf(self) -> tuple[np.ndarray, np.ndarray]:
    """Cable tensions at attachment points, x_m and x_n.

    The two attachments have different values, the highest value is the one with the higher altitude.

    Returns (T_high, T_low), T_high being the higher tension of the two values.
    """
    x_m, x_n = self.x_m, self.x_n
    Tm_Tn = np.array([self.T(x_m), self.T(x_n)])
    return (np.max(Tm_Tn, axis=0), np.min(Tm_Tn, axis=0))

update_from_dict

update_from_dict(data: dict) -> None

Update the span model with new data.

Parameters:

Name Type Description Default

data

dict

Dictionary containing the new data.

required
Source code in src/mechaphlowers/core/models/cable/span.py
218
219
220
221
222
223
224
225
226
def update_from_dict(self, data: dict) -> None:
    """Update the span model with new data.

    Args:
            data (dict): Dictionary containing the new data.
    """
    for key, value in data.items():
        if hasattr(self, key):
            setattr(self, key, value)

x abstractmethod

x(resolution: int) -> ndarray

x_coordinate for catenary generation in cable frame: abscissa of the different points of the cable

Args: resolution (int, optional): Number of point to generation between supports.

Returns: np.ndarray: points generated x number of rows in SectionArray. Last column is nan due to the non-definition of last span.

Source code in src/mechaphlowers/core/models/cable/span.py
288
289
290
291
292
293
294
295
296
297
@abstractmethod
def x(self, resolution: int) -> np.ndarray:
    """x_coordinate for catenary generation in cable frame: abscissa of the different points of the cable

    Args:
    resolution (int, optional): Number of point to generation between supports.

    Returns:
    np.ndarray: points generated x number of rows in SectionArray. Last column is nan due to the non-definition of last span.
    """

z_many_points abstractmethod

z_many_points(x: ndarray) -> ndarray

Altitude of cable points depending on the abscissa.

Args: x: abscissa

Returns: altitudes based on the sag tension parameter "p" stored in the model.

x is an array of any length.

Example with 3 spans, named a, b, c:

span_length = [500, 600, 700]

p = [2_000, 1_500, 1_000]

x = [x0, x1, x2, x3]

Then, the output is:

1
2
3
4
5
6
              z = [
                  [z0_a, z0_b, z0_c],
                  [z1_a, z1_b, z1_c],
                  [z2_a, z2_b, z2_c],
                  [z3_a, z3_b, z3_c],
              ]

Source code in src/mechaphlowers/core/models/cable/span.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
@abstractmethod
def z_many_points(self, x: np.ndarray) -> np.ndarray:
    """Altitude of cable points depending on the abscissa.

    Args:
    x: abscissa

    Returns:
    altitudes based on the sag tension parameter "p" stored in the model.


    x is an array of any length.

    Example with 3 spans, named a, b, c:

    `span_length = [500, 600, 700]`

    `p = [2_000, 1_500, 1_000]`

    `x = [x0, x1, x2, x3]`

    Then, the output is:
    ```
                  z = [
                      [z0_a, z0_b, z0_c],
                      [z1_a, z1_b, z1_c],
                      [z2_a, z2_b, z2_c],
                      [z3_a, z3_b, z3_c],
                  ]
    ```
    """

z_one_point abstractmethod

z_one_point(x: ndarray) -> ndarray

Altitude of cable point depending on the abscissa. One cable point per span If there is 2 spans/ 3 supports:

span_length = [500, 600, 700] p = [2_000, 1_500, 1_000] x = [x0, x1, x2]

Then the output is: z = [z0, z1, z2]

Source code in src/mechaphlowers/core/models/cable/span.py
260
261
262
263
264
265
266
267
268
269
270
271
@abstractmethod
def z_one_point(self, x: np.ndarray) -> np.ndarray:
    """Altitude of cable point depending on the abscissa. One cable point per span
    If there is 2 spans/ 3 supports:

    `span_length = [500, 600, 700]`
    `p = [2_000, 1_500, 1_000]`
    `x = [x0, x1, x2]`

    Then the output is:
    z = [z0, z1, z2]
    """

span_model_builder

Builds a Span object, using data from SectionArray and CableArray

Parameters:

Name Type Description Default

section_array

SectionArray

input data (span_length, elevation_difference, parameter)

required

cable_array

CableArray

input data from cable (only linar weight used here)

required

span_model_type

Type[Span]

choose the type of span model to use

required

Returns:

Name Type Description
Span ISpan

span model to return

Source code in src/mechaphlowers/core/models/cable/span.py
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
def span_model_builder(
    section_array: SectionArray,
    cable_array: CableArray,
    span_model_type: Type[ISpan],
) -> ISpan:
    """Builds a Span object, using data from SectionArray and CableArray

    Args:
        section_array (SectionArray): input data (span_length, elevation_difference, parameter)
        cable_array (CableArray): input data from cable (only linar weight used here)
        span_model_type (Type[Span]): choose the type of span model to use

    Returns:
        Span: span model to return
    """
    span_length = section_array.data.span_length.to_numpy()
    elevation_difference = section_array.data.elevation_difference.to_numpy()
    parameter = section_array.data.sagging_parameter.to_numpy()
    linear_weight = np.float64(cable_array.data.linear_weight.iloc[0])
    return span_model_type(
        span_length,
        elevation_difference,
        parameter,
        linear_weight=linear_weight,
    )