1# =============================================================================
2# PROJECT CHRONO - http:#projectchrono.org
3#
4# Copyright (c) 2014 projectchrono.org
5# All rights reserved.
6#
7# Use of this source code is governed by a BSD-style license that can be found
8# in the LICENSE file at the top level of the distribution and at
9# http:#projectchrono.org/license-chrono.txt.
10#
11# =============================================================================
12# Authors: Simone Benatti
13# =============================================================================
14#
15# FEA demo on applying loads to beams
16#
17# =============================================================================
18
19import pychrono as chrono
20import pychrono.fea as fea
21import copy
22
23print("Copyright (c) 2017 projectchrono.org ")
24
25# Create the physical system
26my_system = chrono.ChSystemSMC()
27
28# Create a mesh:
29my_mesh = fea.ChMesh()
30my_system.Add(my_mesh)
31
32# Create some nodes.
33mnodeA = fea.ChNodeFEAxyzrot(chrono.ChFrameD(chrono.ChVectorD(0, 0, 0)))
34mnodeB = fea.ChNodeFEAxyzrot(chrono.ChFrameD(chrono.ChVectorD(2, 0, 0)))
35
36# Default mass for FEM nodes is zero
37mnodeA.SetMass(0.0)
38mnodeB.SetMass(0.0)
39
40my_mesh.AddNode(mnodeA)
41my_mesh.AddNode(mnodeB)
42
43# Create beam section & material
44msection = fea.ChBeamSectionEulerAdvanced()
45beam_wy = 0.1
46beam_wz = 0.2
47msection.SetAsRectangularSection(beam_wy, beam_wz)
48msection.SetYoungModulus(0.01e9)
49msection.SetGshearModulus(0.01e9 * 0.3)
50msection.SetBeamRaleyghDamping(0.200)
51msection.SetDensity(1500)
52
53# Create a beam of Eulero-Bernoulli type:
54melementA = fea.ChElementBeamEuler()
55melementA.SetNodes(mnodeA, mnodeB)
56melementA.SetSection(msection)
57my_mesh.AddElement(melementA)
58
59# Create also a truss
60truss = chrono.ChBody()
61truss.SetBodyFixed(True)
62my_system.Add(truss)
63
64# Create a constraat the end of the beam
65constr_a = chrono.ChLinkMateGeneric()
66constr_a.Initialize(mnodeA, truss, False, mnodeA.Frame(), mnodeA.Frame())
67my_system.Add(constr_a)
68constr_a.SetConstrainedCoords(True, True, True,   # x, y, z
69							   True, True, True)  # Rx, Ry, Rz
70
71# APPLY SOME LOADS!
72
73# First: loads must be added to "load containers",
74# and load containers must be added to your system
75mloadcontainer = chrono.ChLoadContainer()
76my_system.Add(mloadcontainer)
77
78# Example 1:
79
80# Add a vertical load to the end of the beam element:
81mwrench = fea.ChLoadBeamWrench(melementA)
82mwrench.loader.SetApplication(1.0)  # in -1..+1 range, -1: end A, 0: mid, +1: end B
83mwrench.loader.SetForce(chrono.ChVectorD(0, -0.2, 0))
84mloadcontainer.Add(mwrench)  # do not forget to add the load to the load container.
85
86# Example 2:
87
88# Add a distributed load along the beam element:
89mwrenchdis = fea.ChLoadBeamWrenchDistributed(melementA)
90mwrenchdis.loader.SetForcePerUnit(chrono.ChVectorD(0, -0.1, 0))  # load per unit length
91mloadcontainer.Add(mwrenchdis)
92
93# Example 3:
94
95# Add gravity (constant volumetric load)
96mgravity = chrono.LoadLoaderGravity(melementA)
97mloadcontainer.Add(mgravity)
98
99# note that by default all solid elements in the mesh will already
100# get gravitational force, if you want to bypass this automatic gravity, do:
101my_mesh.SetAutomaticGravity(False)
102
103"""
104CANNOT INSTANTIATE TEMPLATE
105Example 4 and 5 in C++ equivalent demo require respectively ChLoad<MyLoaderTriangular> and ChLoad<MyLoaderPointStiff>(mnodeC)
106
107"""
108
109# Example 6:
110
111# As before, create a custom load with stiff force, acting on a single node, but
112# this time we inherit directly from ChLoadCustom, i.e. a load that does not require ChLoader features.
113# This is mostly used in case one does not need the automatic surface/volume quadrature of ChLoader.
114# As a stiff load, this will automatically generate a jacobian (tangent stiffness matrix K)
115# that will be used in statics, implicit integrators, etc.
116
117mnodeD = fea.ChNodeFEAxyz(chrono.ChVectorD(2, 10, 3))
118my_mesh.AddNode(mnodeD)
119
120class MyLoadCustom(chrono.ChLoadCustom):
121    def __init__(self, mloadable):
122        chrono.ChLoadCustom.__init__(self, mloadable)
123    #/ "Virtual" copy constructor (covariant return type).
124    def Clone(self):
125        newinst = copy.deepcopy(self)
126        return  newinst
127
128	# Compute Q=Q(x,v)
129	# This is the function that you have to implement. It should return the generalized Q load
130	# (i.e.the force in generalized lagrangian coordinates).
131	# For ChNodeFEAxyz, Q loads are expected as 3-rows vectors, containing absolute force x,y,z.
132	# As this is a stiff force field, dependency from state_x and state_y must be considered.
133    def ComputeQ(self,state_x,      #/< state position to evaluate Q
134                 state_w):     #/< state speed to evaluate Q
135        if not state_x==None and not state_w==None :
136            node_pos = chrono.ChVectorD(state_x[0], state_x[1], state_x[2])
137            node_vel = chrono.ChVectorD(state_w[0], state_w[1], state_w[2])
138        else:
139            mynode = fea.CastToChNodeFEAxyz( fea.CastToChNodeFEAbase( chrono.CastToChNodeBase(self.loadable) ))
140            node_pos = mynode.GetPos()
141            node_vel = mynode.GetPos_dt()
142        # Just implement a simple force+spring+damper in xy plane,
143        # for spring&damper connected to absolute reference:
144        Kx = 100
145        Ky = 400
146        Dx = 0.6
147        Dy = 0.9
148        x_offset = 2
149        y_offset = 10
150        x_force = 50
151        y_force = 0
152        # Store the computed generalized forces in this.load_Q, same x,y,z order as in state_w
153        self.load_Q[0] = x_force - Kx * (node_pos.x - x_offset) - Dx * node_vel.x
154        self.load_Q[1] = y_force - Ky * (node_pos.y - y_offset) - Dy * node_vel.y
155        self.load_Q[2] = 0
156
157	# Compute jacobian not available from Python
158
159	# Remember to set this as stiff, to enable the jacobians
160    def IsStiff(self) :
161        return True
162
163# Instance load object, applying to a node, as in previous example, and add to container:
164mloadcustom = MyLoadCustom(mnodeD)
165mloadcontainer.Add(mloadcustom)
166
167# Example 7:
168
169# As before, create a custom load with stiff force, acting on MULTIPLE nodes at once.
170# This time we will need the ChLoadCustomMultiple as base class.
171# Those nodes (ie.e ChLoadable objects) can be added in my_mesh in whatever order,
172# not necessarily contiguous, because the bookkeeping is automated.
173# Being a stiff load, a jacobian will be automatically generated
174# by default using numerical differentiation but if you want you
175# can override ComputeJacobian() and compute mK, mR analytically - see prev.example.
176
177mnodeE = fea.ChNodeFEAxyz(chrono.ChVectorD(2, 10, 3))
178my_mesh.AddNode(mnodeE)
179mnodeF = fea.ChNodeFEAxyz(chrono.ChVectorD(2, 11, 3))
180my_mesh.AddNode(mnodeF)
181
182class MyLoadCustomMultiple(chrono.ChLoadCustomMultiple):
183    def __init__(self, mloadables):
184        chrono.ChLoadCustomMultiple.__init__(self, mloadables)
185    #/ "Virtual" copy constructor (covariant return type).
186    def Clone(self):
187        print('I am there!')
188        newinst = copy.deepcopy(self)
189        return  newinst
190    # Compute Q=Q(x,v)
191    # This is the function that you have to implement. It should return the generalized Q load
192    # (i.e.the force in generalized lagrangian coordinates).
193    # Since here we have multiple connected ChLoadable objects (the two nodes), the rule is that
194    # all the vectors (load_Q, state_x, state_w) are split in the same order that the loadable objects
195    # are added to MyLoadCustomMultiple in this case for instance Q={Efx,Efy,Efz,Ffx,Ffy,Ffz}.
196    # As this is a stiff force field, dependency from state_x and state_y must be considered.
197    def ComputeQ(self,state_x,      #/< state position to evaluate Q
198                 state_w):     #/< state speed to evaluate Q
199        #chrono.ChVectorD Enode_pos
200        #chrono.ChVectorD Enode_vel
201        #chrono.ChVectorD Fnode_pos
202        #chrono.ChVectorD Fnode_vel
203        if not state_x==None and not state_w==None :
204            Enode_pos = chrono.ChVectorD(state_x[0], state_x[1], state_x[2])
205            Enode_vel = chrono.ChVectorD(state_w[0], state_w[1], state_w[2])
206            Fnode_pos = chrono.ChVectorD(state_x[3], state_x[4], state_x[5])
207            Fnode_vel = chrono.ChVectorD(state_w[3], state_w[4], state_w[5])
208        else:
209            # explicit integrators might call ComputeQ(0,0), null pointers mean
210            # that we assume current state, without passing state_x for efficiency
211            Enode = fea.CastToChNodeFEAxyz( fea.CastToChNodeFEAbase( chrono.CastToChNodeBase(self.loadables[0])))
212            Fnode = fea.CastToChNodeFEAxyz( fea.CastToChNodeFEAbase( chrono.CastToChNodeBase(self.loadables[1])))
213            Enode_pos = Enode.GetPos()
214            Enode_vel = Enode.GetPos_dt()
215            Fnode_pos = Fnode.GetPos()
216            Fnode_vel = Fnode.GetPos_dt()
217  		# Just implement two simple force+spring+dampers in xy plane:
218  		# ... from node E to ground,
219        Kx1 = 60
220        Ky1 = 50
221        Dx1 = 0.3
222        Dy1 = 0.2
223        E_x_offset = 2
224        E_y_offset = 10
225        spring1 = chrono.ChVectorD(-Kx1 * (Enode_pos.x - E_x_offset) - Dx1 * Enode_vel.x, -Ky1 * (Enode_pos.y - E_y_offset) - Dy1 * Enode_vel.y, 0)
226  		# ... from node F to node E,
227        Ky2 = 10
228        Dy2 = 0.2
229        EF_dist = 1
230        spring2 = chrono.ChVectorD (0, -Ky2 * (Fnode_pos.y - Enode_pos.y - EF_dist) - Dy2 * (Enode_vel.y - Fnode_vel.y), 0)
231        Fforcey = 2
232  		# store generalized forces as a contiguous vector in this.load_Q, with same order of state_w
233        self.load_Q[0] = spring1.x - spring2.x    # Fx component of force on 1st node
234        self.load_Q[1] = spring1.y - spring2.y    # Fy component of force on 1st node
235        self.load_Q[2] = spring1.z - spring2.z    # Fz component of force on 1st node
236        self.load_Q[3] = spring2.x                # Fx component of force on 2nd node
237        self.load_Q[4] = spring2.y + Fforcey      # Fy component of force on 2nd node
238        self.load_Q[5] = spring2.z                # Fz component of force on 2nd node
239
240	# OPTIONAL: if you want to provide an analytical jacobian, just implement the following:
241	#   virtual void ComputeJacobian(...)
242
243	# Remember to set this as stiff, to enable the jacobians
244    def IsStiff(self) :
245        return True
246
247
248# Instance load object. This require a list of ChLoadable objects
249# (these are our two nodes,pay attention to the sequence order), and add to container.
250mnodelist = chrono.vector_ChLoadable()
251mnodelist.append(mnodeE)
252mnodelist.append(mnodeF)
253mloadcustommultiple = MyLoadCustomMultiple(mnodelist)
254mloadcontainer.Add(mloadcustommultiple)
255
256###################/
257
258# Setup a MINRES solver. For FEA one cannot use the default PSOR type solver.
259
260solver = chrono.ChSolverMINRES()
261my_system.SetSolver(solver)
262solver.SetMaxIterations(100)
263solver.SetTolerance(1e-10)
264solver.EnableDiagonalPreconditioner(True)
265solver.SetVerbose(True)
266
267# Perform a static analysis:
268my_system.DoStaticLinear()
269
270print(" constr_a reaction force  F= " + str(constr_a.Get_react_force()) )
271print( " constr_a reaction torque T= " + str(constr_a.Get_react_torque()))
272
273#print("mnodeC position = " + mnodeC.GetPos() )
274#print("mloadstiff K jacobian=" + mloadstiff.GetJacobians().K )
275
276print("mnodeD position = ")
277print(mnodeD.GetPos() )
278print("mloadcustom K jacobian=" )
279print( mloadcustom.GetJacobians().K.GetMatr() )
280
281print(" mnodeE position = " )
282print( mnodeE.GetPos() )
283print(" mnodeF position = " )
284print( mnodeF.GetPos() )
285print(" mloadcustommultiple K jacobian=" )
286print( mloadcustommultiple.GetJacobians().K.GetMatr() )
287