.. _testmodels-contactspherespheretesteapm: ****************************** contactSphereSphereTestEAPM.py ****************************** You can view and download this file on Github: `contactSphereSphereTestEAPM.py <https://github.com/jgerstmayr/EXUDYN/tree/master/main/pythonDev/TestModels/contactSphereSphereTestEAPM.py>`_ .. code-block:: python :linenos: #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # This is an EXUDYN example # # Details: Test of the SphereSphere contact module # # Author: Johannes Gerstmayr and Sebastian Weyrer # Date: 2025-01-07 # # 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. # #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import exudyn as exu from exudyn.utilities import * import exudyn.graphics as graphics import numpy as np useGraphics = True #without test #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #you can erase the following lines and all exudynTestGlobals related operations if this is not intended to be used as TestModel: try: #only if called from test suite from modelUnitTests import exudynTestGlobals #for globally storing test results useGraphics = exudynTestGlobals.useGraphics except: class ExudynTestGlobals: pass exudynTestGlobals = ExudynTestGlobals() #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ SC = exu.SystemContainer() mbs = SC.AddSystem() exu.Print('EXUDYN version = ' + exu.GetVersionString()) #+++++++++++++++++++++++++++++++++++++++++++++ # define behaviour of script T = 1 # [s] simulation time stepSize = 2e-5 # [s] impactModel = 0 # 0 = Adhesive Elasto-Plastic Model; 1 = Hunt-Crossley Model; 2 = mixed Gonthier/EtAl-Carvalho/Martins Model restitutionCoefficient = 0.9 # used restitution coefficient if impactModel != 0 use2Contacts = True # only applies if impactModel != 0, specifies numbers of used contacts for chosen impact model #+++++++++++++++++++++++++++++++++++++++++++++ # define parameters of the speheres radius = 0.1 # [m] radius of spheres mass = 1.6 # [kg] mass of each sphere # define reference position of the moving sphere and initial velocity if impactModel == 0: xInit = 0 yInit = 2 * radius # gap = 0 at beginning of simulation vyInit = 0 else: xInit = 0 yInit = 2 * radius + 0.5 # gap = 0.5m at beginning of simulation vyInit = 10 #+++++++++++++++++++++++++++++++++++++++++++++ # create ground (with marker) that is needed for every test case mbs.CreateGround(referencePosition=[0, 0, 0], graphicsDataList=[graphics.Sphere(radius=radius, color=graphics.color.red, nTiles=64)]) nGround = mbs.AddNode(NodePointGround(referenceCoordinates = [0, 0, 0])) mGround = mbs.AddMarker(MarkerNodeRigid(nodeNumber=nGround)) #+++++++++++++++++++++++++++++++++++++++++++++ # add mass point (with marker) for moving spehere massPoint = mbs.CreateRigidBody(referencePosition=[xInit, yInit, 0], initialVelocity=[0, vyInit, 0], inertia=InertiaSphere(mass, radius), gravity=[0, 0, 0], graphicsDataList=[graphics.Sphere(radius=radius, color=graphics.color.green, nTiles=64)]) mMass = mbs.AddMarker(MarkerBodyRigid(bodyNumber=massPoint)) nMassPoint = mbs.GetObject(massPoint)['nodeNumber'] #+++++++++++++++++++++++++++++++++++++++++++++ # if impactModel = 0, simulate loading and unloading of (soil) particles if impactModel == 0: tMove = T/2 dMove = 0.002 def UFforce(mbs, t, itemNumber, u, v, k, d, F0): if t < tMove: setPosition = 2*radius - (dMove/tMove)*t else: setPosition = 2*radius - dMove + (dMove/tMove)*(t-tMove) # print(setPosition) return (u-setPosition)*1e5 mbs.AddObject(SpringDamper(markerNumbers=[mGround, mMass], referenceLength=0, springForceUserFunction=UFforce)) nData = mbs.AddNode(NodeGenericData(initialCoordinates=[0, 0, 0, 0], numberOfDataCoordinates=4)) oSSC = mbs.AddObject(ObjectContactSphereSphere(markerNumbers=[mGround, mMass], # m1 is moving spehere nodeNumber=nData, spheresRadii=[radius,radius], impactModel=impactModel, contactStiffness=1e5, contactStiffnessExponent=2, contactDamping=0.001, contactPlasticityRatio=0.6, constantPullOffForce=0.01, adhesionCoefficient=4e4, adhesionExponent=2)) #+++++++++++++++++++++++++++++++++++++++++++++ # if impactModel != 0 do impact simulation of particles else: nData = mbs.AddNode(NodeGenericData(initialCoordinates=[0, 0, 0, 0], numberOfDataCoordinates=4)) oSSC = mbs.AddObject(ObjectContactSphereSphere(markerNumbers=[mGround, mMass], # m1 is moving spehere nodeNumber=nData, spheresRadii=[radius,radius], contactStiffness=1e6, contactDamping=0, impactModel=impactModel, restitutionCoefficient=restitutionCoefficient, minimumImpactVelocity=0)) if use2Contacts: # add this ground object with a gap of 0.5m above the moving spehere mbs.CreateGround(referencePosition=[0, 1+4*radius, 0], graphicsDataList=[graphics.Sphere(radius=radius, color=graphics.color.red, nTiles=64)]) nGround1 = mbs.AddNode(NodePointGround(referenceCoordinates = [0, 1+4*radius, 0])) mGround1 = mbs.AddMarker(MarkerNodeRigid(nodeNumber=nGround1)) nData1 = mbs.AddNode(NodeGenericData(initialCoordinates=[0, 0, 0, 0], numberOfDataCoordinates=4)) oSSC1 = mbs.AddObject(ObjectContactSphereSphere(markerNumbers=[mGround1, mMass], # m1 is moving spehere nodeNumber=nData1, spheresRadii=[radius,radius], contactStiffness=1e6, contactDamping=0, impactModel=impactModel, restitutionCoefficient=restitutionCoefficient, minimumImpactVelocity=0)) #+++++++++++++++++++++++++++++++++++++++++++++ # meassure output variables of moving spehere in any test case sPos = mbs.AddSensor(SensorBody(bodyNumber=massPoint, outputVariableType=exu.OutputVariableType.Position, writeToFile=False, storeInternal=True)) sVel = mbs.AddSensor(SensorBody(bodyNumber=massPoint, outputVariableType=exu.OutputVariableType.Velocity, writeToFile=False, storeInternal=True)) sGap = mbs.AddSensor(SensorObject(objectNumber=oSSC, outputVariableType=exu.OutputVariableType.DisplacementLocal, writeToFile=False, storeInternal=True)) sContactForce = mbs.AddSensor(SensorObject(objectNumber=oSSC, outputVariableType=exu.OutputVariableType.Force, writeToFile=False, storeInternal=True)) #+++++++++++++++++++++++++++++++++++++++++++++ mbs.Assemble() # exu.Print(mbs) #+++++++++++++++++++++++++++++++++++++++++++++ simulationSettings = exu.SimulationSettings() simulationSettings.timeIntegration.numberOfSteps = int(T/stepSize) simulationSettings.solutionSettings.sensorsWritePeriod = stepSize simulationSettings.timeIntegration.endTime = T simulationSettings.displayStatistics = True simulationSettings.timeIntegration.verboseMode = 1 simulationSettings.timeIntegration.newton.useModifiedNewton = True simulationSettings.timeIntegration.generalizedAlpha.spectralRadius = 1 #+++++++++++++++++++++++++++++++++++++++++++++ if useGraphics: exu.StartRenderer() # start graphics visualization mbs.WaitForUserToContinue() # wait for pressing SPACE bar to continue mbs.SolveDynamic(simulationSettings) if useGraphics: SC.WaitForRenderEngineStopFlag() # wait for pressing 'Q' to quit exu.StopRenderer() # safely close rendering window! #+++++++++++++++++++++++++++++++++++++++++++++ uTotal = mbs.GetNodeOutput(nMassPoint, exu.OutputVariableType.CoordinatesTotal) exu.Print('uTotal=',uTotal[1]) exudynTestGlobals.testResult = uTotal[1] if useGraphics: #+++++++++++++++++++++++++++++++++++++++++++++ # plot sensor data for different test cases import matplotlib.pyplot as plt def setMatplotlibSettings(): import matplotlib as mpl import matplotlib.font_manager as font_manager import matplotlib.pyplot as plt fsize = 9 # general fontsize tsize = 9 # legend # setting ticks, fontsize tdir = 'out' # 'in' or 'out': direction of ticks major = 5.0 # length major ticks minor = 3.0 # length minor ticks lwidth = 0.8 # width of the frame lhandle = 2 # length legend handle plt.style.use('default') # set font to the locally saved computermodern type plt.rcParams['font.family']='serif' cmfont = font_manager.FontProperties(fname=mpl.get_data_path() + '/fonts/ttf/cmr10.ttf') plt.rcParams['font.serif']=cmfont.get_name() plt.rcParams['mathtext.fontset']='cm' plt.rcParams['axes.unicode_minus']=False plt.rcParams['font.size'] = fsize plt.rcParams['legend.fontsize'] = tsize plt.rcParams['xtick.direction'] = tdir plt.rcParams['ytick.direction'] = tdir plt.rcParams['xtick.major.size'] = major plt.rcParams['xtick.minor.size'] = minor plt.rcParams['ytick.major.size'] = major plt.rcParams['ytick.minor.size'] = minor plt.rcParams['axes.linewidth'] = lwidth plt.rcParams['axes.formatter.use_mathtext'] = True plt.rcParams['legend.handlelength'] = lhandle plt.rcParams['lines.linewidth'] = 1.2 return setMatplotlibSettings() mbs.PlotSensor([sPos,sVel], components=[1, 1], closeAll=False) plt.show(block=True) # for figures to stay open at end of plot routines if impactModel == 0: def add_arrow(line, position=None, direction='right', size=15, color=None): if color is None: color = line.get_color() xdata = line.get_xdata() ydata = line.get_ydata() if position is None: position = xdata.mean() start_ind = np.argmin(np.absolute(xdata - position)) if direction == 'right': end_ind = start_ind + 1 else: end_ind = start_ind - 1 line.axes.annotate('', xytext=(xdata[start_ind], ydata[start_ind]), xy=(xdata[end_ind], ydata[end_ind]), arrowprops=dict(arrowstyle="->", color=color), size=size) fig, (ax1, ax2) = plt.subplots(1, 2, figsize=[8, 4]) t = mbs.GetSensorStoredData(sGap)[:, 0] penetration = -mbs.GetSensorStoredData(sGap)[:, 1] globalContactForce = mbs.GetSensorStoredData(sContactForce)[:, 2] repulsiveForce = -globalContactForce indexFirstPositivePenetration = np.where(penetration > 0)[0][0] indexLastPositivePenetration = np.where(penetration > 0)[0][-1] line1 = ax1.plot(penetration[indexFirstPositivePenetration:indexLastPositivePenetration+1], repulsiveForce[indexFirstPositivePenetration:indexLastPositivePenetration+1])[0] add_arrow(line1, position=0.001) add_arrow(line1, position=0.0016) ax1.grid() ax1.set_xlabel('negative gap in m') ax1.set_ylabel('force (m1) in N') line2 = ax2.plot(penetration[indexFirstPositivePenetration:indexLastPositivePenetration+1], globalContactForce[indexFirstPositivePenetration:indexLastPositivePenetration+1])[0] add_arrow(line2, position=0.001) add_arrow(line2, position=0.0016) ax2.grid() ax2.set_xlabel('negative gap in m') ax2.set_ylabel('force (m0) in N (= global force)') plt.tight_layout() if impactModel != 0: def MaximaAfterCrossings(y): # Identify where the signal crosses from (-) to (+) crossings = np.where((y[:-1] < 0) & (y[1:] > 0))[0] + 1 # Indices where crossing occurs # Add the end of the signal as the last segment crossings = np.concatenate(([0], crossings, [len(y)])) # Calculate the maximum of each segment maxima = [np.max(y[crossings[i]:crossings[i + 1]]) for i in range(len(crossings) - 1)] minima = [np.min(y[crossings[i]:crossings[i + 1]]) for i in range(len(crossings) - 1)] return [maxima[0],-minima[0],maxima[1],-minima[1]] # return [maxima[0],-minima[0]] y = mbs.GetSensorStoredData(sVel)[:,2] maxima = MaximaAfterCrossings(y) #max 4 crossings exu.Print("Maxima after each crossing:", maxima) exu.Print('relations=',maxima[1]/maxima[0],maxima[2]/maxima[1],maxima[3]/maxima[2]) #+++++++++++++++++++++++++++++++++++++++++++++++++++++ #Results: #e=0.5, stepSize=1e-5, Gonthier-CarvalhoMartins: #Maxima after each crossing: [10.0, 5.001784818335925, 2.501696294255996, 1.251250609056101] #e=0.5, stepSize=2e-5, Gonthier-CarvalhoMartins: #Maxima after each crossing: [10.0, 5.001975126052718, 2.501837721858895, 1.2513458250009686] #e=0.5, stepSize=5e-5, Gonthier-CarvalhoMartins: #Maxima after each crossing: [10.0, 5.003239017129689, 2.501566602043766, 1.2509080365758343] #e=0.9, stepSize=2e-5, Gonthier-CarvalhoMartins: #Maxima after each crossing: [10.0, 9.000168963897542, 8.100761764779794, 7.290837898880879] #e=0.8, stepSize=2e-5, Gonthier-CarvalhoMartins: #Maxima after each crossing: [10.0, 8.000159389915021, 6.400886105172352, 5.120771413440074] #e=0.3, stepSize=2e-5, Gonthier-CarvalhoMartins: #Maxima after each crossing: [10.0, 3.0423710530759065, 0.9256019663437189] #e=0.1, stepSize=2e-5, Gonthier-CarvalhoMartins: #Maxima after each crossing: [10.0, 1.0098986986792768, 0.10198953720292783] #e=0.025, stepSize=2e-5, Gonthier-CarvalhoMartins: #Maxima after each crossing: [10.0, 0.25015634771732226] #e=0.9, stepSize=2e-5, Hunt-Crossley: #Maxima after each crossing: [10.0, 9.090298887177969, 8.263743088547175, 7.512142692359256] #e=0.8, stepSize=2e-5, Hunt-Crossley: #Maxima after each crossing: [10.0, 8.32903493005253, 6.937608834244499, 5.778355621679387] #e=0.5, stepSize=2e-5, Hunt-Crossley: #Maxima after each crossing: [10.0, 6.629838391096419, 4.3951552519078065, 2.913688143667944] #e=0.3, stepSize=2e-5, Hunt-Crossley: #Maxima after each crossing: [10.0, 5.813176318310211, 3.3794625180566293, 1.964542613454227] #e=0.1, stepSize=2e-5, Hunt-Crossley: #Maxima after each crossing: [10.0, 5.158573889338283, 2.6613482605395395, 1.3728789060291537] #+++++++++++++++++++++++++++++++++++++++++++++++++++++