wawi 0.0.3__py3-none-any.whl → 0.0.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of wawi might be problematic. Click here for more details.

wawi/abq.py DELETED
@@ -1,1128 +0,0 @@
1
- import numpy as np
2
- import pdb
3
-
4
- from abaqus import *
5
- from abaqus import session
6
- from abaqusConstants import *
7
- import __main__
8
- import section
9
- import regionToolset
10
- import displayGroupMdbToolset as dgm
11
- import step
12
- import part
13
- import material
14
- import assembly
15
- import interaction
16
- import load
17
- import mesh
18
- import optimization
19
- import job
20
- import sketch
21
- import visualization
22
- import xyPlot
23
- import displayGroupOdbToolset as dgo
24
- import connectorBehavior
25
- import symbolicConstants
26
- import odbAccess
27
- import shutil
28
-
29
- import csv
30
- from copy import deepcopy
31
-
32
- import numpy as np
33
- import os
34
-
35
- from .general import merge_tr_phi
36
-
37
- '''
38
- Abaqus interaction module
39
- '''
40
-
41
- ## Functions to retrieve data from ODB
42
-
43
- def modalparameters(frequency_step):
44
- '''
45
- Output the modal parameters from frequency step of current output database.
46
-
47
- Parameters
48
- -------------
49
- frequency_step : str
50
- name of step containing the modal results (frequency step)
51
-
52
- Returns
53
- --------------
54
- f : float
55
- numpy array with undamped natural frequencies in Hz of all modes computed
56
- m : float
57
- numpy array with modal mass for all modes computed
58
- '''
59
-
60
- odb = get_db('odb')
61
- history_region_key = odb.steps[frequency_step].historyRegions.keys()[0]
62
-
63
- ftemp = odb.steps[frequency_step].historyRegions[history_region_key].historyOutputs['EIGFREQ'].data
64
- f = np.array([x[1] for x in ftemp])
65
-
66
- if 'GM' in odb.steps[frequency_step].historyRegions[history_region_key].historyOutputs.keys():
67
- mtemp = odb.steps[frequency_step].historyRegions[history_region_key].historyOutputs['GM'].data
68
- m = np.array([x[1] for x in mtemp])
69
- else:
70
- m = np.ones(np.shape(f)) #if no GM field is available, mass normalization is assumed used on eigenvalues
71
- return f, m
72
-
73
-
74
- def modeshapes_from_region(regionobjs, frequency_step, field_outputs):
75
- """
76
- Get modes (shape, frequency and modal mass) from "Frequency step" (eigenvalue analysis) in active Abaqus ODB.
77
-
78
- Args:
79
- regionobjs: Abaqus region objects in list
80
- frequency_step: name of frequency step
81
- field_outputs: list of strings with field output quantities, e.g., ['U', 'UR']
82
- Returns:
83
- phi: mode shape transformation matrix, ordered as NumPy matrices in list for each specified outputs
84
- f: undamped natural frequencies
85
- m: modal mass
86
- output_dict: dictionary to access correct index in output phi
87
-
88
- AAJ / Knut Andreas Kvaale, 2017
89
- Further developed NTNU / Knut Andreas Kvaale, 2018
90
- """
91
- odb = get_db('odb')
92
-
93
- if odb.steps[frequency_step].domain != MODAL: #MODAL is a variable in abaqusConstants
94
- raise TypeError('Type of input step is not modal!')
95
-
96
- Nmodes = len(odb.steps[frequency_step].frames)-1
97
- phi = [None]*len(field_outputs)
98
-
99
- for iout, field_output in enumerate(field_outputs):
100
- Ndofs, point_ranges, dof_ranges = count_region(regionobjs, field_output, odb.steps[frequency_step].frames[0])
101
- phio = np.zeros([np.sum(Ndofs), Nmodes])
102
- foobj0 = odb.steps[frequency_step].frames[0].fieldOutputs[field_output]
103
-
104
- for ix, regionobj in enumerate(regionobjs):
105
- current_dof_range = np.arange(dof_ranges[ix], dof_ranges[ix+1])
106
-
107
- for mode in range(0, Nmodes):
108
- foobj = odb.steps[frequency_step].frames[mode+1].fieldOutputs[field_output]
109
- phio[:, mode] = np.reshape((np.array([v.data for v in foobj.getSubset(region=regionobj).values])), [np.sum(Ndofs)])
110
-
111
- phi[iout] = phio
112
-
113
- return phi
114
-
115
-
116
- def modeshapes_from_nodelist(node_labels, frequency_step, field_outputs):
117
- """
118
- Get mode shapes from "Frequency step" (eigenvalue analysis) in active Abaqus ODB.
119
-
120
- Args:
121
- node_labels:
122
- frequency_step:
123
- field_outputs:
124
- Returns:
125
- phi: mode shape transformation matrix, ordered as NumPy matrices in list for each specified outputs
126
-
127
- NTNU / Knut Andreas Kvaale, 2018
128
- """
129
- odb = get_db('odb')
130
-
131
- if odb.steps[frequency_step].domain != MODAL: #MODAL is a variable in abaqusConstants
132
- raise TypeError('Type of input step is not modal!')
133
-
134
- Nnodes = len(node_labels)
135
- Nmodes = len(odb.steps[frequency_step].frames) - 1
136
- phi = [None]*len(field_outputs)
137
- basedisp = [None]*len(field_outputs)
138
-
139
- for iout, field_output in enumerate(field_outputs):
140
- foobj0 = odb.steps[frequency_step].frames[0].fieldOutputs[field_output]
141
-
142
- Ndofs = len(foobj0.values[0].data)
143
- phio = np.zeros([Ndofs*Nnodes, Nmodes])
144
-
145
- # Get correct data indices to get correct order (as given in node_labels)
146
- all_nodes = [value.nodeLabel for value in foobj0.values]
147
- data_indices = [None]*Nnodes
148
-
149
- for ix, node in enumerate(node_labels):
150
- data_indices[ix] = all_nodes.index(node)
151
-
152
- basedisp[iout] = np.array([foobj0.values[data_ix].data for data_ix in data_indices]).flatten()
153
-
154
- for mode in range(0, Nmodes):
155
- foobj = odb.steps[frequency_step].frames[mode+1].fieldOutputs[field_output]
156
- phio[:, mode] = np.array([foobj.values[data_ix].data for data_ix in data_indices]).flatten()
157
-
158
- phi[iout] = phio
159
-
160
- return phi, basedisp
161
-
162
-
163
- def modeshapes_from_elementlist(element_labels, frequency_step, field_outputs):
164
- """
165
- Get mode shape from "Frequency step" (eigenvalue analysis) in active Abaqus ODB.
166
-
167
- Args:
168
- node_labels:
169
- frequency_step:
170
- field_outputs:
171
- Returns:
172
- phi: mode shape transformation matrix, ordered as NumPy matrices in list for each specified outputs
173
-
174
- NTNU / Knut Andreas Kvaale, 2018
175
- """
176
- odb = get_db('odb')
177
-
178
- if odb.steps[frequency_step].domain != MODAL: #MODAL is a variable in abaqusConstants
179
- raise TypeError('Type of input step is not modal!')
180
-
181
-
182
- Nmodes = len(odb.steps[frequency_step].frames) - 1
183
- phi = [None]*len(field_outputs)
184
- integration_points = [None]*len(field_outputs)
185
-
186
- for iout, field_output in enumerate(field_outputs):
187
- foobj0 = odb.steps[frequency_step].frames[0].fieldOutputs[field_output]
188
- Ndofs = len(foobj0.values[0].data)
189
-
190
- # Get correct data indices to get correct order (as given in node_labels)
191
- all_elements = [value.elementLabel for value in foobj0.values]
192
- all_integration_points = [value.integrationPoint for value in foobj0.values]
193
-
194
- Nintpoints = len(element_labels) # number of integration points (same element label might appear multiple times if multiple integration points in element)
195
- phio = np.zeros([Ndofs*Nintpoints, Nmodes])
196
-
197
- data_indices = [None]*Nintpoints
198
-
199
- for ix, element in enumerate(element_labels):
200
- data_indices[ix] = all_elements.index(element)
201
-
202
- for mode in range(0, Nmodes):
203
- foobj = odb.steps[frequency_step].frames[mode+1].fieldOutputs[field_output]
204
- phio[:, mode] = np.array([foobj.values[data_ix].data for data_ix in data_indices]).flatten()
205
-
206
- integration_points[iout] = [all_integration_points[ix] for ix in data_indices]
207
- phi[iout] = phio
208
-
209
-
210
- return phi, integration_points
211
-
212
-
213
- def modeshapes_from_set_xydata(field_output, components, output_position, instance_name, set_name, region_type):
214
- """
215
- Get mode shapes from "Frequency step" (eigenvalue analysis) in active Abaqus ODB from specified sets.
216
-
217
- Args: NOT FINISHED
218
- field_output:
219
- components:
220
- data_position:
221
- output_position:
222
- set_name:
223
- region_type:
224
- Returns:
225
- phi: mode shape transformation matrix (Numpy array)
226
-
227
- NTNU / Knut Andreas Kvaale, 2018
228
- """
229
-
230
- set_names = [(instance_name + '.' +set_name)]
231
-
232
- odb = get_db('odb')
233
- n_components = len(components)
234
- xy_data = [None]*n_components
235
-
236
- if region_type == 'element':
237
- data_position = INTEGRATION_POINT
238
- elif region_type == 'node':
239
- data_position = NODAL
240
-
241
- if output_position == 'element':
242
- output_position = ELEMENT_NODAL
243
-
244
- for ix, component in enumerate(components):
245
- refinement = [[COMPONENT, component]]
246
- variable = [[field_output, data_position, refinement]]
247
-
248
- if region_type == 'element':
249
- xy_data[ix] = session.xyDataListFromField(odb=odb, outputPosition=output_position, variable=variable, elementSets=set_names)
250
- else:
251
- xy_data[ix] = session.xyDataListFromField(odb=odb, outputPosition=output_position, variable=variable, nodeSets=set_names)
252
-
253
- n_elements = len(xy_data[0])
254
- n_modes = len(xy_data[0][0])
255
-
256
- phi = np.zeros([n_components*n_elements, n_modes])
257
- for compix, component in enumerate(xy_data):
258
- for elix, element in enumerate(component):
259
- for mode in range(0, n_modes):
260
- phi[elix*n_components + compix, mode] = element[mode][1]
261
-
262
-
263
- return phi, xy_data
264
-
265
- ## MODIFY ODB OR MDB
266
- def set_view_variable(var, component):
267
- """
268
- Set a new view variable and component in current odb session.
269
-
270
- Args:
271
- var: variable name
272
- component: component to display
273
-
274
- NTNU / Knut Andreas Kvaale, 2018
275
- """
276
- position = {NODAL}
277
- session.viewports['Viewport: 1'].odbDisplay.setPrimaryVariable(variableLabel=var, outputPosition=NODAL, refinement=(COMPONENT, component),)
278
-
279
-
280
- def get_db(db_type):
281
- """
282
- Return the current database (either a model or an odb object).
283
-
284
- If a model db is wanted and no model is active, the model in the mdb is selected regardless,
285
- as long as there is only one model open in the mdb. If no database fits the requirements, None is returned.
286
-
287
- Args:
288
- db_type: 'odb' or 'model'
289
- Returns:
290
- db: database
291
-
292
- NTNU / Knut Andreas Kvaale, 2018
293
- """
294
- if db_type is 'model' or db_type is 'mdb':
295
- if not session_is_odb():
296
- db = mdb.models[session.viewports['Viewport: 1'].displayedObject.modelName]
297
- elif len(mdb.models.keys()) is 1:
298
- db = mdb.models[mdb.models.keys()[0]]
299
- elif len(mdb.models.keys()) > 1:
300
- raise AttributeError('No model is not active, and more than one model is available in model database. Impossible to select correct.')
301
- else:
302
- db = None
303
- else:
304
- if session_is_odb():
305
- db = session.viewports[session.currentViewportName].displayedObject
306
- else:
307
- db = None
308
-
309
- return db
310
-
311
-
312
-
313
- ## MODIFY ODB
314
- def unlock_odb():
315
- """
316
- Unlock current ODB file.
317
-
318
- Returns:
319
- odb: database (odb) object
320
-
321
- NTNU / Knut Andreas Kvaale, 2018
322
- """
323
- odb = session.viewports[session.currentViewportName].displayedObject
324
-
325
- if odb.isReadOnly:
326
- load_path = odb.path
327
- odb.close()
328
- odb = odbAccess.openOdb(load_path, readOnly=False)
329
- session.viewports['Viewport: 1'].setValues(displayedObject=session.odbs[load_path])
330
-
331
- return odb
332
-
333
-
334
- def copy_and_unlock_odb():
335
- """
336
- Copy and unlock current ODB file.
337
-
338
- Returns:
339
- odb: database (odb) object
340
-
341
- NTNU / Knut Andreas Kvaale, 2018
342
- """
343
- odb = session.viewports[session.currentViewportName].displayedObject
344
- old_file_path = odb.path
345
- new_file_path = odb.path.split('.odb')[0]+'_org.odb'
346
-
347
- shutil.copyfile(old_file_path, new_file_path) #copy the old file
348
-
349
- odb.close()
350
- odb = odbAccess.openOdb(old_file_path, readOnly=False)
351
- session.viewports['Viewport: 1'].setValues(displayedObject=session.odbs[old_file_path])
352
-
353
- return odb
354
-
355
-
356
- def session_is_odb():
357
- """
358
- Check if current session is ODB.
359
-
360
- Returns:
361
- is_odb: boolean indicating if the session is odb or not
362
-
363
- NTNU / Knut Andreas Kvaale, 2018
364
- """
365
- is_odb =(('session' in locals() or 'session' in globals()) and
366
- session.viewports['Viewport: 1'].displayedObject is not None and
367
- hasattr(session.viewports['Viewport: 1'].displayedObject, 'jobData'))
368
-
369
- return is_odb
370
-
371
-
372
- def save_and_reopen_odb():
373
- """
374
- Save and reopen database (odb) as read-only.
375
-
376
- Returns:
377
- odb: odb object
378
-
379
- NTNU / Knut Andreas Kvaale, 2018
380
- """
381
- odb = get_db('odb')
382
- odb.save()
383
- load_path = odb.path
384
- odb.close()
385
-
386
- odb = odbAccess.openOdb(load_path, readOnly=True)
387
-
388
- return odb
389
-
390
-
391
- def add_response_step_from_modal(phi_response, field_outputs, modal_var, frequency_step, step_name, region_strings, region_type, instance_name, description):
392
- """
393
- Add an artificial step in Abaqus ODB for response data.
394
-
395
- Args:
396
- phi_response: phi of the requested response quantities (list with one matrix for each response quantities)
397
- field_outputs: names of field output variables
398
- modal_var: covariance matrix for the generalized (modal) DOFs
399
- frequency_step: name of the new artificial step_name
400
- step_name: node set name or region object that define what nodes / DOFs phi refers to
401
- regionobjs: Abaqus region objects in list
402
- instance_name: name of the instance
403
- description: frame description
404
-
405
- NTNU / Knut Andreas Kvaale, 2018
406
- """
407
-
408
- odb = copy_and_unlock_odb()
409
- regionobjs = str2region(instance_name, region_strings, region_type, 'odb')
410
- instance = odb.rootAssembly.instances[instance_name]
411
-
412
- step_data = odb.Step(name=step_name, description='Response step', domain=TIME, timePeriod=0)
413
- frame = step_data.Frame(incrementNumber=0, description='Response', frameValue=0)
414
-
415
- type_dict = {'SF': [TENSOR_3D_SURFACE, INTEGRATION_POINT, 'Section forces'], 'SM': [TENSOR_3D_SURFACE, INTEGRATION_POINT, 'Section moments'], 'U': [VECTOR, NODAL, 'Spatial displacement'], 'UR': [VECTOR, NODAL, 'Rotational displacement'] }
416
-
417
- for ix, field_output in enumerate(field_outputs):
418
- foobj_ref = odb.steps[frequency_step].frames[0].fieldOutputs[field_output]
419
- phi = phi_response[ix]
420
- region_type = type_dict[field_output][1]
421
- comps = len(odb.steps[frequency_step].frames[0].fieldOutputs[field_output].componentLabels)
422
-
423
- sigma = np.sqrt(np.sum((np.dot(phi, modal_var) * phi), axis=1)) # Calculate sigma (square root of covariance matrix) from modal coordinates
424
- sigma_comp = np.reshape(sigma, [-1, 3]).astype('float')
425
- data = [list(this) for this in sigma_comp]
426
-
427
- foobj = frame.FieldOutput(name=field_output, description=type_dict[field_output][2], type=type_dict[field_output][0], validInvariants=())
428
-
429
- N = len(odb.steps[frequency_step].frames[0].fieldOutputs[field_output].values)
430
- Ndofs, point_ranges, dof_ranges = count_region(regionobjs, field_output, odb.steps[frequency_step].frames[0])
431
-
432
- for regix,regionobj in enumerate(regionobjs):
433
- good_ix, good_entries = good_element_ix(foobj_ref, regionobj)
434
- point_range = range(point_ranges[regix],point_ranges[regix+1])
435
-
436
- foobj.addData(position=region_type, instance=instance, labels=good_entries, data=data)
437
-
438
- step_data.setDefaultField(foobj)
439
-
440
- odb = save_and_reopen_odb()
441
-
442
- return odb
443
-
444
-
445
- def add_std_to_frame(odb, frame, instance_name, modal_var, phi, regionobj, field_output, reference_step):
446
- '''
447
- Under development. Not verified.
448
- '''
449
- if odb.isReadOnly:
450
- raise TypeError('ODB is read only. Unable to add data.')
451
-
452
- type_dict = {'SF': [TENSOR_3D_SURFACE, INTEGRATION_POINT, 'Section forces'], 'SM': [TENSOR_3D_SURFACE, INTEGRATION_POINT, 'Section moments'], 'U': [VECTOR, NODAL, 'Spatial displacement'], 'UR': [VECTOR, NODAL, 'Rotational displacement'] }
453
- foobj_ref = odb.steps[reference_step].frames[0].fieldOutputs[field_output]
454
-
455
- region_type = type_dict[field_output][1]
456
- comps = len(odb.steps[reference_step].frames[0].fieldOutputs[field_output].componentLabels)
457
-
458
- sigma = np.sqrt(np.sum((np.dot(phi, modal_var) * phi), axis=1)) # Calculate sigma (square root of covariance matrix) from modal coordinates
459
- sigma_comp = np.reshape(sigma, [-1, comps]).astype('float')
460
- data = [list(this) for this in sigma_comp]
461
-
462
- # If already exists, don't create new, but assign to that.
463
- if field_output not in frame.fieldOutputs.keys():
464
- foobj = frame.FieldOutput(name=field_output, description=type_dict[field_output][2], type=type_dict[field_output][0], validInvariants=())
465
- else:
466
- foobj = frame.fieldOutputs[field_output]
467
-
468
- N = len(odb.steps[reference_step].frames[0].fieldOutputs[field_output].values)
469
- good_ix, good_entries = good_element_ix(foobj_ref, regionobj)
470
- instance = odb.rootAssembly.instances[instance_name]
471
-
472
- foobj.addData(position=region_type, instance=instance, labels=good_entries, data=data)
473
- step_data.setDefaultField(foobj)
474
-
475
-
476
-
477
- def add_complex_mode_step(phi, eigvals, instance_name, step_name, region):
478
- """
479
- Add an artificial step in Abaqus ODB for complex modes.
480
-
481
- Args:
482
- phi: complex eigenvector matrix
483
- eigvals: complex eigenvalues
484
- instance_name: name of the instance
485
- step_name: name of the new artificial step_name
486
- regionobj: Abaqus region object
487
-
488
- Knut Andreas Kvaale, 2018
489
- """
490
-
491
- odb = unlock_odb()
492
- complex_step = odb.Step(name=step_name, description='Complex modes', domain=MODAL)
493
- frame0 = complex_step.Frame(incrementNumber=0, description='Base state', frameValue=0)
494
- frame_data = frame0.FieldOutput(name='U', description='Spatial displacement', type=VECTOR, validInvariants=(MAGNITUDE,))
495
- instance = odb.rootAssembly.instances[instance_name]
496
-
497
- for m, lambdam in enumerate(eigvals):
498
- phim = np.reshape(phi[:, m], (-1, 3)).astype('float')
499
- xim = -np.real(lambdam)/abs(lambdam)
500
-
501
- freqm_ud_rad = abs(lambdam)
502
- freqm_d_rad = abs(np.imag(lambdam))
503
- freqm_ud_Hz = freqm_ud_rad/(2*np.pi)
504
-
505
- periodm_ud = 2*np.pi/abs(lambdam)
506
-
507
- description_m = 'Mode ' + str(m+1) + ': f = ' + str(freqm_ud_Hz) + 'Hz | om = ' + str(freqm_ud_rad) + 'rad/s | T = ' + str(periodm_ud) + 's | xi = ' + str(xim*100) + '%'
508
-
509
- frame_m = complex_step.Frame(incrementNumber=m+1, description=description_m, frameValue=freqm_ud_Hz)
510
- frame_data = frame_m.FieldOutput(name='U', description='Spatial displacement', type=VECTOR, validInvariants=(MAGNITUDE,))
511
- nodelabels = np.array([node.label for node in regionobj.nodes[0]]).astype('int')
512
-
513
- frame_data.addData(position=NODAL, instance=instance, labels=nodelabels, data=np.real(phim), conjugateData=np.imag(phim))
514
-
515
- odb.save()
516
- load_path = odb.path
517
- odb.close()
518
- odb = odbAccess.openOdb(load_path, readOnly=True)
519
-
520
- ## MODIFY MDB
521
- def mass_and_stiffness_input(stiffness,mass,pontoon_set_names,pont_nodes,trans_mats,filename):
522
- pontoons = len(pontoon_set_names)
523
- if len(pont_nodes) != pontoons or len(trans_mats)!=pontoons:
524
- raise ValueError('Mismatch between dimensions for input variables: pontoon_set_names, pont_nodes and trans_mats')
525
- f = open(filename, 'w')
526
-
527
- for pontoon in range(0,pontoons):
528
- f.write('********************************PONTOON NUMBER {0} ************************************* \n'.format(str(pontoon+1)))
529
- f.write('*USER ELEMENT, LINEAR, NODES=1, UNSYM, TYPE=U{0}00 \n'.format(str(pontoon+1))) # Defines a linear user element
530
- f.write('1, 2, 3, 4, 5, 6 \n') # The element has one node with 6 DOFS
531
-
532
- T = trans_mats[pontoon]
533
-
534
- K = np.dot(np.dot(T.transpose(), stiffness),T)
535
- M = np.dot(np.dot(T.transpose(), mass),T)
536
-
537
- f.write('*MATRIX, TYPE=MASS \n') # Defines the mass matrix in GLOBAL coordinate system
538
- for n in range(0,6):
539
- string1 = ','.join(map(str, M[n, 0:4]))
540
- string2 = ','.join(map(str, M[n, 4:6]))
541
- f.write(string1 + '\n' + string2 +'\n')
542
-
543
- f.write('*MATRIX, TYPE=STIFFNESS \n')
544
- for n in range(0,6):
545
- string1 = ','.join(map(str, K[n, 0:4]))
546
- string2 = ','.join(map(str, K[n, 4:6]))
547
- f.write(string1 + '\n' + string2 +'\n')
548
-
549
- f.write('*ELEMENT, TYPE=U{0}00, ELSET={1} \n'.format(str(pontoon+1),pontoon_set_names[pontoon])) #Introduce one user element into the FE model
550
- f.write('800{0}, {1} \n'.format(str(pontoon+1),pont_nodes[pontoon])) #Numbering elements as 8001,8002,...,8007, followed by first node number forming the element
551
- f.write('*UEL PROPERTY, ELSET={0} \n'.format(pontoon_set_names[pontoon]))
552
-
553
-
554
- def update_input(freq,wadam_file,input_file,pontoon_set_names,pont_nodes,trans_mats):
555
- from .io import import_wadam_mat
556
- static_mass, stiffness, added_mass, damping, frequency = import_wadam_mat(wadam_file)
557
- mass = freq_sysmat(added_mass,frequency,freq)+static_mass
558
- mass_and_stiffness_input(stiffness,mass,pontoon_set_names,pont_nodes,trans_mats,input_file)
559
- print('Input file '+ input_file + ' is modified to correspond to added mass at f = ' + str(freq) + ' Hz.')
560
-
561
-
562
- def imperfection_input(node_labels, displacement_vector, input_file=None, rotations=False):
563
-
564
- d = np.array(displacement_vector)
565
-
566
- if rotations is True:
567
- n_nodes = len(d)/6
568
- d_trans = np.zeros([n_nodes*3])
569
- for node in range(0, n_nodes):
570
- d_trans[node*3:node*3+3] = d[node*6:node*6+3]
571
-
572
- d = d_trans
573
- else:
574
- n_nodes = len(d)/3
575
-
576
- mat = np.hstack([np.array(node_labels)[:, np.newaxis], d.reshape(-1, 3)])
577
-
578
- if input_file != None:
579
- open(input_file, 'w').close()
580
-
581
- with open(input_file, 'a') as f:
582
- f.write('*IMPERFECTION \n')
583
- np.savetxt(f, mat, delimiter=',', fmt='%i,%.8e,%.8e,%.8e')
584
-
585
- return mat
586
-
587
-
588
- def add_input_file(model, input_file_path, pos, target_string=None, relative_pos=0):
589
-
590
- if target_string != None:
591
- pos = model.keywordBlock.sieBlocks.index(target_string)
592
-
593
- model.keywordBlock.insert(pos+relative_pos, '*INCLUDE, INPUT={0}'.format(input_file_path))
594
-
595
-
596
- def add_springs(assem, Kh, region, name):
597
-
598
- ON = symbolicConstants.AbaqusBoolean(1)
599
-
600
- Kpos = (Kh+abs(Kh))/2
601
- Krem = (Kh-abs(Kh))/2
602
-
603
- for dof in range(2, 5):
604
- if Kpos[dof, dof] != 0:
605
- assem.engineeringFeatures.SpringDashpotToGround(name=name+'_K%i%i' % (dof+1, dof+1), region=region, orientation=None, dof=dof+1, springBehavior=ON, springStiffness=Kpos[dof, dof])
606
-
607
- return Krem
608
-
609
-
610
- def add_inertia(assem, M0, region, name, specify_rot=False):
611
- if specify_rot is True:
612
- assem.engineeringFeatures.PointMassInertia(alpha=0.0, composite=0.0, i11=M0[3, 3], i12=M0[3, 4], i13=M0[3, 5], i22=M0[4, 4], i23=M0[4, 5],
613
- i33=M0[5, 5], mass1=M0[0, 0], mass2=M0[1, 1], mass3=M0[2, 2], name=name+'_M0', region=region)
614
- comps = range(0, 6)
615
- elif specify_rot is False:
616
- assem.engineeringFeatures.PointMassInertia(alpha=0.0, composite=0.0, mass1=M0[0, 0], mass2=M0[1, 1], mass3=M0[2, 2], name=name+'_M0', region=region)
617
- comps = range(0, 3)
618
-
619
- Mrem = deepcopy(M0)
620
-
621
- for comp in comps:
622
- Mrem[comp, comp] = 0
623
-
624
- return Mrem
625
-
626
-
627
- #%% USEFUL FUNCTIONS FOR DEALING WITH REGIONS IN DATABASE
628
- def count_region(regionobjs, field_output, frame):
629
- """
630
- Count the number of DOFs and points in the specified region objects for given field output and frame object.
631
-
632
- Args:
633
- regionobjs: list of region objects to query
634
- field_output: string specifying field output
635
- frame: frame object (from where fieldOutputs field is accessible)
636
- Returns:
637
- Ndofs: number of DOFs for each region (list)
638
- point_ranges: point/node ranges for each region (list of lists)
639
- dof_ranges: dof ranges for each region (list of lists)
640
-
641
- NTNU / Knut Andreas Kvaale, 2018
642
- """
643
- odb = get_db('odb')
644
-
645
- Npoints = [len(frame.fieldOutputs[field_output].getSubset(region=regionobj).values) for regionobj in regionobjs]
646
- Ndofs = np.dot(Npoints, len(frame.fieldOutputs[field_output].componentLabels))
647
-
648
- dof_ranges = np.cumsum(np.append([0], Ndofs))
649
- point_ranges = np.cumsum(np.append([0], Npoints))
650
-
651
- return Ndofs, point_ranges, dof_ranges
652
-
653
-
654
- def good_element_ix(foobj, regionobj):
655
- """
656
- Get the indices of the good (??) elements.
657
-
658
- Args:
659
- foobj: field object
660
- regionobj: region object
661
- Returns:
662
- good_ix: ?
663
- good_entries: ?
664
-
665
- NTNU / Knut Andreas Kvaale, 2018
666
- """
667
- foobj_values = foobj.getSubset(region=regionobj).values
668
- region_type = obtain_region_types([regionobj])[0]
669
-
670
- if region_type is 'elements':
671
- rootobj = regionobj.elements
672
- label_string = 'elementLabel'
673
- elif region_type is 'nodes':
674
- rootobj = regionobj.nodes
675
- label_string = 'nodeLabel'
676
-
677
- if type(rootobj) is tuple:
678
- rootobj = rootobj[0]
679
-
680
- good_entries = [getattr(val, label_string) for val in foobj_values]
681
- all_entries = [obj.label for obj in rootobj]
682
-
683
- good_ix = [all_entries.index(this_entry) for this_entry in good_entries]
684
-
685
- return good_ix, good_entries
686
-
687
-
688
- def obtain_region_types(regionobjs):
689
- """
690
- Get the region types of list of region objects.
691
-
692
- Args:
693
- regionobjs: list of region objects
694
- Returns:
695
- region_type: list of region types
696
-
697
- NTNU / Knut Andreas Kvaale, 2018
698
- """
699
- elementsets = [regionobj.nodes is None for regionobj in regionobjs] # true if regionobjects are element sets
700
- settypedict = {False: 'nodes', True: 'elements'}
701
- region_type = [settypedict[elementset] for elementset in elementsets]
702
-
703
- return region_type
704
-
705
-
706
- def str2region(instance_name, setnames, region_type, db_type, *args):
707
- """
708
- Construct a region object from a string defining the set name or a region object.
709
-
710
- Args:
711
- instance_name: string defining the set name (either node or element set) or a region object
712
- setnames: name of set asked for
713
- region_type: type of set ('elements' or 'nodes')
714
- db_type: 'odb' or 'model'
715
- Optional args:
716
- db: database object, either mdb.model[...] or session.openOdb(...) - will get from viewport 1 if not given
717
- Returns:
718
- regionobjs: region objects
719
-
720
- AAJ / Knut Andreas Kvaale, 2017
721
- Further developed NTNU / Knut Andreas Kvaale, 2018
722
- """
723
-
724
- is_assembly = instance_name is None
725
-
726
- set_type = settype(region_type, db_type)
727
- standard_sets = {'nodes': [' ALL NODES'], 'elements': [' ALL ELEMENTS']}
728
-
729
- if setnames is None:
730
- setnames = standard_sets[region_type]
731
-
732
- if len(args)==1: # a db has been input
733
- db = args[0]
734
- isodb = hasattr(db,'jobData') #check if the input db is reffering to result/odb or model
735
-
736
- else:
737
- db = get_db(db_type)
738
-
739
- if db is None:
740
- raise TypeError('The database is empty. Please input a database object, or input parameters that matches one. Remember that odbs have to be active to get the db automatically!')
741
-
742
- if is_assembly: # Instance name is given
743
- regroot = db.rootAssembly
744
- else:
745
- regroot = db.rootAssembly.instances[instance_name]
746
-
747
- regionobjs = [None] * np.size(setnames)
748
-
749
- for ix,thisname in enumerate(setnames):
750
- regionobjs[ix] = getattr(regroot, set_type)[thisname]
751
-
752
- return regionobjs
753
-
754
-
755
- def region2nodes(regionobj, sortfun=None):
756
- """
757
- Give node labels (indices) of nodes in specified node set(s).
758
-
759
- Args:
760
- regionobj: region object to query for node labels
761
-
762
- Optional args:
763
- sortfun: function with three inputs (1: x, 2: y, 3:z) to sort nodes by
764
- examples: sortfun = lambda x, y, z: -np.arctan2(y,x)
765
- sortfun = lambda x, y, z: x
766
-
767
- Returns:
768
- node_labels: list with nodelabels
769
-
770
- NTNU / Knut Andreas Kvaale, 2018
771
- """
772
-
773
- set_name = regionobj.__repr__().split("ets[")[1].split("'")[1]
774
-
775
- if len(np.shape(regionobj.nodes))>1:
776
- nodes = regionobj.nodes[0]
777
- else:
778
- nodes = regionobj.nodes
779
-
780
- node_labels = np.array([node.label for node in nodes])
781
- node_coordinates = np.array([node.coordinates for node in nodes])
782
-
783
- if sortfun != None:
784
- vals = sortfun(x=node_coordinates[:,0], y=node_coordinates[:,1], z=node_coordinates[:,2])
785
- sort_ix = np.argsort(vals)
786
- node_labels = node_labels[:, sort_ix]
787
- node_coordinates = node_coordinates[sort_ix, :]
788
-
789
- return node_labels, node_coordinates
790
-
791
- def region2elnodes(regionobj, avoid_central_nodes=True, db_type='odb'):
792
- """
793
- Give node labels (indices) for each node in specified element set.
794
-
795
- Args:
796
- regionobj: region object to query for labels
797
-
798
- Returns:
799
- element_labels: the labels (indices) of the elements in list
800
- element_node_indices: the labels (indices) of the ndoes in each element; list of lists
801
- node_labels: all the nodes labels (indices) in a flattened list
802
- node_coordinates: node coordinates for each element (list of lists)
803
-
804
- NTNU / Knut Andreas Kvaale, 2018
805
- """
806
-
807
- db = get_db(db_type)
808
- objstr = regionobj.__repr__()
809
- if 'instances' in objstr:
810
- instance_name = objstr.split(".instances['")[1].split("'].")[0]
811
- else:
812
- instance_name = None
813
-
814
- if instance_name is None:
815
- instance = db.rootAssembly
816
- else:
817
- instance = db.rootAssembly.instances[instance_name]
818
-
819
- # Get the elements object root
820
- if len(np.shape(regionobj.elements))>1:
821
- elements = regionobj.elements[0]
822
- else:
823
- elements = regionobj.elements
824
-
825
- # Get all element labels and corresponding connectivity (node labels)
826
- element_labels = np.array([element.label for element in elements])
827
- node_labels = [el.connectivity for el in elements]
828
-
829
- if avoid_central_nodes:
830
- node_labels = np.unique([item for sublist in node_labels for item in sublist[:1]+sublist[-1:]])
831
- else:
832
- node_labels = [item for sublist in node_labels for item in sublist]
833
-
834
- element_matrix = None
835
-
836
- return element_labels, node_labels, element_matrix
837
-
838
-
839
- def get_element_matrix(element_labels=None): #if None is specified, full model is exported
840
- pass
841
-
842
- def get_node_matrix(node_labels=None): #if None is specified, full model is exported
843
- pass
844
-
845
- def region2elnodes_legacy(regionobjs, avoid_central_nodes=True):
846
- """
847
- Give node labels (indices) for each node in specified element set.
848
-
849
- Args:
850
- regionobjs: region objects to query for node labels
851
-
852
- Returns:
853
- element_labels: the labels (indices) of the elements in list
854
- element_node_indices: the labels (indices) of the ndoes in each element; list of lists
855
- node_labels: all the nodes labels (indices) in a flattened list
856
- node_coordinates: node coordinates for each element (list of lists)
857
-
858
- NTNU / Knut Andreas Kvaale, 2018
859
- """
860
-
861
- objstr = regionobjs.__repr__()
862
- instance_name = objstr.split(".instances['")[1].split("'].")[0]
863
-
864
- if '.odb' in objstr:
865
- db = get_db('odb')
866
- dbtype = 'odb'
867
- else:
868
- db = get_db('mdb')
869
- dbtype = 'mdb'
870
-
871
- # Get the elements object root
872
- if len(np.shape(regionobjs.elements))>1:
873
- elements = regionobjs.elements[0]
874
- else:
875
- elements = regionobjs.elements
876
-
877
- # Get all element labels and corresponding connectivity (node labels)
878
- element_labels = np.array([element.label for element in elements])
879
-
880
- # Instance object
881
- instance = db.rootAssembly.instances[instance_name]
882
-
883
- # Full arrays labels and coordinates
884
- all_node_labels = np.array([node.label for node in instance.nodes]).flatten([-1])
885
- all_node_coords = np.array([node.coordinates for node in instance.nodes])
886
-
887
- # Nodes belonging to all the elements
888
- if dbtype is 'odb':
889
- element_node_labels = [element.connectivity for element in elements]
890
- else:
891
- element_node_labels = [[all_node_labels[ix] for ix in element.connectivity] for element in elements]
892
-
893
- if avoid_central_nodes:
894
- element_node_labels = [[node_lb[0], node_lb[-1]] for node_lb in element_node_labels]
895
-
896
- node_labels = np.unique(np.array(element_node_labels).flatten())
897
-
898
- nodeixs = np.array([np.where(all_node_labels==node)[0] for node in node_labels]).flatten()
899
- node_coordinates = all_node_coords[nodeixs, :]
900
- element_node_indices = np.array([np.array([np.where(node_labels==node_label) for node_label in node_labels_for_element]).flatten() for node_labels_for_element in element_node_labels])
901
-
902
- return element_labels, element_node_indices, node_labels, node_coordinates
903
-
904
-
905
- #%% RETRIEVE THINGS FROM DATABASE
906
- def element_orientations(element_labels, instance_name):
907
- """
908
- Provide transformation matrices describing the three unit vectors of the local CSYS of all elements in element_labels.
909
-
910
- Args:
911
- element_labels: element labels to query
912
- instance_name: name of instance to find beam orientations
913
-
914
- Returns:
915
- element_orientations: array of numpy 2d-arrays with transformation matrices of all elements in element_labels
916
-
917
- NTNU / Knut Andreas Kvaale, 2018
918
- """
919
- db_type = 'odb' # may consider mdb option later
920
- db = get_db(db_type)
921
-
922
- all_elements = db.rootAssembly.elementSets[' ALL ELEMENTS'].elements[0]
923
- all_nodes = db.rootAssembly.nodeSets[' ALL NODES'].nodes[0]
924
- all_element_labels = [value.label for value in all_elements]
925
- all_node_labels = [value.label for value in all_nodes]
926
- element_orientations = [None]*len(element_labels)
927
-
928
- beam_orientations = db.rootAssembly.instances[instance_name].beamOrientations
929
-
930
- for beam_orientation in beam_orientations:
931
- bo_elements = [value.label for value in beam_orientation.region.elements]
932
- for this_element_label in bo_elements:
933
- if this_element_label in element_labels:
934
- n1_temp = np.array(beam_orientation.vector)
935
- node_labels = all_elements[all_element_labels.index(this_element_label)].connectivity
936
-
937
- node_start_coor = all_nodes[all_node_labels.index(node_labels[0])].coordinates
938
- node_end_coor = all_nodes[all_node_labels.index(node_labels[-1])].coordinates
939
- t = (node_end_coor-node_start_coor)
940
- t = t/np.linalg.norm(t)
941
-
942
- n2 = np.cross(t, n1_temp)
943
- n2 = n2/np.linalg.norm(n2)
944
-
945
- n1 = np.cross(n2, t) #does this actually work?
946
-
947
- element_orientations[np.where(element_labels == this_element_label)[0]] = np.array([t,n1,n2])
948
-
949
- return element_orientations
950
-
951
-
952
- def freq_sysmat(mat,freqs,freq):
953
- """
954
- Interpolate frequency dependent matrix, for given frequency value. !! Deprecated - use numpy functions directly instead !!
955
-
956
- Args:
957
- mat: 3D matrix (Numpy array)
958
- freqs: frequency axis (Numpy array)
959
- freq: selected frequency value (scalar)
960
- Returns:
961
- mat_sel: 2D matrix corresponding to queried frequency value (Numpy array)
962
-
963
- NTNU / AAJ / Knut Andreas Kvaale, 2018
964
- """
965
- from .general import interp1z
966
-
967
- if freq == []:
968
- mat_sel = 0
969
- else:
970
- mat_sel = interp1z(freqs[:,0,0],mat,freq)
971
- return mat_sel
972
-
973
-
974
- def wind_set_data(set_strings, frequency_step, instance, db_type, field_outputs, mode_type='nodes', use_node_region_acronym=False):
975
- # use_node_region_acronym: if True, a node set with identical name as the element set given in set_strings is picked and the nodes assumed to correspond to the element. If not the case, the element set is used to establish the nodes (and thus phi)
976
- wind_element_regions = str2region(instance, set_strings, 'elements', db_type) # index 0 is girder, index 1 is columns
977
-
978
- if use_node_region_acronym:
979
- wind_node_regions = str2region(instance, set_strings, 'nodes', db_type)
980
-
981
- element_labels = [None]*len(set_strings)
982
- element_node_indices = [None]*len(set_strings)
983
- node_labels = [None]*len(set_strings)
984
- node_coordinates = [None]*len(set_strings)
985
- phi_ae = [None]*len(set_strings)
986
-
987
- for set_ix, set_string in enumerate(set_strings):
988
- element_labels[set_ix], element_node_indices[set_ix], nl, nc = region2elnodes_legacy(wind_element_regions[set_ix])
989
- if use_node_region_acronym:
990
- nl, nc = region2nodes(wind_node_regions[set_ix])
991
-
992
- node_labels[set_ix] = nl
993
- node_coordinates[set_ix] = nc
994
-
995
- # Establish modal transformation matrix, phi
996
- if mode_type=='nodes':
997
- for set_ix, set_string in enumerate(set_strings):
998
- phi_ae_temp = modeshapes_from_nodelist(node_labels[set_ix], frequency_step, field_outputs)
999
- phi_ae[set_ix] = merge_tr_phi(phi_ae_temp[0][0], phi_ae_temp[0][1])
1000
- elif mode_type=='elements':
1001
- for set_ix, set_string in enumerate(set_strings):
1002
- phi_ae_temp, integration_points = modeshapes_from_elementlist(element_labels[set_ix], frequency_step, field_outputs)
1003
- phi_ae[set_ix] = merge_tr_phi(phi_ae_temp[0], phi_ae_temp[1])
1004
-
1005
- return element_labels, element_node_indices, node_labels, node_coordinates, phi_ae
1006
-
1007
-
1008
-
1009
- def settype(region_type, db_type):
1010
- """
1011
- Define the string used to get set based on region type and database type.
1012
-
1013
- Args:
1014
- region_type: 'element' or 'node'
1015
- db_type: 'odb' or 'mdb'
1016
- Returns:
1017
- set_string: string used to obtain set data from database object (odb or mdb)
1018
-
1019
- NTNU / Knut Andreas Kvaale, 2018
1020
- """
1021
- if db_type is 'odb':
1022
- if 'element' in region_type.lower():
1023
- set_string = 'elementSets'
1024
- elif 'node' in region_type.lower():
1025
- set_string = 'nodeSets'
1026
- else:
1027
- raise TypeError('Wrong input!')
1028
- elif db_type == 'mdb' or db_type == 'model':
1029
- set_string = 'sets'
1030
-
1031
- return set_string
1032
-
1033
- #%% EXPORT THINGS
1034
- def save_nodes_and_elements(folder, element_labels, element_node_indices, node_labels, node_coordinates, element_orientations=None, set_strings=None):
1035
- for ix, element_labels_i in enumerate(element_labels):
1036
- element_info = np.column_stack([element_labels[ix], element_node_indices[ix]])
1037
- node_info = np.column_stack([node_labels[ix], node_coordinates[ix]])
1038
- np.savetxt(os.path.join(folder, 'node_info_%i.dat' % (ix)), node_info)
1039
- np.savetxt(os.path.join(folder, 'element_info_%i.dat' % (ix)), element_info)
1040
-
1041
- if element_orientations:
1042
- np.savetxt(os.path.join(folder, 'element_orientations_%i.dat' % (ix)), element_orientations)
1043
-
1044
- if set_strings:
1045
- np.savetxt(os.path.join(folder, 'node_and_element_sets.txt'), set_strings, fmt='%s', delimiter=',')
1046
-
1047
-
1048
- def save_pontoon_info(folder, node_labels, node_coordinates, pontoon_labels=None, pontoon_angles=None):
1049
- if pontoon_labels==None: # standard if no pontoon_labels are provided (integers!)
1050
- pontoon_labels = np.linspace(1, len(node_labels), len(node_labels)).astype(int)
1051
-
1052
- if pontoon_angles==None:
1053
- pontoon_angles = np.zeros(len(node_labels)) #if no angles are given, output zero for all pontoon angles
1054
-
1055
- pontooninfo = np.column_stack([pontoon_labels, node_coordinates, node_labels, pontoon_angles])
1056
- np.savetxt(os.path.join(folder, 'pontoon_info.dat'), pontooninfo)
1057
-
1058
-
1059
- def save_all_modal(folder, phi, suffix='', f=None, m=None, set_strings=None):
1060
-
1061
- if isinstance(phi, list):
1062
- for ix, phi_i in enumerate(phi):
1063
- np.savetxt(os.path.join(folder, 'phi_%s_%i.dat' % (suffix, ix)), phi_i)
1064
-
1065
- if set_strings:
1066
- np.savetxt(os.path.join(folder, 'phi_%s_sets.txt' % (suffix)), set_strings, fmt='%s', delimiter=',')
1067
-
1068
- elif isinstance(phi, np.ndarray):
1069
- np.savetxt(os.path.join(folder, 'phi_%s_%i.dat' % (suffix, 0)), phi)
1070
-
1071
- if f is not None:
1072
- np.savetxt(os.path.join(folder, 'f.dat'), f)
1073
- if m is not None:
1074
- np.savetxt(os.path.join(folder, 'm.dat'), m)
1075
-
1076
-
1077
- #%% ONLY DEBUGGED IN BRIGADE
1078
- def mode2df(model, node_labels, phi, name, instance_name):
1079
- nodes = tuple(np.repeat(node_labels,6).tolist())
1080
- dofs = np.tile(np.arange(1,6+1), len(node_labels))
1081
-
1082
- dofs_and_mags = np.empty([np.shape(dofs)[0],2])
1083
- dofs_and_mags[:, 0::2] = dofs[:, np.newaxis]
1084
- dofs_and_mags[:, 1::2] = phi[:, np.newaxis]
1085
-
1086
- data = ((instance_name, 2, nodes, tuple(dofs_and_mags.flatten().tolist())),)
1087
- df = model.DiscreteField(data=data, dataWidth=2, defaultValues=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), description='Mode displacement', fieldType=PRESCRIBEDCONDITION_DOF, location=NODES, name=name)
1088
-
1089
- return df
1090
-
1091
-
1092
- def apply_nodal_load(model, node_labels, step_name, loads, instance_name, prefix=''):
1093
- instance = model.rootAssembly.instances[instance_name]
1094
- all_node_labels = [node.label for node in instance.nodes]
1095
- ndof = 6 # assumes 6 DOFs for all nodes - be aware!
1096
- for node_ix, node_label in enumerate(node_labels):
1097
- if all_node_labels.count(node_label) != None: # if in node labels
1098
- global_node_ix = all_node_labels.index(node_label)
1099
- node_set = model.rootAssembly.Set(name='node_%i' % (node_label), nodes=instance.nodes[global_node_ix:global_node_ix+1])
1100
- nodeloads = loads[node_ix*6:node_ix*6+6]
1101
-
1102
- if not np.all(nodeloads[0:3]==0):
1103
- model.ConcentratedForce(cf1=nodeloads[0], cf2=nodeloads[1], cf3=nodeloads[2], createStepName=step_name, distributionType=UNIFORM, field='', localCsys=None, name='%sforces_node_%i' % (prefix, node_label), region=node_set)
1104
-
1105
- if not np.all(nodeloads[3:6]==0):
1106
- model.Moment(cm1=nodeloads[3], cm2=nodeloads[4], cm3=nodeloads[5], createStepName=step_name, distributionType=UNIFORM, field='', localCsys=None, name='%smoments_node_%i' % (prefix, node_label), region=node_set)
1107
-
1108
- else:
1109
- raise ValueError('Node %i does not exist in selected instance.' % (node_label))
1110
-
1111
-
1112
- def assign_modal_constraint_equation(model, instance_name, name, node_labels, displacement):
1113
- ndof = 6 # assumes 6 DOFs for all nodes - be aware!
1114
- instance = model.rootAssembly.instances[instance_name]
1115
- all_node_labels = [node.label for node in instance.nodes]
1116
- terms = []
1117
- for node_ix, node_label in enumerate(node_labels):
1118
- if all_node_labels.count(node_label) != None: # if in node labels
1119
- global_node_ix = all_node_labels.index(node_label)
1120
- node_set_name = 'node_%i' % (node_label)
1121
- node_set = model.rootAssembly.Set(name=node_set_name, nodes=instance.nodes[global_node_ix:global_node_ix+1])
1122
- displacement_of_node = displacement[node_ix*ndof:node_ix*ndof+ndof]
1123
- non_zero = np.where(displacement_of_node !=0 )[0]
1124
- terms.append([(displacement_of_node[ldof], node_set_name, ldof+1) for ldof in non_zero])
1125
-
1126
- terms = tuple([term for sublist in terms for term in sublist])
1127
- model.Equation(name=name, terms=terms)
1128
-