OpenSeesPyAssistant 1.1
OpenSeesPy for everyone
FunctionalFeatures.py
Go to the documentation of this file.
1"""Module with useful functions (discretise curves, ID conventions, etc) \n
2Carmine Schipani, 2021
3"""
4
5import math
6import numpy as np
7import matplotlib.pyplot as plt
8import openseespy.postprocessing.internal_plotting_functions as ipltf
9from openseespy.opensees import *
11from OpenSeesPyAssistant.Units import *
12
13
14def ProgressingPercentage(max_iter, i: int, next_step: int, step = 10):
15 """
16 Function that shows the progressing percentage of an iterative process.
17
18 @param max_iter (int): Maximal number of interations
19 @param i (int): Current iteration
20 @param next_step (int): Next step of the percentage (set to 0 for the first iteration and then use the return parameter)
21 @param step (int, optional): Size of the step (should be a fraction of 100). Defaults to 10.
22
23 @returns int: The updated next step
24 """
25 if i*100.0/(max_iter-1) >= next_step:
26 print("The progression is {}%".format(next_step))
27 return next_step + step
28
29 return next_step
30
31
32def DiscretizeLoadProtocol(SDR_LP: np.ndarray, nr_cycles_LP: np.ndarray, discr_first_cycle: int, plot = False, block = False, show_original_peaks = True):
33 """
34 Discretized a cyclic load protocol keeping a similar discretisation step throughout the different cycles and keeping in the output the extremes (peaks).
35
36 @param SDR_LP (np.ndarray): Array (1 dimension) that stores the peaks of the cycles.
37 They needs to be only the positive peaks, beacuse this function will use them as the extreme for each cycle.
38 @param nr_cycles_LP (np.ndarray): Array (1 dimension) that stores the number of cycles for every extreme declared in 'SDR_LP' and its countepart negative.
39 They need to be positive integers.
40 @param discr_first_cycle (int): The number of points from peak to peak (counting the two peaks) in the first cycle. It should be odd.
41 @param plot (bool, optional): Option to show the plot of the discretized (and also the original peaks). Defaults to False.
42 @param block (bool, optional): Option to wait the user command 'plt.show()' (avoiding the stop of the program everytime that a plot should pop up). Defaults to False.
43 @param show_original_peaks (bool, optional): Option to show the original peaks to check if the discretized curve is correct.
44 The argument plot need to be True. Defaults to True.
45
46 @exception WrongDimension: SDR_LP and nr_cycles_LP need to be of same length.
47 @exception NegativeValue: SDR_LP needs to have only positive integers.
48 @exception NegativeValue: nr_cycles_LP needs to have only positive integers.
49 @exception NegativeValue: discr_first_cycle needs to be a positive integer.
50
51 @returns np.array: Array (1 dimension) that stores the new discretized load protocol curve.
52 """
53 if np.size(SDR_LP) != np.size(nr_cycles_LP): raise WrongDimension()
54 if any(col < 0 for col in SDR_LP): raise NegativeValue()
55 if any(col < 0 for col in nr_cycles_LP): raise NegativeValue()
56 if discr_first_cycle < 0: raise NegativeValue()
57
58 if discr_first_cycle % 2 == 0:
59 discr_first_cycle = discr_first_cycle + 1
60 discr_factor = discr_first_cycle / (SDR_LP[0]*2)
61 discretized_LP = [0.0]
62 x_val = []
63 skip_x = 0
64 for i in range(np.size(SDR_LP)):
65 discr_i = math.ceil(discr_factor*SDR_LP[i]*2)-1;
66 if discr_i % 2 == 0:
67 discr_i = discr_i + 1
68 length_tmp = int((discr_i+1)/2)
69 tmp_up = np.linspace(0.0, SDR_LP[i], length_tmp)
70 tmp_down = np.linspace(SDR_LP[i], 0.0, length_tmp)
71 for j in range(int(nr_cycles_LP[i])):
72 discretized_LP = np.append(discretized_LP, tmp_up[1:length_tmp])
73 discretized_LP = np.append(discretized_LP, tmp_down[1:length_tmp])
74 discretized_LP = np.append(discretized_LP, -tmp_up[1:length_tmp])
75 discretized_LP = np.append(discretized_LP, -tmp_down[1:length_tmp])
76 # for check of original peaks
77 x_val.append(length_tmp-1+skip_x)
78 skip_x = (length_tmp-1)*(4*(nr_cycles_LP[i]-1)+3)+x_val[-1]
79
80
81 if plot:
82 fig, ax = plt.subplots()
83 ax.plot(discretized_LP, '-r', label='Discretised LP')
84
85 ax.set(xlabel='Step number [-]', ylabel='Unit of the loading protocol',
86 title='Discretized loading protocol')
87 ax.grid()
88
89 if show_original_peaks:
90 ax.plot(x_val, SDR_LP, 'ob', label='Original LP')
91 ax.legend()
92
93 if block:
94 plt.show()
95
96 return discretized_LP
97
98
99def DiscretizeLinearly(LP: np.ndarray, discr: int, plot = False, block = False, show_original_LP = True):
100 """
101 This function discretize the curve 'LP' given adding the number of point given by 'discr' between every point (linearly).
102
103 @param LP (np.ndarray): Array (1 dimension) that stores the curve that needs to be discretized
104 @param discr (int): The number of points to add between every two points of 'LP' (linearly)
105 @param plot (bool, optional): Option to show the plot of the discretized (and also the original LP). Defaults to False.
106 @param block (bool, optional): Option to wait the user command 'plt.show()' (avoiding the stop of the program everytime that a plot should pop up). Defaults to False.
107 @param show_original_LP (bool, optional): Option to show the original LP to check if the discretized curve is correct. Defaults to True.
108
109 @returns np.ndarray: Array (1 dimension) that stores the new discretized load protocol.
110 """
111
112 #TODO: check discr nonnegative int and LP 1 dimension
113
114 # Define the new discretized LP
115 length = 1 + (np.size(LP)-1) * (discr+1)
116 discr_LP = np.zeros(length)
117
118 # Performa manually the first iteration
119 yprev = LP[0]
120 x = [0, 1]
121 discr_LP[0] = yprev
122 iter = 0
123
124 # add the other points and the discretized ones
125 for ynext in LP[1:]:
126 y = [yprev, ynext]
127
128 # Compute new points
129 xnew = np.linspace(x[0], x[1], discr+2)
130 ynew = np.interp(xnew[1:], x, y)
131
132 # Add to the recording vector discr_LP
133 index = np.array(np.arange(discr+1)+1+iter)
134 discr_LP[index] = ynew
135
136 # Prepare for next iteration
137 yprev = ynext
138 iter = iter + discr + 1
139
140 if plot:
141 fig, ax = plt.subplots()
142 ax.plot(discr_LP, '-r', label='Discretised LP')
143
144 ax.set(xlabel='Step number [-]', ylabel='Unit of the loading protocol',
145 title='Discretized loading protocol')
146 ax.grid()
147
148 if show_original_LP:
149 x_val = np.arange(0, np.size(discr_LP), discr+1)
150 ax.plot(x_val, LP, 'ob', label='Original LP')
151 ax.legend()
152
153 if block:
154 plt.show()
155
156 return discr_LP
157
158
159def GridIDConvention(pier_axis: int, floor_axis: int, max_pier = -1, max_floor = -1):
160 """
161 Function used to construct the ID of the nodes in the grid (first nodes that define the geometry of the model).
162 The conventional grid node ID is xy, with x = the pier position 'pier_axis'; y = the floor position 'floor_axis'.
163
164 @param pier_axis (int): The pier (or x) postion of the node.
165 @param floor_axis (int): The floor (or y) position of the node.
166 @param max_pier (int, optional): Maximal pier position of the model (used to identify the number of digits).
167 Defaults to -1, e.g. taken equal of pier_axis.
168 @param max_floor (int, optional): Maximal floor position of the model (used to identify the number of digits).
169 Defaults to -1, e.g. taken equal of floor_axis.
170
171 @exception NameError: Work In Progress: only 9 floors/bays.
172 @exception NegativeValue: The argument pier_axis needs to be a positive integer.
173 @exception NegativeValue: The argument floor_axis needs to be a positive integer.
174 @exception NegativeValue: The argument max_pier needs to be a positive integer if different from -1.
175 @exception NegativeValue: The argument max_floor needs to be a positive integer if different from -1.
176 @exception WrongArgument: The argument max_pier need to be equal or bigger to pier_axis
177 @exception WrongArgument: The argument max_floor need to be equal or bigger to floor_axis
178
179 @returns int: The grid node ID
180 """
181 # Convention:
182 # GridNodeID: xy with x = pier, y = floor
183 if pier_axis > 9 or floor_axis > 9 or max_pier > 9 or max_floor > 9: raise NameError("WIP: maximal 9 floors or bays")
184 max_pier = pier_axis if max_pier == -1 else max_pier
185 max_floor = floor_axis if max_floor == -1 else max_floor
186
187 if pier_axis < 0: raise NegativeValue()
188 if floor_axis < 0: raise NegativeValue()
189 if max_pier < 0: raise NegativeValue()
190 if max_floor < 0: raise NegativeValue()
191 if max_pier < pier_axis: raise WrongArgument()
192 if max_floor < floor_axis: raise WrongArgument()
193
194 max_x_digits = int(math.log10(max_pier))+1
195 max_y_digits = int(math.log10(max_floor))+1
196
197 # return 10**(max_x_digits+max_y_digits) + pier_axis*10**max_y_digits + floor_axis # with 1 as prefix (to consider more than one digit per axis, but exceed max ID)
198 return pier_axis*10**max_y_digits + floor_axis
199
200
201def IDConvention(prefix: int, suffix: int, n_zeros_between = 0):
202 """
203 Function used to construct IDs for elements and offgrid nodes.
204 It appends to a positive integer number 'prefix' a number of zeros 'n_zeros_between' and at last another positive integer 'suffix'.
205 The conventional element ID is xy(a)x'y'(a') with xya = the node ID in pier x, floor y and offgrid parameter a (optional);
206 x'y'a' = the node ID in pier x', floor y' and offgrid parameter a' (optional).
207 For more information on x and y, see GridIDConvention; for more information on a, see OffsetNodeIDConvention.
208
209 @param prefix (int): Prefix of the new ID. For a vertical element it should be the left node ID;
210 for an horizontal one it should be the bottom node.
211 @param suffix (int): Suffix of the new ID. For a vertical element it should be the right node ID;
212 for an horizontal one it should be the top node.
213 @param n_zeros_between (int, optional): Number of zeros to add between the two nodes. Defaults to 0.
214
215 @exception NegativeValue: The argument prefix needs to be a positive integer.
216 @exception NegativeValue: The argument suffix needs to be a positive integer.
217 @exception NegativeValue: The argument n_zeros_between needs to be a positive integer.
218
219 @returns int: The combined ID
220 """
221 # Convention:
222 # ElementID: xy(a)x'y'(a') with xy(a) = NodeID i and x'y'(a') = NodeID j
223 # TrussID: xy(a)x'y'(a') with xy(a) = NodeID i and x'y'(a') = NodeID j
224 # Spring: xy(a)x'y'(a') with xy(a) = NodeID i and x'y'(a') = NodeID j
225 if prefix < 0: raise NegativeValue()
226 if suffix < 0: raise NegativeValue()
227 if n_zeros_between < 0: raise NegativeValue()
228
229 return int(str(prefix*10**n_zeros_between) + str(suffix))
230
231
232def OffsetNodeIDConvention(node_ID: int, orientation: str, position_i_or_j: str):
233 """
234 Function used to add node on top of existing ones in the extremes of memebers with springs.
235
236 @param node_ID (int): Node that we refer to.
237 @param orientation (str): Orientation of the memeber. Can be 'vertical' or 'horizontal'.
238 @param position_i_or_j (str): Position at the start 'i' (left or bottom)
239 or at the end 'j' (right or top) of 'node_ID' in the member.
240
241 @exception NegativeValue: The argument node_ID needs to be a positive integer.
242 @exception WrongArgument: The argument position_i_or_j needs to be 'i' or 'j'
243 @exception WrongArgument: The argument orientation needs to be 'vertical' or 'horizontal'
244
245 @returns int: The combined ID
246 """
247 # Convention: o xy
248 # GridNodeID: xy with x = pier, y = floor o xy7
249 # AdditionalNodeID: xya with x = pier, y = floor, a: o xy xy2 o-------o x'y3 x'y o |
250 # PanelZoneNodeID: xy(a)a see MemberModel for the panel zone ID convention |
251 # |
252 # o xy'6
253 # o xy'
254 if node_ID < 1: raise NegativeValue()
255 if position_i_or_j != "i" and position_i_or_j != "j": raise WrongArgument()
256
257 if orientation == "vertical":
258 if position_i_or_j == "i":
259 return IDConvention(node_ID, 6)
260 else:
261 return IDConvention(node_ID, 7)
262 elif orientation == "horizontal":
263 if position_i_or_j == "i":
264 return IDConvention(node_ID, 2)
265 else:
266 return IDConvention(node_ID, 3)
267 else: raise WrongArgument()
268
269
270def NodesOrientation(iNode_ID: int, jNode_ID: int):
271 """
272 Function that finds the orientation of the vector with direction 'jNode_ID''iNode_ID'.
273 If the the nodes are on top of each other, the function returns 'zero_length'.
274
275 @param iNode_ID (int): Node i.
276 @param jNode_ID (int): Node j.
277
278 @exception NegativeValue: The argument iNode_ID needs to be a positive integer.
279 @exception NegativeValue: The argument jNode_ID needs to be a positive integer.
280
281 @returns str: The orientation of the vector.
282 """
283 if iNode_ID < 1: raise NegativeValue()
284 if jNode_ID < 1: raise NegativeValue()
285
286 iNode = np.array(nodeCoord(iNode_ID))
287 jNode = np.array(nodeCoord(jNode_ID))
288 if abs(iNode[0]-jNode[0]) + abs(iNode[1]-jNode[1]) == 0:
289 return "zero_length"
290 elif abs(iNode[0]-jNode[0]) < abs(iNode[1]-jNode[1]):
291 return "vertical"
292 else:
293 return "horizontal"
294
295
296def plot_member(element_array: list, member_name = "Member name not defined", show_element_ID = True, show_node_ID = True):
297 """
298 Function that plots a set of elements. It can be used to check the correctness of a part of the model or of a member.
299 If the entire model need to be plotted, use instead 'plot_model("nodes", "elements")' from openseespy.postprocessing.Get_Rendering. \n
300 Inspired by plot_model written by Anurag Upadhyay and Christian Slotboom.
301
302 @param element_array (list): An array (list of lists of one dimensions and length = 3) that store the element and nodes IDs.
303 An element is stored in one list with 3 entries: the element ID, node i ID and node j ID.
304 @param member_name (str, optional): The name of what is plotted. Defaults to "Member name not defined".
305 @param show_element_ID (bool, optional): Option to show the element IDs. Defaults to True.
306 @param show_node_ID (bool, optional): Option to show the nodes IDs. Defaults to True.
307
308 @exception WrongDimension: element_array needs to be non-empty.
309 @exception WrongDimension: The number of entries in the lists inside the argument element_array need to be 3.
310
311 @returns matplotlib.axes._subplots.AxesSubplo: The figure's wrappr, useful to customise the plot (change axes label, etc).
312 """
313 if len(element_array) == 0: raise WrongArgument()
314 if len(element_array[0]) != 3: raise WrongDimension()
315
316 node_style = {'color':'black', 'marker':'o', 'facecolor':'black','linewidth':0.}
317 node_text_style = {'fontsize':8, 'fontweight':'regular', 'color':'green'}
318 track_node = {}
319
320 if show_element_ID:
321 show_e_ID = 'yes'
322 else:
323 show_e_ID = 'no'
324
325 fig = plt.figure()
326 ax = fig.add_subplot(1,1,1)
327
328 for ele in element_array:
329 eleTag = int(ele[0])
330 Nodes =ele[1:]
331
332 if len(Nodes) == 2:
333 # 2D element
334 iNode = np.array(nodeCoord(Nodes[0]))
335 jNode = np.array(nodeCoord(Nodes[1]))
336 ipltf._plotBeam2D(iNode, jNode, ax, show_e_ID, eleTag, "solid")
337 ax.scatter(*iNode, **node_style)
338 ax.scatter(*jNode, **node_style)
339 if show_node_ID:
340 if abs(sum(iNode - jNode)) > 1e-6:
341 # beam-col
342 __plt_node(Nodes[0], track_node, iNode, ax, node_text_style)
343 __plt_node(Nodes[1], track_node, jNode, ax, node_text_style, h_align='right', v_align='bottom')
344 else:
345 # zerolength
346 __plt_node(Nodes[0], track_node, iNode, ax, node_text_style, h_align='right')
347 __plt_node(Nodes[1], track_node, jNode, ax, node_text_style)
348 else:
349 print("Too many nodes in this elemnet (see shell elements)")
350
351 ax.set_xlabel('x [{}]'.format(length_unit))
352 ax.set_ylabel('y [{}]'.format(length_unit))
353 plt.title("Visualisation of: {}".format(member_name))
354 plt.axis('equal')
355 return ax
356
357
358def plot_nodes(nodes_array: list, name = "Not defined", show_node_ID = True):
359 """
360 Function that plots a set of nodes. It can be used to check the correctness of the model's geometry.
361 If the entire model need to be plotted, use instead 'plot_model("nodes", "elements")' from openseespy.postprocessing.Get_Rendering.
362
363 @param nodes_array (list): List of 1 dimension with the IDs of the nodes to be displayed.
364 @param name (str, optional): Name that describe what the plot will show. Defaults to "Not defined".
365 @param show_node_ID (bool, optional): Option to show the node IDs. Defaults to True.
366
367 @exception WrongArgument: nodes_array needs to be non-empty.
368
369 @returns (matplotlib.axes._subplots.AxesSubplot): The figure's wrapper, useful to customise the plot (change axes label, etc).
370 """
371 if len(nodes_array) == 0: raise WrongArgument()
372
373 node_style = {'color':'black', 'marker':'o', 'facecolor':'black','linewidth':0.}
374 node_text_style = {'fontsize':8, 'fontweight':'regular', 'color':'green'}
375 track_node = {}
376
377 fig = plt.figure()
378 ax = fig.add_subplot(1,1,1)
379
380 for node_ID in nodes_array:
381 node_xy = np.array(nodeCoord(node_ID))
382 ax.scatter(*node_xy, **node_style)
383 if show_node_ID:
384 __plt_node(node_ID, track_node, node_xy, ax, node_text_style)
385
386 ax.set_xlabel('x [{}]'.format(length_unit))
387 ax.set_ylabel('y [{}]'.format(length_unit))
388 plt.title("Visualisation of: {}".format(name))
389 plt.axis('equal')
390 return ax
391
392
393def __plt_node(nodeID: int, track_node: dict, NodeXY, ax, node_text_style, x_off = 0, y_off = 0, h_align = 'left', v_align='top'):
394 """
395 PRIVATE FUNCTION. Used to plot the nodes in a controlled way (no repetition, position of the IDs, text style).
396
397 @param nodeID (int): The ID of the node.
398 @param track_node (dict): A dictionary used to avoid plotting a node multiple times.
399 @param NodeXY (list): List of dimension 1, length 2 with the position of the node.
400 @param ax (matplotlib.axes._subplots.AxesSubplot): The figure's wrappr.
401 @param node_text_style (dict): Dictionary for the text style.
402 @param x_off (int, optional): Offset in x direction. Defaults to 0.
403 @param y_off (int, optional): Offset in y direction. Defaults to 0.
404 @param h_align (str, optional): Horizontal alignment ('left' or 'right'). Defaults to 'left'.
405 @param v_align (str, optional): Vertical alignment ('center', 'top' or 'bottom'). Defaults to 'top'.
406 """
407 if not nodeID in track_node:
408 track_node[nodeID] = True
409 ax.text(NodeXY[0]+x_off, NodeXY[1]+y_off, nodeID,**node_text_style, horizontalalignment=h_align, verticalalignment=v_align)
410
411
413 """Class that manage the ID generation.
414 USE ONLY IF EVERY NODE IS DEFINED BY THE USER (because the OpenSeesPyAssistant modules use the convention defined in the functions above).
415 """
416 def __init__(self):
417 """The class constructor.
418 """
419 self.current_node_IDcurrent_node_ID = 0
420 self.current_element_IDcurrent_element_ID = 0
421 self.current_mat_IDcurrent_mat_ID = 0
422 self.current_fiber_IDcurrent_fiber_ID = 0
423
424 def GenerateIDNode(self):
425 """
426 Method that generate a unique node ID.
427
428 @returns int: The node ID.
429 """
430 self.current_node_IDcurrent_node_ID = self.current_node_IDcurrent_node_ID + 1
431 return self.current_node_IDcurrent_node_ID
432
434 """
435 Method that generate a unique element ID.
436
437 @returns int: The element ID.
438 """
439 self.current_element_IDcurrent_element_ID = self.current_element_IDcurrent_element_ID + 1
440 return self.current_element_IDcurrent_element_ID
441
442 def GenerateIDMat(self):
443 """
444 Method that generate a unique material ID.
445
446 @returns int: The material ID.
447 """
448 self.current_mat_IDcurrent_mat_ID = self.current_mat_IDcurrent_mat_ID + 1
449 return self.current_mat_IDcurrent_mat_ID
450
452 """
453 Method that generate a unique fiber ID.
454
455 @returns int: The fiber ID.
456 """
457 self.current_fiber_IDcurrent_fiber_ID = self.current_fiber_IDcurrent_fiber_ID + 1
458 return self.current_fiber_IDcurrent_fiber_ID
459
460
Class that manage the ID generation.
def GenerateIDFiber(self)
Method that generate a unique fiber ID.
def GenerateIDNode(self)
Method that generate a unique node ID.
def GenerateIDMat(self)
Method that generate a unique material ID.
def __init__(self)
The class constructor.
def GenerateIDElement(self)
Method that generate a unique element ID.
def ProgressingPercentage(max_iter, int i, int next_step, step=10)
Function that shows the progressing percentage of an iterative process.
def OffsetNodeIDConvention(int node_ID, str orientation, str position_i_or_j)
Function used to add node on top of existing ones in the extremes of memebers with springs.
def NodesOrientation(int iNode_ID, int jNode_ID)
Function that finds the orientation of the vector with direction 'jNode_ID''iNode_ID'.
def IDConvention(int prefix, int suffix, n_zeros_between=0)
Function used to construct IDs for elements and offgrid nodes.
def DiscretizeLoadProtocol(np.ndarray SDR_LP, np.ndarray nr_cycles_LP, int discr_first_cycle, plot=False, block=False, show_original_peaks=True)
Discretized a cyclic load protocol keeping a similar discretisation step throughout the different cyc...
def DiscretizeLinearly(np.ndarray LP, int discr, plot=False, block=False, show_original_LP=True)
This function discretize the curve 'LP' given adding the number of point given by 'discr' between eve...
def GridIDConvention(int pier_axis, int floor_axis, max_pier=-1, max_floor=-1)
Function used to construct the ID of the nodes in the grid (first nodes that define the geometry of t...
def plot_member(list element_array, member_name="Member name not defined", show_element_ID=True, show_node_ID=True)
Function that plots a set of elements.
def plot_nodes(list nodes_array, name="Not defined", show_node_ID=True)
Function that plots a set of nodes.