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