NGsolveCMStutorial.py
You can view and download this file on Github: NGsolveCMStutorial.py
1#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2# This is an EXUDYN example
3#
4# Details: Test for Hurty-Craig-Bampton modes using a simple flexible pendulum meshed with Netgen
5#
6# Author: Johannes Gerstmayr
7# Date: 2021-04-20
8# Update: 2024-05-14: add node weighting and add some fixes
9#
10# Copyright:This file is part of Exudyn. Exudyn is free software. You can redistribute it and/or modify it under the terms of the Exudyn license. See 'LICENSE.txt' for more details.
11#
12#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
13
14
15import exudyn as exu
16from exudyn.utilities import * #includes itemInterface and rigidBodyUtilities
17import exudyn.graphics as graphics #only import if it does not conflict
18from exudyn.FEM import *
19
20SC = exu.SystemContainer()
21mbs = SC.AddSystem()
22
23import numpy as np
24import time
25
26
27useGraphics = True
28fileName = 'testData/netgenHinge' #for load/save of FEM data
29
30#%%+++++++++++++++++++++++++++++++++++++++++++++++++++++
31#netgen/meshing part:
32fem = FEMinterface()
33
34#geometrical parameters:
35L = 0.4 #Length of plate (X)
36w = 0.04 #width of plate (Y)
37h = 0.02 #height of plate (Z)
38d = 0.03 #diameter of bolt
39D = d*2 #diameter of bushing
40b = 0.05 #length of bolt
41nModes = 8
42meshH = 0.01 #0.01 is default, 0.002 gives 100000 nodes and is fairly converged;
43#meshH = 0.0014 #203443 nodes, takes 1540 seconds for eigenmode computation (free-free) and 753 seconds for postprocessing on i9
44
45#steel:
46rho = 7850
47Emodulus=2.1e11
48nu=0.3
49
50#test high flexibility
51Emodulus=2e8
52# nModes = 32
53
54
55#helper function for cylinder with netgen
56def CSGcylinder(p0,p1,r):
57 v = VSub(p1,p0)
58 v = Normalize(v)
59 cyl = Cylinder(Pnt(p0[0],p0[1],p0[2]), Pnt(p1[0],p1[1],p1[2]),
60 r) * Plane(Pnt(p0[0],p0[1],p0[2]), Vec(-v[0],-v[1],-v[2])) * Plane(Pnt(p1[0],p1[1],p1[2]), Vec(v[0],v[1],v[2]))
61 return cyl
62
63meshCreated = False
64
65#%%+++++++++++++++++++++++++++++++++++++++++++++++++++++
66if True: #needs netgen/ngsolve to be installed to compute mesh, see e.g.: https://github.com/NGSolve/ngsolve/releases
67 import ngsolve as ngs
68 import netgen
69 from netgen.meshing import *
70
71 from netgen.geom2d import unit_square
72 #import netgen.libngpy as libng
73 from netgen.csg import *
74
75 geo = CSGeometry()
76
77 #plate
78 block = OrthoBrick(Pnt(0, 0, -0.5*h),Pnt(L, w, 0.5*h))
79
80 #bolt
81 bolt0 = CSGcylinder(p0=[0,w,0], p1=[0,0,0], r=1.6*h)
82 bolt = CSGcylinder(p0=[0,0.5*w,0], p1=[0,-b,0], r=0.5*d)
83
84 #bushing
85 bushing = (CSGcylinder(p0=[L,w,0], p1=[L,-b,0], r=0.5*D) -
86 CSGcylinder(p0=[L,0,0], p1=[L,-b*1.1,0], r=0.5*d))
87
88 geo.Add(block+bolt0+bolt+bushing)
89
90 curvaturesafety = 2
91 if meshH==0.04:
92 curvaturesafety = 1.2#this case is for creating very small files ...
93
94 mesh = ngs.Mesh( geo.GenerateMesh(maxh=meshH, curvaturesafety=curvaturesafety))
95 mesh.Curve(1)
96
97 if False: #set this to true, if you want to visualize the mesh inside netgen/ngsolve
98 import netgen.gui #this starts netgen gui; Press button "Visual" and activate "Auto-redraw after (sec)"; Then select "Mesh"
99
100 #%%+++++++++++++++++++++++++++++++++++++++++++++++++++++
101 #Use fem to import FEM model and create FFRFreducedOrder object
102 [bfM, bfK, fes] = fem.ImportMeshFromNGsolve(mesh, density=rho, youngsModulus=Emodulus, poissonsRatio=nu)
103 meshCreated = True
104 if (meshH==0.04):
105 print('save file')
106 fem.SaveToFile(fileName)
107
108
109#%%+++++++++++++++++++++++++++++++++++++++++++++++++++++
110#compute Hurty-Craig-Bampton modes
111if True: #now import mesh as mechanical model to EXUDYN
112 if not meshCreated: fem.LoadFromFile(fileName)
113
114 boltP1=[0,0,0]
115 boltP2=[0,-b,0]
116 nodesOnBolt = fem.GetNodesOnCylinder(boltP1, boltP2, radius=0.5*d)
117 #print("boundary nodes bolt=", nodesOnBolt)
118 nodesOnBoltWeights = fem.GetNodeWeightsFromSurfaceAreas(nodesOnBolt)
119
120 bushingP1=[L,0,0]
121 bushingP2=[L,-b,0]
122 nodesOnBushing = fem.GetNodesOnCylinder(bushingP1, bushingP2, radius=0.5*d)
123 #print("boundary nodes bushing=", nodesOnBushing)
124 nodesOnBushingWeights = fem.GetNodeWeightsFromSurfaceAreas(nodesOnBushing)
125
126 print("nNodes=",fem.NumberOfNodes())
127
128 strMode = ''
129 if True: #pure eigenmodes
130 print("compute eigen modes... ")
131 start_time = time.time()
132
133 if False: #faster but not so accurate
134 fem.ComputeEigenmodesNGsolve(bfM, bfK, nModes, excludeRigidBodyModes = 6)
135 else:
136 fem.ComputeEigenmodes(nModes, excludeRigidBodyModes = 6, useSparseSolver = True)
137 print("eigen modes computation needed %.3f seconds" % (time.time() - start_time))
138 print("eigen freq.=", fem.GetEigenFrequenciesHz())
139
140 else:
141 strMode = 'HCB'
142 #boundaryList = [nodesOnBolt, nodesOnBolt, nodesOnBushing] #for visualization, use first interface twice
143 boundaryList = [nodesOnBolt, nodesOnBushing]
144
145 print("compute HCB modes... ")
146 start_time = time.time()
147 fem.ComputeHurtyCraigBamptonModes(boundaryNodesList=boundaryList,
148 nEigenModes=nModes,
149 useSparseSolver=True,
150 computationMode = HCBstaticModeSelection.RBE2)
151
152 print("eigen freq.=", fem.GetEigenFrequenciesHz())
153 print("HCB modes needed %.3f seconds" % (time.time() - start_time))
154
155
156
157 #%%+++++++++++++++++++++++++++++++++++++++++++++++++++++
158 #compute stress modes for postprocessing (inaccurate for coarse meshes, just for visualization):
159 if True:
160 mat = KirchhoffMaterial(Emodulus, nu, rho)
161 varType = exu.OutputVariableType.StressLocal
162 #varType = exu.OutputVariableType.StrainLocal
163 print("ComputePostProcessingModes ... (may take a while)")
164 start_time = time.time()
165 #without NGsolve:
166 if True: #faster with ngsolve
167 fem.ComputePostProcessingModesNGsolve(fes, material=mat,
168 outputVariableType=varType)
169 else:
170 fem.ComputePostProcessingModes(material=mat,
171 outputVariableType=varType)
172 print(" ... needed %.3f seconds" % (time.time() - start_time))
173 SC.visualizationSettings.contour.reduceRange=True
174 SC.visualizationSettings.contour.outputVariable = varType
175 SC.visualizationSettings.contour.outputVariableComponent = 0 #x-component
176 else:
177 varType = exu.OutputVariableType.DisplacementLocal
178 SC.visualizationSettings.contour.outputVariable = exu.OutputVariableType.DisplacementLocal
179 SC.visualizationSettings.contour.outputVariableComponent = 0
180
181 #%%+++++++++++++++++++++++++++++++++++++++++++++++++++++
182 print("create CMS element ...")
183 cms = ObjectFFRFreducedOrderInterface(fem)
184
185 objFFRF = cms.AddObjectFFRFreducedOrder(mbs, positionRef=[0,0,0],
186 initialVelocity=[0,0,0],
187 initialAngularVelocity=[0,0,0],
188 color=[0.9,0.9,0.9,1.],
189 )
190
191 #%%+++++++++++++++++++++++++++++++++++++++++++++++++++++
192 #add markers and joints
193 nodeDrawSize = 0.0025 #for joint drawing
194
195
196
197 if True:
198 boltMidPoint = 0.5*(np.array(boltP1)+boltP2)
199
200 oGround = mbs.AddObject(ObjectGround(referencePosition= [0,0,0]))
201
202 altApproach = True
203 mBolt = mbs.AddMarker(MarkerSuperElementRigid(bodyNumber=objFFRF['oFFRFreducedOrder'],
204 meshNodeNumbers=np.array(nodesOnBolt), #these are the meshNodeNumbers
205 useAlternativeApproach=altApproach,
206 weightingFactors=nodesOnBoltWeights))
207 bushingMidPoint = 0.5*(np.array(bushingP1)+bushingP2)
208
209 #add marker for visualization of boundary nodes
210 mBushing = mbs.AddMarker(MarkerSuperElementRigid(bodyNumber=objFFRF['oFFRFreducedOrder'],
211 meshNodeNumbers=np.array(nodesOnBushing), #these are the meshNodeNumbers
212 useAlternativeApproach=altApproach,
213 weightingFactors=nodesOnBushingWeights))
214
215 lockedAxes=[1,1,1,1,1*0,1]
216 if True:
217
218 mGroundBolt = mbs.AddMarker(MarkerBodyRigid(bodyNumber=oGround,
219 localPosition=boltMidPoint,
220 visualization=VMarkerBodyRigid(show=True)))
221 mbs.AddObject(GenericJoint(markerNumbers=[mGroundBolt, mBolt],
222 constrainedAxes = lockedAxes,
223 visualization=VGenericJoint(show=False, axesRadius=0.1*b, axesLength=0.1*b)))
224
225 else:
226
227 mGroundBushing = mbs.AddMarker(MarkerBodyRigid(bodyNumber=oGround, localPosition=bushingMidPoint))
228 mbs.AddObject(GenericJoint(markerNumbers=[mGroundBushing, mBushing],
229 constrainedAxes = lockedAxes,
230 visualization=VGenericJoint(axesRadius=0.1*b, axesLength=0.1*b)))
231
232
233 #%%+++++++++++++++++++++++++++++++++++++++++++++++++++++
234 #animate modes
235 SC.visualizationSettings.markers.show = True
236 SC.visualizationSettings.markers.defaultSize=0.0075
237 SC.visualizationSettings.markers.drawSimplified = False
238
239 SC.visualizationSettings.loads.show = False
240 SC.visualizationSettings.loads.drawSimplified = False
241 SC.visualizationSettings.loads.defaultSize=0.1
242 SC.visualizationSettings.loads.defaultRadius = 0.002
243
244 SC.visualizationSettings.openGL.multiSampling=4
245 SC.visualizationSettings.openGL.lineWidth=2
246
247 if False: #activate to animate modes
248 from exudyn.interactive import AnimateModes
249 mbs.Assemble()
250 SC.visualizationSettings.nodes.show = False
251 SC.visualizationSettings.openGL.showFaceEdges = True
252 SC.visualizationSettings.openGL.multiSampling=4
253 SC.visualizationSettings.openGL.lineWidth=2
254 SC.visualizationSettings.window.renderWindowSize = [1600,1080]
255 SC.visualizationSettings.contour.showColorBar = False
256 SC.visualizationSettings.general.textSize = 16
257
258 #%%+++++++++++++++++++++++++++++++++++++++
259 #animate modes of ObjectFFRFreducedOrder (only needs generic node containing modal coordinates)
260 SC.visualizationSettings.general.autoFitScene = False #otherwise, model may be difficult to be moved
261
262 nodeNumber = objFFRF['nGenericODE2'] #this is the node with the generalized coordinates
263 AnimateModes(SC, mbs, nodeNumber, period=0.1, showTime=False, renderWindowText='Hurty-Craig-Bampton: 2 x 6 static modes and 8 eigenmodes\n',
264 runOnStart=True)
265 import sys
266 sys.exit()
267
268 #add gravity (not necessary if user functions used)
269 oFFRF = objFFRF['oFFRFreducedOrder']
270 mBody = mbs.AddMarker(MarkerBodyMass(bodyNumber=oFFRF))
271 mbs.AddLoad(LoadMassProportional(markerNumber=mBody, loadVector= [0,0,-9.81]))
272
273
274 #%%+++++++++++++++++++++++++++++++++++++++++++++++++++++
275 fileDir = 'solution/'
276 sensBolt = mbs.AddSensor(SensorMarker(markerNumber=mBolt,
277 fileName=fileDir+'hingePartBoltPos'+str(nModes)+strMode+'.txt',
278 outputVariableType = exu.OutputVariableType.Position))
279 # sensBushing= mbs.AddSensor(SensorMarker(markerNumber=mBushing,
280 # fileName=fileDir+'hingePartBushingPos'+str(nModes)+strMode+'.txt',
281 # outputVariableType = exu.OutputVariableType.Position))
282 sensBushingVel= mbs.AddSensor(SensorMarker(markerNumber=mBushing,
283 fileName=fileDir+'hingePartBushingVel'+str(nModes)+strMode+'.txt',
284 outputVariableType = exu.OutputVariableType.Velocity))
285 sensBushing= mbs.AddSensor(SensorMarker(markerNumber=mBushing,
286 fileName=fileDir+'hingePartBushing'+str(nModes)+strMode+'.txt',
287 outputVariableType = exu.OutputVariableType.Position))
288
289 mbs.Assemble()
290
291 simulationSettings = exu.SimulationSettings()
292
293 SC.visualizationSettings.nodes.defaultSize = nodeDrawSize
294 SC.visualizationSettings.nodes.drawNodesAsPoint = False
295 SC.visualizationSettings.connectors.defaultSize = 2*nodeDrawSize
296
297 SC.visualizationSettings.nodes.show = False
298 SC.visualizationSettings.nodes.showBasis = True #of rigid body node of reference frame
299 SC.visualizationSettings.nodes.basisSize = 0.12
300 SC.visualizationSettings.bodies.deformationScaleFactor = 1 #use this factor to scale the deformation of modes
301
302 SC.visualizationSettings.openGL.showFaceEdges = True
303 SC.visualizationSettings.openGL.showFaces = True
304
305 SC.visualizationSettings.sensors.show = True
306 SC.visualizationSettings.sensors.drawSimplified = False
307 SC.visualizationSettings.sensors.defaultSize = 0.01
308
309
310 simulationSettings.solutionSettings.solutionInformation = "CMStutorial "+str(nModes)+" "+strMode+"modes"
311
312 h=1e-3
313 tEnd = 2
314
315 simulationSettings.timeIntegration.numberOfSteps = int(tEnd/h)
316 simulationSettings.timeIntegration.endTime = tEnd
317 simulationSettings.solutionSettings.writeSolutionToFile = True
318 simulationSettings.timeIntegration.verboseMode = 1
319 #simulationSettings.timeIntegration.verboseModeFile = 3
320 simulationSettings.timeIntegration.newton.useModifiedNewton = True
321
322 simulationSettings.solutionSettings.sensorsWritePeriod = h
323
324 simulationSettings.timeIntegration.generalizedAlpha.spectralRadius = 0.8
325 #simulationSettings.displayStatistics = True
326 simulationSettings.displayComputationTime = True
327
328 #create animation:
329 # simulationSettings.solutionSettings.recordImagesInterval = 0.005
330 # SC.visualizationSettings.exportImages.saveImageFileName = "animation/frame"
331 SC.visualizationSettings.window.renderWindowSize=[1920,1080]
332 SC.visualizationSettings.openGL.multiSampling = 4
333
334 useGraphics=True
335 if True:
336 if useGraphics:
337 SC.visualizationSettings.general.autoFitScene=False
338
339 SC.renderer.Start()
340 if 'renderState' in exu.sys: SC.renderer.SetState(exu.sys['renderState']) #load last model view
341
342 SC.renderer.DoIdleTasks() #press space to continue
343
344 if True:
345 # mbs.SolveDynamic(solverType=exu.DynamicSolverType.TrapezoidalIndex2,
346 # simulationSettings=simulationSettings)
347 mbs.SolveDynamic(simulationSettings=simulationSettings)
348 else:
349 mbs.SolveStatic(simulationSettings=simulationSettings)
350
351 if varType == exu.OutputVariableType.StressLocal:
352 mises = CMSObjectComputeNorm(mbs, 0, exu.OutputVariableType.StressLocal, 'Mises')
353 print('max von-Mises stress=',mises)
354
355 if useGraphics:
356 SC.renderer.DoIdleTasks()
357 SC.renderer.Stop() #safely close rendering window!
358
359 if False:
360
361 mbs.PlotSensor(sensorNumbers=[sensBushingVel], components=[1])
362
363#%%
364if False:
365 import matplotlib.pyplot as plt
366 import matplotlib.ticker as ticker
367 import exudyn as exu
368 from exudyn.utilities import * #includes itemInterface and rigidBodyUtilities
369 import exudyn.graphics as graphics #only import if it does not conflict
370 CC = PlotLineCode
371 comp = 1 #1=x, 2=y, ...
372 var = ''
373 # data = np.loadtxt('solution/hingePartBushing'+var+'2.txt', comments='#', delimiter=',')
374 # plt.plot(data[:,0], data[:,comp], CC(7), label='2 eigenmodes')
375 # data = np.loadtxt('solution/hingePartBushing'+var+'4.txt', comments='#', delimiter=',')
376 # plt.plot(data[:,0], data[:,comp], CC(8), label='4 eigenmodes')
377 data = np.loadtxt('solution/hingePartBushing'+var+'8.txt', comments='#', delimiter=',')
378 plt.plot(data[:,0], data[:,comp], CC(9), label='8 eigenmodes')
379 data = np.loadtxt('solution/hingePartBushing'+var+'16.txt', comments='#', delimiter=',')
380 plt.plot(data[:,0], data[:,comp], CC(10), label='16 eigenmodes')
381 data = np.loadtxt('solution/hingePartBushing'+var+'32.txt', comments='#', delimiter=',')
382 plt.plot(data[:,0], data[:,comp], CC(11), label='32 eigenmodes')
383
384 data = np.loadtxt('solution/hingePartBushing'+var+'2HCB.txt', comments='#', delimiter=',')
385 plt.plot(data[:,0], data[:,comp], CC(1), label='HCB + 2 eigenmodes')
386 data = np.loadtxt('solution/hingePartBushing'+var+'4HCB.txt', comments='#', delimiter=',')
387 plt.plot(data[:,0], data[:,comp], CC(2), label='HCB + 4 eigenmodes')
388 data = np.loadtxt('solution/hingePartBushing'+var+'8HCB.txt', comments='#', delimiter=',')
389 plt.plot(data[:,0], data[:,comp], CC(3), label='HCB + 8 eigenmodes')
390 data = np.loadtxt('solution/hingePartBushing'+var+'16HCB.txt', comments='#', delimiter=',')
391 plt.plot(data[:,0], data[:,comp], CC(4), label='HCB + 16 eigenmodes')
392 data = np.loadtxt('solution/hingePartBushing'+var+'32HCB.txt', comments='#', delimiter=',')
393 plt.plot(data[:,0], data[:,comp], CC(5), label='HCB + 32 eigenmodes')
394 data = np.loadtxt('solution/hingePartBushing'+var+'64HCB.txt', comments='#', delimiter=',')
395 plt.plot(data[:,0], data[:,comp], CC(6), label='HCB + 64 eigenmodes')
396 data = np.loadtxt('solution/hingePartBushing'+var+'128HCB.txt', comments='#', delimiter=',')
397 plt.plot(data[:,0], data[:,comp], CC(7), label='HCB + 128 eigenmodes')
398
399
400 ax=plt.gca() # get current axes
401 ax.grid(True, 'major', 'both')
402 ax.xaxis.set_major_locator(ticker.MaxNLocator(10))
403 ax.yaxis.set_major_locator(ticker.MaxNLocator(10))
404 #
405 plt.xlabel("time (s)")
406 plt.ylabel("y-component of tip velocity of hinge (m)")
407 plt.legend() #show labels as legend
408 plt.tight_layout()
409 plt.show()