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