1 // =============================================================================
2 // PROJECT CHRONO - http://projectchrono.org
3 //
4 // Copyright (c) 2020 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 MBS 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 <fstream>
23 #include <algorithm>
24 #include <set>
25 #include <vector>
26 
27 #include "chrono/ChConfig.h"
28 #include "chrono/solver/ChIterativeSolver.h"
29 #include "chrono/solver/ChDirectSolverLS.h"
30 #include "chrono/utils/ChUtilsInputOutput.h"
31 
32 #ifdef CHRONO_PARDISO_MKL
33     #include "chrono_pardisomkl/ChSolverPardisoMKL.h"
34 #endif
35 
36 #ifdef CHRONO_MUMPS
37     #include "chrono_mumps/ChSolverMumps.h"
38 #endif
39 
40 #include "chrono_vehicle/cosim/ChVehicleCosimMBSNode.h"
41 
42 using std::cout;
43 using std::endl;
44 
45 namespace chrono {
46 namespace vehicle {
47 
48 // Construction of the base MBS node
ChVehicleCosimMBSNode()49 ChVehicleCosimMBSNode::ChVehicleCosimMBSNode() : ChVehicleCosimBaseNode("MBS"), m_fix_chassis(false) {
50     // Default integrator and solver types
51     m_int_type = ChTimestepper::Type::EULER_IMPLICIT_LINEARIZED;
52     m_slv_type = ChSolver::Type::BARZILAIBORWEIN;
53 
54     // Create the (sequential) SMC system
55     m_system = new ChSystemSMC;
56     m_system->Set_G_acc(ChVector<>(0, 0, m_gacc));
57 
58     // Set default number of threads
59     m_system->SetNumThreads(1, 1, 1);
60 }
61 
~ChVehicleCosimMBSNode()62 ChVehicleCosimMBSNode::~ChVehicleCosimMBSNode() {
63     delete m_system;
64 }
65 
66 // -----------------------------------------------------------------------------
67 
SetNumThreads(int num_threads)68 void ChVehicleCosimMBSNode::SetNumThreads(int num_threads) {
69     m_system->SetNumThreads(num_threads, 1, 1);
70 }
71 
SetIntegratorType(ChTimestepper::Type int_type,ChSolver::Type slv_type)72 void ChVehicleCosimMBSNode::SetIntegratorType(ChTimestepper::Type int_type, ChSolver::Type slv_type) {
73     m_int_type = int_type;
74     m_slv_type = slv_type;
75 #ifndef CHRONO_PARDISO_MKL
76     if (m_slv_type == ChSolver::Type::PARDISO_MKL)
77         m_slv_type = ChSolver::Type::BARZILAIBORWEIN;
78 #endif
79 #ifndef CHRONO_MUMPS
80     if (m_slv_type == ChSolver::Type::MUMPS)
81         m_slv_type = ChSolver::Type::BARZILAIBORWEIN;
82 #endif
83 }
84 
85 // -----------------------------------------------------------------------------
86 
AttachDrawbarPullRig(std::shared_ptr<ChVehicleCosimDBPRig> rig)87 void ChVehicleCosimMBSNode::AttachDrawbarPullRig(std::shared_ptr<ChVehicleCosimDBPRig> rig) {
88     m_DBP_rig = rig;
89 }
90 
GetDrawbarPullRig() const91 std::shared_ptr<ChVehicleCosimDBPRig> ChVehicleCosimMBSNode::GetDrawbarPullRig() const {
92     return m_DBP_rig;
93 }
94 
95 // -----------------------------------------------------------------------------
96 // Initialization of the MBS node:
97 // - receive terrain height and dimensions
98 // - receive tire mass and radius
99 // - construct and initialize MBS
100 // - send load mass on each wheel
101 // -----------------------------------------------------------------------------
Initialize()102 void ChVehicleCosimMBSNode::Initialize() {
103     // Invoke the base class method to figure out distribution of node types
104     ChVehicleCosimBaseNode::Initialize();
105 
106     // Complete setup of the underlying ChSystem
107     InitializeSystem();
108 
109     MPI_Status status;
110 
111     // Receive from TERRAIN node the initial terrain dimensions and the terrain height
112     double init_dim[3];
113     MPI_Recv(init_dim, 3, MPI_DOUBLE, TERRAIN_NODE_RANK, 0, MPI_COMM_WORLD, &status);
114 
115     if (m_verbose) {
116         cout << "[MBS node    ] Received initial terrain height = " << init_dim[0] << endl;
117         cout << "[MBS node    ] Received terrain length    =  " << init_dim[1] << endl;
118         cout << "[MBS node    ] Received terrain width    =  " << init_dim[2] << endl;
119     }
120 
121     double terrain_height = init_dim[0];
122     ChVector2<> terrain_size(init_dim[1], init_dim[2]);
123 
124     // For each TIRE, receive the tire mass and radius
125     std::vector<ChVector<>> tire_info;
126 
127     for (unsigned int i = 0; i < m_num_tire_nodes; i++) {
128         double tmp[3];
129         MPI_Recv(tmp, 3, MPI_DOUBLE, TIRE_NODE_RANK(i), 0, MPI_COMM_WORLD, &status);
130         tire_info.push_back(ChVector<>(tmp[0], tmp[1], tmp[2]));
131     }
132 
133     // Let derived classes construct and initialize their multibody system
134     InitializeMBS(tire_info, terrain_size, terrain_height);
135     assert(GetNumSpindles() == (int)m_num_tire_nodes);
136 
137     GetChassisBody()->SetBodyFixed(m_fix_chassis);
138 
139     // For each tire:
140     // - cache the spindle body
141     // - get the load on the wheel and send to TIRE node
142     for (unsigned int i = 0; i < m_num_tire_nodes; i++) {
143         double load = GetSpindleLoad(i);
144         MPI_Send(&load, 1, MPI_DOUBLE, TIRE_NODE_RANK(i), 0, MPI_COMM_WORLD);
145     }
146 
147     // Initialize the DBP rig if one is attached
148     if (m_DBP_rig) {
149         m_DBP_rig->m_verbose = m_verbose;
150         m_DBP_rig->Initialize(GetChassisBody(), tire_info, m_step_size);
151 
152         m_DBP_outf.open(m_node_out_dir + "/DBP.dat", std::ios::out);
153         m_DBP_outf.precision(7);
154         m_DBP_outf << std::scientific;
155 
156         OnInitializeDBPRig(m_DBP_rig->GetMotorFunction());
157     }
158 }
159 
160 // -----------------------------------------------------------------------------
161 // Complete setup of the underlying ChSystem based on any user-provided settings
162 // -----------------------------------------------------------------------------
InitializeSystem()163 void ChVehicleCosimMBSNode::InitializeSystem() {
164     // Change solver
165     switch (m_slv_type) {
166         case ChSolver::Type::PARDISO_MKL: {
167 #ifdef CHRONO_PARDISO_MKL
168             auto solver = chrono_types::make_shared<ChSolverPardisoMKL>();
169             solver->LockSparsityPattern(true);
170             m_system->SetSolver(solver);
171 #endif
172             break;
173         }
174         case ChSolver::Type::MUMPS: {
175 #ifdef CHRONO_MUMPS
176             auto solver = chrono_types::make_shared<ChSolverMumps>();
177             solver->LockSparsityPattern(true);
178             m_system->SetSolver(solver);
179 #endif
180             break;
181         }
182         case ChSolver::Type::SPARSE_LU: {
183             auto solver = chrono_types::make_shared<ChSolverSparseLU>();
184             solver->LockSparsityPattern(true);
185             m_system->SetSolver(solver);
186             break;
187         }
188         case ChSolver::Type::SPARSE_QR: {
189             auto solver = chrono_types::make_shared<ChSolverSparseQR>();
190             solver->LockSparsityPattern(true);
191             m_system->SetSolver(solver);
192             break;
193         }
194         case ChSolver::Type::PSOR:
195         case ChSolver::Type::PSSOR:
196         case ChSolver::Type::PJACOBI:
197         case ChSolver::Type::PMINRES:
198         case ChSolver::Type::BARZILAIBORWEIN:
199         case ChSolver::Type::APGD:
200         case ChSolver::Type::GMRES:
201         case ChSolver::Type::MINRES:
202         case ChSolver::Type::BICGSTAB: {
203             m_system->SetSolverType(m_slv_type);
204             auto solver = std::dynamic_pointer_cast<ChIterativeSolver>(m_system->GetSolver());
205             assert(solver);
206             solver->SetMaxIterations(100);
207             solver->SetTolerance(1e-10);
208             break;
209         }
210         default: {
211             cout << "Solver type not supported!" << endl;
212             return;
213         }
214     }
215 
216     // Change integrator
217     switch (m_int_type) {
218         case ChTimestepper::Type::HHT:
219             m_system->SetTimestepperType(ChTimestepper::Type::HHT);
220             m_integrator = std::static_pointer_cast<ChTimestepperHHT>(m_system->GetTimestepper());
221             m_integrator->SetAlpha(-0.2);
222             m_integrator->SetMaxiters(50);
223             m_integrator->SetAbsTolerances(5e-05, 1.8e00);
224             m_integrator->SetMode(ChTimestepperHHT::POSITION);
225             m_integrator->SetScaling(true);
226             m_integrator->SetVerbose(false);
227             m_integrator->SetMaxItersSuccess(5);
228             break;
229         default:
230             break;
231     }
232 }
233 
234 // -----------------------------------------------------------------------------
235 // Synchronization of the MBS node:
236 // - extract and send tire mesh vertex states
237 // - receive and apply vertex contact forces
238 // -----------------------------------------------------------------------------
Synchronize(int step_number,double time)239 void ChVehicleCosimMBSNode::Synchronize(int step_number, double time) {
240     MPI_Status status;
241 
242     for (unsigned int i = 0; i < m_num_tire_nodes; i++) {
243         // Send wheel state to the tire node
244         BodyState state = GetSpindleState(i);
245         double state_data[] = {
246             state.pos.x(),     state.pos.y(),     state.pos.z(),                      //
247             state.rot.e0(),    state.rot.e1(),    state.rot.e2(),    state.rot.e3(),  //
248             state.lin_vel.x(), state.lin_vel.y(), state.lin_vel.z(),                  //
249             state.ang_vel.x(), state.ang_vel.y(), state.ang_vel.z()                   //
250         };
251 
252         MPI_Send(state_data, 13, MPI_DOUBLE, TIRE_NODE_RANK(i), step_number, MPI_COMM_WORLD);
253 
254         // Receive spindle force as applied to the center of the spindle/wheel.
255         // Note that we assume this is the resultant wrench at the wheel origin (expressed in absolute frame).
256         double force_data[6];
257         MPI_Recv(force_data, 6, MPI_DOUBLE, TIRE_NODE_RANK(i), step_number, MPI_COMM_WORLD, &status);
258 
259         TerrainForce spindle_force;
260         spindle_force.point = GetSpindleBody(i)->GetPos();
261         spindle_force.force = ChVector<>(force_data[0], force_data[1], force_data[2]);
262         spindle_force.moment = ChVector<>(force_data[3], force_data[4], force_data[5]);
263         ApplySpindleForce(i, spindle_force);
264     }
265 }
266 
267 // -----------------------------------------------------------------------------
268 // Advance simulation of the MBS node by the specified duration
269 // -----------------------------------------------------------------------------
Advance(double step_size)270 void ChVehicleCosimMBSNode::Advance(double step_size) {
271     m_timer.reset();
272     m_timer.start();
273     double t = 0;
274     while (t < step_size) {
275         double h = std::min<>(m_step_size, step_size - t);
276         PreAdvance();
277         m_system->DoStepDynamics(h);
278         if (m_DBP_rig) {
279             m_DBP_rig->OnAdvance(step_size);
280         }
281         PostAdvance();
282         t += h;
283     }
284     m_timer.stop();
285     m_cum_sim_time += m_timer();
286 }
287 
OutputData(int frame)288 void ChVehicleCosimMBSNode::OutputData(int frame) {
289     double time = m_system->GetChTime();
290 
291     // If a DBP rig is attached, output its results
292     if (m_DBP_rig && time >= m_DBP_rig->m_delay_time) {
293         std::string del("  ");
294 
295         m_DBP_outf << time << del;
296         m_DBP_outf << m_DBP_rig->GetLinVel() << del << m_DBP_rig->GetAngVel() << del;
297         m_DBP_outf << m_DBP_rig->GetSlip() << del << m_DBP_rig->GetFilteredSlip() << del;
298         m_DBP_outf << m_DBP_rig->GetDBP() << del << m_DBP_rig->GetFilteredDBP() << del;
299 
300         m_DBP_outf << endl;
301     }
302 
303     // Let derived classes perform specific output
304     OnOutputData(frame);
305 }
306 
OutputVisualizationData(int frame)307 void ChVehicleCosimMBSNode::OutputVisualizationData(int frame) {
308     auto filename = OutputFilename(m_node_out_dir + "/visualization", "vis", "dat", frame, 5);
309     utils::WriteVisualizationAssets(m_system, filename, true);
310 }
311 
312 }  // end namespace vehicle
313 }  // end namespace chrono
314