Skip to content

line_angles

CablePlane

CablePlane(
    span_length: ndarray,
    conductor_attachment_altitude: ndarray,
    crossarm_length: ndarray,
    insulator_length: ndarray,
    line_angle: ndarray,
    beta: ndarray,
    get_displacement: Callable[[], ndarray],
    get_attachments_coords: Callable,
)

This class handles the parameters for defining the cable plane

Source code in src/mechaphlowers/core/geometry/line_angles.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
def __init__(
    self,
    span_length: np.ndarray,
    conductor_attachment_altitude: np.ndarray,
    crossarm_length: np.ndarray,
    insulator_length: np.ndarray,
    line_angle: np.ndarray,
    beta: np.ndarray,
    get_displacement: Callable[[], np.ndarray],
    get_attachments_coords: Callable,
):
    self.get_attachments_coords = get_attachments_coords
    self.displacement_vector = DisplacementVector(
        get_displacement, line_angle
    )

    self.a = span_length
    self.line_angle = line_angle
    self.conductor_attachment_altitude = conductor_attachment_altitude
    self.crossarm_length = crossarm_length
    self.insulator_length = insulator_length
    self.beta = beta

a_chain property

a_chain: ndarray

Span length, taking into account arm, line angles and chain

a_prime property

a_prime: ndarray

Span length after taking wind angle into account

azimuth_angle property

azimuth_angle: ndarray

Azimuth angle: horizontal angle between the current span (chain and arm included) and the first line (the line between the first two supports)

b property

b

no need here to compute b but for memory // for coherence of the class it could be added one day

b_chain property

b_chain: ndarray

Elevation difference, taking into account arm, line angles and chain

b_prime property

b_prime: ndarray

Elevation difference after taking wind angle into account

DisplacementVector

DisplacementVector(
    get_displacement: Callable[[], ndarray],
    line_angle: ndarray,
)

Class to store chain displacement, and change frame of displacement vector into the global frame

Source code in src/mechaphlowers/core/geometry/line_angles.py
265
266
267
268
269
270
271
272
def __init__(
    self,
    get_displacement: Callable[[], np.ndarray],
    line_angle: np.ndarray,
) -> None:
    self.get_displacement = get_displacement
    self.line_angle = line_angle
    self.change_frame()

dxdydz_global_frame property

dxdydz_global_frame: ndarray

Returns displacement vector dxdydz in the global frame.

Format: [[dx0, dy0, dz0], [dx1, dy1, dz1], ...]

Returns:

Type Description
ndarray

np.ndarray: displacement vector in the global frame

change_frame

change_frame() -> None

Change frame of displacement vector from support frame to global frame

Source code in src/mechaphlowers/core/geometry/line_angles.py
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
def change_frame(self) -> None:
    """Change frame of displacement vector from support frame to global frame"""
    line_angle_sums = np.cumsum(self.line_angle)

    temp_value = rotation_quaternion_same_axis(
        self.get_displacement(),
        line_angle_sums,
        rotation_axis=np.array([0, 0, 1]),
    )
    # Rotate the translation vectors to take into account the angle of the line
    self._dxdydz_global_frame = rotation_quaternion_same_axis(
        temp_value,
        -self.line_angle / 2,
        rotation_axis=np.array([0, 0, 1]),
    )

angle_between_vectors

angle_between_vectors(
    vector_a: ndarray, vector_b: ndarray
) -> ndarray

Calculate the angle between two 2D vectors.

Parameters:

Name Type Description Default

vector_a

ndarray

A 2D array of shape (n, 2) representing the first set of vectors.

required

vector_b

ndarray

A 2D array of shape (n, 2) representing the second set of vectors.

required

Returns:

Type Description
ndarray

A 1D array of angles in radians, where each angle corresponds to the angle between the vectors at the same index.

Source code in src/mechaphlowers/core/geometry/line_angles.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def angle_between_vectors(
    vector_a: np.ndarray, vector_b: np.ndarray
) -> np.ndarray:
    """Calculate the angle between two 2D vectors.

    Arguments:
        vector_a: A 2D array of shape (n, 2) representing the first set of vectors.
        vector_b: A 2D array of shape (n, 2) representing the second set of vectors.

    Returns:
        A 1D array of angles in radians, where each angle corresponds to the angle between the vectors at the same index.
    """
    cross_product = np.cross(vector_a, vector_b)
    dot_product = np.vecdot(vector_a, vector_b)
    angle_result = np.arctan2(cross_product, dot_product)
    # Return NaN if either vector is null
    is_vector_null = np.logical_or(
        (vector_a == 0).all(axis=1), (vector_b == 0).all(axis=1)
    )
    return np.where(is_vector_null, np.nan, angle_result)

compute_span_azimuth

compute_span_azimuth(attachment_coords: ndarray) -> ndarray

compute_span_azimuth

Compute the azimuth angle of the span between two attachment points. The azimuth angle is the angle between the x-axis and the line connecting two attachment points in the xy-plane. The angle is computed in radians and rotation is counter-clockwise (trigonometric).

Parameters:

Name Type Description Default

attachment_coords

ndarray

Attachment coordinates of the span.

required

Returns:

Type Description
ndarray

1D array of shape (n,) representing the azimuth angle of the span in radians.

Source code in src/mechaphlowers/core/geometry/line_angles.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def compute_span_azimuth(
    attachment_coords: np.ndarray,
) -> np.ndarray:
    """compute_span_azimuth

    Compute the azimuth angle of the span between two attachment points.
    The azimuth angle is the angle between the x-axis and the line connecting two attachment points in the xy-plane.
    The angle is computed in radians and rotation is counter-clockwise (trigonometric).

    Args:
        attachment_coords (np.ndarray): Attachment coordinates of the span.

    Returns:
        1D array of shape (n,) representing the azimuth angle of the span in radians.
    """
    vector_attachment_to_next = (
        np.roll(attachment_coords[:, :-1], -1, axis=0)
        - attachment_coords[:, :-1]
    )
    full_x_axis = np.full_like(vector_attachment_to_next, np.array([1, 0]))
    rotation_angles = angle_between_vectors(
        full_x_axis,
        vector_attachment_to_next,
    )
    rotation_angles[-1] = np.nan
    return rotation_angles

get_attachment_coords

get_attachment_coords(
    edge_arm_coords: ndarray, displacement_vector: ndarray
) -> ndarray

Get the coordinates of the attachment points in the global frame. These are the coordinates of the end of the suspension insulators. Currently, we assume that isulators set are vetical.

Parameters:

Name Type Description Default

edge_arm_coords

ndarray

coordinates of the edge of the arms (output of get_edge_arm_coords())

required

displacement_vector

ndarray

displacement vector of the chains (output of BalanceEngine.change_state())

required

Returns:

Type Description
ndarray

np.ndarray: coordinates of the attachment points in the global frame.

Source code in src/mechaphlowers/core/geometry/line_angles.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def get_attachment_coords(
    edge_arm_coords: np.ndarray,
    displacement_vector: np.ndarray,
) -> np.ndarray:
    """Get the coordinates of the attachment points in the global frame. These are the coordinates of the end of the suspension insulators.
    Currently, we assume that isulators set are vetical.

    Args:
        edge_arm_coords (np.ndarray): coordinates of the edge of the arms (output of `get_edge_arm_coords()`)
        displacement_vector (np.ndarray): displacement vector of the chains (output of BalanceEngine.change_state())

    Returns:
        np.ndarray: coordinates of the attachment points in the global frame.
    """
    return edge_arm_coords + displacement_vector

get_edge_arm_coords

get_edge_arm_coords(
    supports_ground_coords: ndarray,
    conductor_attachment_altitude: ndarray,
    crossarm_length: ndarray,
    line_angle: ndarray,
    insulator_length: ndarray,
) -> tuple[ndarray, ndarray]

Build the supports and arms in the global frame.

Parameters:

Name Type Description Default

supports_ground_coords

ndarray

coordinates of ground supports (output of get_supports_ground_coords())

required

conductor_attachment_altitude

ndarray

attachment altitude (input from SectionArray)

required

crossarm_length

ndarray

crossarm lengths (input from SectionArray)

required

line_angle

ndarray

line angles (input from SectionArray)

required

insulator_length

ndarray

insulator lengths (input from SectionArray)

required

Returns:

Type Description
tuple[ndarray, ndarray]

Returns two 2D arrays of shape (n, 3): - center_arm_coords: coordinates of the intersection of arms and supports in the global frame - edge_arm_coords: coordinates of the edge of the arms in the global frame

Source code in src/mechaphlowers/core/geometry/line_angles.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def get_edge_arm_coords(
    supports_ground_coords: np.ndarray,
    conductor_attachment_altitude: np.ndarray,
    crossarm_length: np.ndarray,
    line_angle: np.ndarray,
    insulator_length: np.ndarray,
) -> tuple[np.ndarray, np.ndarray]:
    """Build the supports and arms in the global frame.

    Args:
        supports_ground_coords (np.ndarray): coordinates of ground supports (output of `get_supports_ground_coords()`)
        conductor_attachment_altitude (np.ndarray): attachment altitude (input from SectionArray)
        crossarm_length (np.ndarray): crossarm lengths (input from SectionArray)
        line_angle (np.ndarray): line angles (input from SectionArray)
        insulator_length (np.ndarray): insulator lengths (input from SectionArray)


    Returns:
        Returns two 2D arrays of shape (n, 3):
            - center_arm_coords: coordinates of the intersection of arms and supports in the global frame
            - edge_arm_coords: coordinates of the edge of the arms in the global frame
    """
    # Create the coordinates of the intersection of the arms and the supports by adding attachmeent altitude
    center_arm_coords = supports_ground_coords.copy()

    # TODO: to refactor later
    center_arm_coords[:, 2] = conductor_attachment_altitude
    center_arm_coords[1:-1, 2] = (
        conductor_attachment_altitude[1:-1] + insulator_length[1:-1]
    )

    line_angle_sums = np.cumsum(line_angle)
    # Create translation vectors, which are the vectors that follows the arm
    arm_translation_vectors = np.zeros((line_angle.size, 3))
    arm_translation_vectors[:, 1] = crossarm_length
    # Rotate the translation vectors into the global frame
    arm_translation_vectors = rotation_quaternion_same_axis(
        arm_translation_vectors,
        line_angle_sums,
        rotation_axis=np.array([0, 0, 1]),
    )
    # Rotate the translation vectors to take into account the angle of the line
    arm_translation_vectors = rotation_quaternion_same_axis(
        arm_translation_vectors,
        -line_angle / 2,
        rotation_axis=np.array([0, 0, 1]),
    )
    return center_arm_coords, center_arm_coords + arm_translation_vectors

get_elevation_diff_between_attachments

get_elevation_diff_between_attachments(
    attachment_coords: ndarray,
) -> ndarray

Get the elevation differences between attachment points.

Source code in src/mechaphlowers/core/geometry/line_angles.py
220
221
222
223
224
225
226
227
228
229
230
def get_elevation_diff_between_attachments(
    attachment_coords: np.ndarray,
) -> np.ndarray:
    """Get the elevation differences between attachment points."""
    attachment_coords_z = attachment_coords[:, 2]  # Keep only z coordinates
    # Calculate the altitude differences between consecutive attachment points
    # warning: this is right minus left (z_N - z_M in the span notation)
    alt_diff = np.roll(attachment_coords_z, -1, axis=0) - attachment_coords_z

    alt_diff[-1] = np.nan
    return alt_diff

get_insulator_layer

get_insulator_layer(
    edge_arm_coords: ndarray, attachment_coords: ndarray
) -> ndarray

Stack the coordinates of the insulators in the global frame.

Source code in src/mechaphlowers/core/geometry/line_angles.py
189
190
191
192
193
194
195
196
197
198
199
200
201
def get_insulator_layer(
    edge_arm_coords: np.ndarray,
    attachment_coords: np.ndarray,
) -> np.ndarray:
    """Stack the coordinates of the insulators in the global frame."""

    return np.stack(
        (
            edge_arm_coords,
            attachment_coords,
        ),
        axis=1,
    )

get_span_lengths_between_attachments

get_span_lengths_between_attachments(
    attachment_coords: ndarray,
) -> ndarray

Get the lengths between the attachment points.

Source code in src/mechaphlowers/core/geometry/line_angles.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def get_span_lengths_between_attachments(
    attachment_coords: np.ndarray,
) -> np.ndarray:
    """Get the lengths between the attachment points."""
    attachment_coords_x_y = attachment_coords[
        :, :2
    ]  # Keep only x and y coordinates
    # Calculate the lengths between consecutive attachment points
    lengths = np.linalg.norm(
        attachment_coords_x_y - np.roll(attachment_coords_x_y, -1, axis=0),
        axis=1,
    )
    lengths[-1] = np.nan
    return lengths

get_supports_coords

get_supports_coords(
    span_length: ndarray,
    line_angle: ndarray,
    conductor_attachment_altitude: ndarray,
    crossarm_length: ndarray,
    insulator_length: ndarray,
    displacement_vector: ndarray,
    ground_altitude: ndarray,
) -> tuple[ndarray, ndarray, ndarray, ndarray]

Helper to get all the coordinates of the supports packed in a tuple.

Source code in src/mechaphlowers/core/geometry/line_angles.py
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
259
def get_supports_coords(
    span_length: np.ndarray,
    line_angle: np.ndarray,
    conductor_attachment_altitude: np.ndarray,
    crossarm_length: np.ndarray,
    insulator_length: np.ndarray,
    displacement_vector: np.ndarray,
    ground_altitude: np.ndarray,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    """Helper to get all the coordinates of the supports packed in a tuple."""
    supports_ground_coords = get_supports_ground_coords(
        span_length, line_angle, ground_altitude
    )
    center_arm_coords, arm_coords = get_edge_arm_coords(
        supports_ground_coords,
        conductor_attachment_altitude,
        crossarm_length,
        line_angle,
        insulator_length,
    )
    attachment_coords = get_attachment_coords(arm_coords, displacement_vector)
    return (
        supports_ground_coords,
        center_arm_coords,
        arm_coords,
        attachment_coords,
    )

get_supports_ground_coords

get_supports_ground_coords(
    span_length: ndarray,
    line_angle: ndarray,
    ground_altitude: ndarray,
) -> ndarray

Get the coordinates of the supports in the global frame. These are the coordinates of the barycenter of the support, at ground.

Parameters:

Name Type Description Default

span_length

ndarray

span lengths between supports (input from SectionArray)

required

line_angle

ndarray

line angles (input from SectionArray)

required

Returns:

Type Description
ndarray

2D array of shape (n, 3) representing the coordinates of the supports in the global frame.

Source code in src/mechaphlowers/core/geometry/line_angles.py
 71
 72
 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
def get_supports_ground_coords(
    span_length: np.ndarray,
    line_angle: np.ndarray,
    ground_altitude: np.ndarray,
) -> np.ndarray:
    """Get the coordinates of the supports in the global frame. These are the coordinates of the barycenter of the support, at ground.

    Args:
        span_length (np.ndarray): span lengths between supports (input from SectionArray)
        line_angle (np.ndarray): line angles (input from SectionArray)

    Returns:
        2D array of shape (n, 3) representing the coordinates of the supports in the global frame.
    """
    line_angle_sums = np.cumsum(line_angle)
    # Creates the translations vectors: these are the vectors between two supports
    translations_vectors = np.empty((span_length.size, 3))
    translations_vectors[:, 0] = span_length
    translations_vectors[:, 1:2] = 0
    translations_vectors = rotation_quaternion_same_axis(
        translations_vectors,
        line_angle_sums,
        rotation_axis=np.array([0, 0, 1]),
    )
    # Computes the coordinates of the supports by adding successive translation vectors
    supports_ground_coords = np.cumsum(translations_vectors, axis=0)
    supports_ground_coords = np.roll(supports_ground_coords, 1, axis=0)
    # Ensure that the first coordinates are (0,0,0), and not (nan,nan,nan)
    supports_ground_coords[0, :] = np.array([0, 0, 0])
    # Add ground altitudes
    supports_ground_coords[:, 2] = ground_altitude
    return supports_ground_coords

get_supports_layer

get_supports_layer(
    supports_ground_coords: ndarray,
    center_arm_coords: ndarray,
    edge_arm_coords: ndarray,
) -> ndarray

Stack the coordinates of the supports and the arms in the global frame.

Source code in src/mechaphlowers/core/geometry/line_angles.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def get_supports_layer(
    supports_ground_coords: np.ndarray,
    center_arm_coords: np.ndarray,
    edge_arm_coords: np.ndarray,
) -> np.ndarray:
    """Stack the coordinates of the supports and the arms in the global frame."""

    return np.stack(
        (
            supports_ground_coords,
            center_arm_coords,
            edge_arm_coords,
        ),
        axis=1,
    )