1 // =============================================================================
2 // PROJECT CHRONO - http://projectchrono.org
3 //
4 // Copyright (c) 2021 projectchrono.org
5 // All right 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: Radu Serban
13 // =============================================================================
14 //
15 // Definition of the base vehicle co-simulation TIRE NODE class.
16 //
17 // The global reference frame has Z up, X towards the front of the vehicle, and
18 // Y pointing to the left.
19 //
20 // =============================================================================
21 
22 #include "chrono/ChConfig.h"
23 #include "chrono/solver/ChIterativeSolver.h"
24 #include "chrono/solver/ChDirectSolverLS.h"
25 
26 #ifdef CHRONO_PARDISO_MKL
27     #include "chrono_pardisomkl/ChSolverPardisoMKL.h"
28 #endif
29 
30 #ifdef CHRONO_MUMPS
31 #include "chrono_mumps/ChSolverMumps.h"
32 #endif
33 
34 #include "chrono_vehicle/cosim/ChVehicleCosimTireNode.h"
35 
36 #include "chrono_thirdparty/rapidjson/filereadstream.h"
37 #include "chrono_thirdparty/rapidjson/istreamwrapper.h"
38 
39 using std::cout;
40 using std::endl;
41 
42 using namespace rapidjson;
43 
44 namespace chrono {
45 namespace vehicle {
46 
47 // =============================================================================
48 
49 // Dummy ChWheel subsystem (needed to attach a ChTire)
50 class DummyWheel : public ChWheel {
51   public:
DummyWheel()52     DummyWheel() : ChWheel("tire_wheel") {}
GetMass() const53     virtual double GetMass() const override { return 0; }
GetInertia() const54     virtual ChVector<> GetInertia() const override { return ChVector<>(0); }
GetRadius() const55     virtual double GetRadius() const override { return 1; }
GetWidth() const56     virtual double GetWidth() const override { return 1; }
57 };
58 
59 // =============================================================================
60 
ChVehicleCosimTireNode(int index)61 ChVehicleCosimTireNode::ChVehicleCosimTireNode(int index)
62     : ChVehicleCosimBaseNode("TIRE_" + std::to_string(index)), m_index(index), m_tire_pressure(true) {
63     // Default integrator and solver types
64     m_int_type = ChTimestepper::Type::EULER_IMPLICIT_LINEARIZED;
65     m_slv_type = ChSolver::Type::BARZILAIBORWEIN;
66 
67     // Create the (sequential) SMC system
68     m_system = new ChSystemSMC;
69     m_system->Set_G_acc(ChVector<>(0, 0, m_gacc));
70 }
71 
72 // -----------------------------------------------------------------------------
73 
GetTireTypeAsString(TireType type)74 std::string ChVehicleCosimTireNode::GetTireTypeAsString(TireType type) {
75     switch (type) {
76         case TireType::RIGID:
77             return "RIGID";
78         case TireType::FLEXIBLE:
79             return "FLEXIBLE";
80         default:
81             return "UNKNOWN";
82     }
83 }
84 
GetTireTypeFromString(const std::string & type)85 ChVehicleCosimTireNode::TireType ChVehicleCosimTireNode::GetTireTypeFromString(const std::string& type) {
86     if (type == "RIGID")
87         return TireType::RIGID;
88     if (type == "FLEXIBLE")
89         return TireType::FLEXIBLE;
90 
91     return TireType::UNKNOWN;
92 }
93 
ReadSpecfile(const std::string & specfile,Document & d)94 bool ChVehicleCosimTireNode::ReadSpecfile(const std::string& specfile, Document& d) {
95     std::ifstream ifs(specfile);
96     if (!ifs.good()) {
97         cout << "ERROR: Could not open JSON file: " << specfile << "\n" << endl;
98         return false;
99     }
100 
101     IStreamWrapper isw(ifs);
102     d.ParseStream<ParseFlag::kParseCommentsFlag>(isw);
103     if (d.IsNull()) {
104         cout << "ERROR: Invalid JSON file: " << specfile << "\n" << endl;
105         return false;
106     }
107 
108     return true;
109 }
110 
GetTireTypeFromSpecfile(const std::string & specfile)111 ChVehicleCosimTireNode::TireType ChVehicleCosimTireNode::GetTireTypeFromSpecfile(const std::string& specfile) {
112     Document d;
113     if (!ReadSpecfile(specfile, d)) {
114         return TireType::UNKNOWN;
115     }
116 
117     if (!d.HasMember("Type") || std::string(d["Type"].GetString()).compare("Tire") != 0) {
118         cout << "ERROR: JSON file " << specfile << " is not a tire JSON specification file!\n" << endl;
119         return TireType::UNKNOWN;
120     }
121 
122     std::string tire_template = d["Template"].GetString();
123     if (tire_template.compare("RigidTire") == 0) {
124         if (d.HasMember("Contact Mesh"))
125             return TireType::RIGID;
126     }
127     if (tire_template.compare("ANCFTire") == 0 || tire_template.compare("ReissnerTire") == 0) {
128         return TireType::FLEXIBLE;
129     }
130 
131     return TireType::UNKNOWN;
132 }
133 
134 // -----------------------------------------------------------------------------
135 
SetTireFromSpecfile(const std::string & filename)136 void ChVehicleCosimTireNode::SetTireFromSpecfile(const std::string& filename) {
137     m_tire_json = filename;
138 }
139 
EnableTirePressure(bool val)140 void ChVehicleCosimTireNode::EnableTirePressure(bool val) {
141     m_tire_pressure = val;
142 }
143 
SetNumThreads(int num_threads)144 void ChVehicleCosimTireNode::SetNumThreads(int num_threads) {
145     m_system->SetNumThreads(num_threads, 1, 1);
146 }
147 
SetIntegratorType(ChTimestepper::Type int_type,ChSolver::Type slv_type)148 void ChVehicleCosimTireNode::SetIntegratorType(ChTimestepper::Type int_type, ChSolver::Type slv_type) {
149     m_int_type = int_type;
150     m_slv_type = slv_type;
151 #ifndef CHRONO_PARDISO_MKL
152     if (m_slv_type == ChSolver::Type::PARDISO_MKL)
153         m_slv_type = ChSolver::Type::BARZILAIBORWEIN;
154 #endif
155 #ifndef CHRONO_MUMPS
156     if (m_slv_type == ChSolver::Type::MUMPS)
157         m_slv_type = ChSolver::Type::BARZILAIBORWEIN;
158 #endif
159 }
160 
Initialize()161 void ChVehicleCosimTireNode::Initialize() {
162     // Invoke the base class method to figure out distribution of node types
163     ChVehicleCosimBaseNode::Initialize();
164 
165     // Complete setup of the underlying ChSystem
166     InitializeSystem();
167 
168     MPI_Status status;
169 
170     // Let derived classes construct the tire
171     ConstructTire();
172 
173     // Create the spindle body
174     m_spindle = chrono_types::make_shared<ChBody>();
175     m_system->AddBody(m_spindle);
176 
177     // Create the wheel subsystem, arbitrarily assuming LEFT side
178     m_wheel = chrono_types::make_shared<DummyWheel>();
179     m_wheel->Initialize(m_spindle, LEFT);
180     m_wheel->SetVisualizationType(VisualizationType::NONE);
181 
182     // Let derived classes initialize the tire and attach it to the provided ChWheel
183     InitializeTire(m_wheel);
184 
185     // Send the tire radius and mass to the MBS node
186     double tire_mass = GetTireMass();
187     double tire_radius = GetTireRadius();
188     double tire_width = GetTireWidth();
189     double tire_info[] = {tire_mass, tire_radius, tire_width};
190     MPI_Send(tire_info, 3, MPI_DOUBLE, MBS_NODE_RANK, 0, MPI_COMM_WORLD);
191 
192     // Receive from the MBS node the load on this tire
193     double load_mass;
194     MPI_Recv(&load_mass, 1, MPI_DOUBLE, MBS_NODE_RANK, 0, MPI_COMM_WORLD, &status);
195 
196     // Overwrite spindle mass and inertia
197     double spindle_mass = load_mass - GetTireMass();
198     ChVector<> spindle_inertia(1, 1, 1);  //// TODO
199     m_spindle->SetMass(spindle_mass);
200     m_spindle->SetInertiaXX(spindle_inertia);
201 
202     // Send the expected communication interface type to the TERRAIN node (only tire 0 does this)
203     if (m_index == 0) {
204         char interface_type = (GetInterfaceType() == InterfaceType::BODY) ? 0 : 1;
205         MPI_Send(&interface_type, 1, MPI_CHAR, TERRAIN_NODE_RANK, 0, MPI_COMM_WORLD);
206     }
207 
208     // Send tire info (mass, radius, width), then mesh info, tire load, and contact material to TERRAIN node
209     MPI_Send(tire_info, 3, MPI_DOUBLE, TERRAIN_NODE_RANK, 0, MPI_COMM_WORLD);
210 
211     unsigned int surf_props[] = {m_mesh_data.nv, m_mesh_data.nn, m_mesh_data.nt};
212     MPI_Send(surf_props, 3, MPI_UNSIGNED, TERRAIN_NODE_RANK, 0, MPI_COMM_WORLD);
213     if (m_verbose)
214         cout << "[Tire node   ] vertices = " << surf_props[0] << "  triangles = " << surf_props[2] << endl;
215 
216     double* vert_data = new double[3 * m_mesh_data.nv + 3 * m_mesh_data.nn];
217     int* tri_data = new int[3 * m_mesh_data.nt + 3 * m_mesh_data.nt];
218     for (unsigned int iv = 0; iv < m_mesh_data.nv; iv++) {
219         vert_data[3 * iv + 0] = m_mesh_data.verts[iv].x();
220         vert_data[3 * iv + 1] = m_mesh_data.verts[iv].y();
221         vert_data[3 * iv + 2] = m_mesh_data.verts[iv].z();
222     }
223     for (unsigned int in = 0; in < m_mesh_data.nn; in++) {
224         vert_data[3 * m_mesh_data.nv + 3 * in + 0] = m_mesh_data.norms[in].x();
225         vert_data[3 * m_mesh_data.nv + 3 * in + 1] = m_mesh_data.norms[in].y();
226         vert_data[3 * m_mesh_data.nv + 3 * in + 2] = m_mesh_data.norms[in].z();
227     }
228     for (unsigned int it = 0; it < m_mesh_data.nt; it++) {
229         tri_data[6 * it + 0] = m_mesh_data.idx_verts[it].x();
230         tri_data[6 * it + 1] = m_mesh_data.idx_verts[it].y();
231         tri_data[6 * it + 2] = m_mesh_data.idx_verts[it].z();
232         tri_data[6 * it + 3] = m_mesh_data.idx_norms[it].x();
233         tri_data[6 * it + 4] = m_mesh_data.idx_norms[it].y();
234         tri_data[6 * it + 5] = m_mesh_data.idx_norms[it].z();
235     }
236     MPI_Send(vert_data, 3 * m_mesh_data.nv + 3 * m_mesh_data.nn, MPI_DOUBLE, TERRAIN_NODE_RANK, 0, MPI_COMM_WORLD);
237     MPI_Send(tri_data, 3 * m_mesh_data.nt + 3 * m_mesh_data.nt, MPI_INT, TERRAIN_NODE_RANK, 0, MPI_COMM_WORLD);
238 
239     MPI_Send(&load_mass, 1, MPI_DOUBLE, TERRAIN_NODE_RANK, 0, MPI_COMM_WORLD);
240 
241     float mat_props[8] = {m_contact_mat->GetKfriction(),    m_contact_mat->GetRestitution(),
242                           m_contact_mat->GetYoungModulus(), m_contact_mat->GetPoissonRatio(),
243                           m_contact_mat->GetKn(),           m_contact_mat->GetGn(),
244                           m_contact_mat->GetKt(),           m_contact_mat->GetGt()};
245     MPI_Send(mat_props, 8, MPI_FLOAT, TERRAIN_NODE_RANK, 0, MPI_COMM_WORLD);
246     if (m_verbose)
247         cout << "[Tire node   ] friction = " << mat_props[0] << endl;
248 }
249 
InitializeSystem()250 void ChVehicleCosimTireNode::InitializeSystem() {
251     // Change solver
252     switch (m_slv_type) {
253         case ChSolver::Type::PARDISO_MKL: {
254 #ifdef CHRONO_PARDISO_MKL
255             auto solver = chrono_types::make_shared<ChSolverPardisoMKL>();
256             solver->LockSparsityPattern(true);
257             m_system->SetSolver(solver);
258 #endif
259             break;
260         }
261         case ChSolver::Type::MUMPS: {
262 #ifdef CHRONO_MUMPS
263             auto solver = chrono_types::make_shared<ChSolverMumps>();
264             solver->LockSparsityPattern(true);
265             m_system->SetSolver(solver);
266 #endif
267             break;
268         }
269         case ChSolver::Type::SPARSE_LU: {
270             auto solver = chrono_types::make_shared<ChSolverSparseLU>();
271             solver->LockSparsityPattern(true);
272             m_system->SetSolver(solver);
273             break;
274         }
275         case ChSolver::Type::SPARSE_QR: {
276             auto solver = chrono_types::make_shared<ChSolverSparseQR>();
277             solver->LockSparsityPattern(true);
278             m_system->SetSolver(solver);
279             break;
280         }
281         case ChSolver::Type::PSOR:
282         case ChSolver::Type::PSSOR:
283         case ChSolver::Type::PJACOBI:
284         case ChSolver::Type::PMINRES:
285         case ChSolver::Type::BARZILAIBORWEIN:
286         case ChSolver::Type::APGD:
287         case ChSolver::Type::GMRES:
288         case ChSolver::Type::MINRES:
289         case ChSolver::Type::BICGSTAB: {
290             m_system->SetSolverType(m_slv_type);
291             auto solver = std::dynamic_pointer_cast<ChIterativeSolver>(m_system->GetSolver());
292             assert(solver);
293             solver->SetMaxIterations(100);
294             solver->SetTolerance(1e-10);
295             break;
296         }
297         default: {
298             cout << "Solver type not supported!" << endl;
299             return;
300         }
301     }
302 
303     // Change integrator
304     switch (m_int_type) {
305         case ChTimestepper::Type::HHT:
306             m_system->SetTimestepperType(ChTimestepper::Type::HHT);
307             m_integrator = std::static_pointer_cast<ChTimestepperHHT>(m_system->GetTimestepper());
308             m_integrator->SetAlpha(-0.2);
309             m_integrator->SetMaxiters(50);
310             m_integrator->SetAbsTolerances(5e-05, 1.8e00);
311             m_integrator->SetMode(ChTimestepperHHT::POSITION);
312             m_integrator->SetScaling(true);
313             m_integrator->SetVerbose(false);
314             m_integrator->SetMaxItersSuccess(5);
315             break;
316 
317         default:
318             break;
319     }
320 }
321 
Synchronize(int step_number,double time)322 void ChVehicleCosimTireNode::Synchronize(int step_number, double time) {
323     switch (GetInterfaceType()) {
324         case InterfaceType::BODY:
325             SynchronizeBody(step_number, time);
326             break;
327         case InterfaceType::MESH:
328             SynchronizeMesh(step_number, time);
329             break;
330     }
331 }
332 
SynchronizeBody(int step_number,double time)333 void ChVehicleCosimTireNode::SynchronizeBody(int step_number, double time) {
334     // Act as a simple counduit between the MBS and TERRAIN nodes
335     MPI_Status status;
336 
337     // Receive spindle state data from MBS node
338     double state_data[13];
339     MPI_Recv(state_data, 13, MPI_DOUBLE, MBS_NODE_RANK, step_number, MPI_COMM_WORLD, &status);
340 
341     BodyState spindle_state;
342     spindle_state.pos = ChVector<>(state_data[0], state_data[1], state_data[2]);
343     spindle_state.rot = ChQuaternion<>(state_data[3], state_data[4], state_data[5], state_data[6]);
344     spindle_state.lin_vel = ChVector<>(state_data[7], state_data[8], state_data[9]);
345     spindle_state.ang_vel = ChVector<>(state_data[10], state_data[11], state_data[12]);
346 
347     // Pass it to derived class
348     ApplySpindleState(spindle_state);
349 
350     // Send spindle state data to Terrain node
351     MPI_Send(state_data, 13, MPI_DOUBLE, TERRAIN_NODE_RANK, step_number, MPI_COMM_WORLD);
352 
353     // Receive spindle force from TERRAIN NODE and send to MBS node
354     double force_data[6];
355     MPI_Recv(force_data, 6, MPI_DOUBLE, TERRAIN_NODE_RANK, step_number, MPI_COMM_WORLD, &status);
356 
357     TerrainForce spindle_force;
358     spindle_force.force = ChVector<>(force_data[0], force_data[1], force_data[2]);
359     spindle_force.moment = ChVector<>(force_data[3], force_data[4], force_data[5]);
360 
361     // Pass it to derived class
362     ApplySpindleForce(spindle_force);
363 
364     // Send spindle force to MBS node
365     MPI_Send(force_data, 6, MPI_DOUBLE, MBS_NODE_RANK, step_number, MPI_COMM_WORLD);
366 }
367 
SynchronizeMesh(int step_number,double time)368 void ChVehicleCosimTireNode::SynchronizeMesh(int step_number, double time) {
369     MPI_Status status;
370 
371     // Receive spindle state data from MBS node
372     double state_data[13];
373     MPI_Recv(state_data, 13, MPI_DOUBLE, MBS_NODE_RANK, step_number, MPI_COMM_WORLD, &status);
374 
375     BodyState spindle_state;
376     spindle_state.pos = ChVector<>(state_data[0], state_data[1], state_data[2]);
377     spindle_state.rot = ChQuaternion<>(state_data[3], state_data[4], state_data[5], state_data[6]);
378     spindle_state.lin_vel = ChVector<>(state_data[7], state_data[8], state_data[9]);
379     spindle_state.ang_vel = ChVector<>(state_data[10], state_data[11], state_data[12]);
380 
381     // Pass it to derived class.
382     ApplySpindleState(spindle_state);
383 
384     // Send mesh state (vertex locations and velocities) to TERRAIN node
385     MeshState mesh_state;
386     LoadMeshState(mesh_state);
387     unsigned int nvs = m_mesh_data.nv;
388     double* vert_data = new double[2 * 3 * nvs];
389     for (unsigned int iv = 0; iv < nvs; iv++) {
390         vert_data[3 * iv + 0] = mesh_state.vpos[iv].x();
391         vert_data[3 * iv + 1] = mesh_state.vpos[iv].y();
392         vert_data[3 * iv + 2] = mesh_state.vpos[iv].z();
393     }
394     for (unsigned int iv = 0; iv < nvs; iv++) {
395         vert_data[3 * nvs + 3 * iv + 0] = mesh_state.vvel[iv].x();
396         vert_data[3 * nvs + 3 * iv + 1] = mesh_state.vvel[iv].y();
397         vert_data[3 * nvs + 3 * iv + 2] = mesh_state.vvel[iv].z();
398     }
399     MPI_Send(vert_data, 2 * 3 * nvs, MPI_DOUBLE, TERRAIN_NODE_RANK, step_number, MPI_COMM_WORLD);
400 
401     // Receive mesh forces from TERRAIN node.
402     // Note that we use MPI_Probe to figure out the number of indices and forces received.
403     int nvc = 0;
404     MPI_Probe(TERRAIN_NODE_RANK, step_number, MPI_COMM_WORLD, &status);
405     MPI_Get_count(&status, MPI_INT, &nvc);
406     int* index_data = new int[nvc];
407     double* mesh_contact_data = new double[3 * nvc];
408     MPI_Recv(index_data, nvc, MPI_INT, TERRAIN_NODE_RANK, step_number, MPI_COMM_WORLD, &status);
409     MPI_Recv(mesh_contact_data, 3 * nvc, MPI_DOUBLE, TERRAIN_NODE_RANK, step_number, MPI_COMM_WORLD, &status);
410 
411     MeshContact mesh_contact;
412     mesh_contact.nv = nvc;
413     mesh_contact.vidx.resize(nvc);
414     mesh_contact.vforce.resize(nvc);
415     for (int iv = 0; iv < nvc; iv++) {
416         int index = index_data[iv];
417         mesh_contact.vidx[iv] = index;
418         mesh_contact.vforce[iv] =
419             ChVector<>(mesh_contact_data[3 * iv + 0], mesh_contact_data[3 * iv + 1], mesh_contact_data[3 * iv + 2]);
420     }
421 
422     if (m_verbose)
423         cout << "[Tire node   ] step number: " << step_number << "  vertices in contact: " << mesh_contact.nv << endl;
424 
425     // Pass the mesh contact forces to the derived class
426     ApplyMeshForces(mesh_contact);
427 
428     // Send spindle forces to MBS node
429     TerrainForce spindle_force;
430     LoadSpindleForce(spindle_force);
431     double force_data[] = {spindle_force.force.x(),  spindle_force.force.y(),  spindle_force.force.z(),
432                            spindle_force.moment.x(), spindle_force.moment.y(), spindle_force.moment.z()};
433     MPI_Send(force_data, 6, MPI_DOUBLE, MBS_NODE_RANK, step_number, MPI_COMM_WORLD);
434 
435     delete[] vert_data;
436     delete[] index_data;
437     delete[] mesh_contact_data;
438 }
439 
OutputData(int frame)440 void ChVehicleCosimTireNode::OutputData(int frame) {
441     OnOutputData(frame);
442 }
443 
444 }  // namespace vehicle
445 }  // namespace chrono
446