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 // Base class for a vehicle co-simulation node.
16 //
17 // =============================================================================
18 
19 #ifndef CH_VEHCOSIM_BASENODE_H
20 #define CH_VEHCOSIM_BASENODE_H
21 
22 #include <fstream>
23 #include <string>
24 #include <iostream>
25 #include <vector>
26 
27 #include <mpi.h>
28 
29 #include "chrono/core/ChTimer.h"
30 #include "chrono/core/ChVector.h"
31 #include "chrono/core/ChQuaternion.h"
32 #include "chrono_vehicle/ChApiVehicle.h"
33 
34 #include "chrono_thirdparty/filesystem/path.h"
35 
36 #define MBS_NODE_RANK 0
37 #define TERRAIN_NODE_RANK 1
38 #define TIRE_NODE_RANK(i) (i+2)
39 
40 namespace chrono {
41 namespace vehicle {
42 
43 /** @addtogroup vehicle_cosim
44  *
45  * The vehicle co-simulation module provides an MPI_based framework for co-simulating a multibody system representing a
46  * wheeled mechanism with various terrain models and optionally various tire models. It implements a 3-way explicit
47  * force-displacement co-simulation approach.  The three different types of nodes present in a co-simulation are as
48  * follows:
49  * - MBS node, a single MPI rank which simulates the multibody system up to the wheel spindles.
50  * - Tire nodes, a number of MPI ranks (equal to the number of wheels), each simulating one of the tires.
51  * - Terrain node(s), one or more MPI ranks which simulate the deformable terrain
52  *
53  * The inter-node communication at each synchronization time is as follows:
54  * - MBS node sends spindle body state to corresponding Tire nodes
55  * - Tire nodes send spindle forces to MBS node
56  * - Tire nodes send tire state (rigid body state or deformable mesh state) to Terrain node
57  * - Terrain node sends tire forces (single resultant force or distributed vertex forces) to corresponding Tire node
58  *
59  * The communication interface between Tire and Terrain nodes can be of one of two types:
60  * - ChVehicleCosimBaseNode::InterfaceType::BODY, in which data (force-displacement) for a single rigid body is
61  * exchanged
62  * - ChVehicleCosimBaseNode::InterfaceType::MESH, in which data (force-displacement) for a deformable mesh is exchanged
63  */
64 
65 /// @addtogroup vehicle_cosim
66 /// @{
67 
68 // =============================================================================
69 
70 namespace cosim {
71 
72 /// Initialize the co-simulation framework.
73 /// This function creates an MPI communicator that includes all nodes designated of type TERRAIN.
74 /// Calling this framework initialization function is optional. If invoked, it *must* be called on all ranks.
75 /// Returns MPI_SUCCESS if successful and MPI_ERR_OTHER if there are not enough ranks.
76 CH_VEHICLE_API int InitializeFramework(int num_tires);
77 
78 /// Return true if the co-simulation framework was initialized and false otherwise.
79 CH_VEHICLE_API bool IsFrameworkInitialized();
80 
81 /// Return the MPI communicator for distributed terrain simulation.
82 /// This intra-communicator is created if more than one node is designated of type TERRAIN.
83 /// On a TERRAIN node, the rank within the intra-communicator is accessible through MPI_Comm_rank.
84 CH_VEHICLE_API MPI_Comm GetTerrainIntracommunicator();
85 
86 };  // namespace cosim
87 
88 // =============================================================================
89 
90 /// Base class for a co-simulation node.
91 class CH_VEHICLE_API ChVehicleCosimBaseNode {
92   public:
93     /// Type of node participating in co-simulation
94     enum class NodeType {
95         MBS,      ///< node performing multibody dynamics (vehicle)
96         TERRAIN,  ///< node performing terrain simulation
97         TIRE      ///< node performing tire simulation (if outside MBS)
98     };
99 
100     /// Type of the tire-terrain communication interface.
101     /// - A BODY interface assumes communication is done at the wheel spindle level.  At a synchronization time, the
102     /// terrain node receives the full state of the spindle body and must send forces acting on the spindle, for each
103     /// tire present in the simulation.  This type of interface should be used for a rigid tire or when the terrain node
104     /// also performs the dynamics of a flexible tire.
105     /// - A MESH interface assumes communication is done at the tire mesh level. At a synchronization time, the terrain
106     /// node receives the tire mesh vertex states (positions and velocities) are must send forces acting on vertices of
107     /// the mesh, for each tire. This tire interface is typically used when flexible tires are simulated outside the
108     /// terrain node (either on the multibody node or else on separate tire nodes).
109     enum class InterfaceType {
110         BODY,  ///< exchange state and force for a single body (wheel spindle)
111         MESH   ///< exchange state and force for a mesh (flexible tire mesh)
112     };
113 
~ChVehicleCosimBaseNode()114     virtual ~ChVehicleCosimBaseNode() {}
115 
116     /// Return the node type.
117     virtual NodeType GetNodeType() const = 0;
118 
119     /// Return the node type as a string.
120     std::string GetNodeTypeString() const;
121 
122     /// Return true if this node is part of the co-simulation infrastructure.
123     bool IsCosimNode() const;
124 
125     /// Set the integration step size (default: 1e-4).
SetStepSize(double step)126     void SetStepSize(double step) { m_step_size = step; }
127 
128     /// Get the integration step size.
GetStepSize()129     double GetStepSize() const { return m_step_size; }
130 
131     /// Set the name of the output directory and an identifying suffix.
132     /// Output files will be created in subdirectories named
133     ///    dir_name/[NodeName]suffix/
134     /// where [NodeName] is "MBS", "TIRE", or "TERRAIN".
135     void SetOutDir(const std::string& dir_name, const std::string& suffix);
136 
137     /// Enable/disable verbose messages during simulation (default: true).
SetVerbose(bool verbose)138     void SetVerbose(bool verbose) { m_verbose = verbose; }
139 
140     /// Get the output directory name for this node.
GetOutDirName()141     const std::string& GetOutDirName() const { return m_node_out_dir; }
142 
143     /// Get the simulation execution time for the current step on this node.
144     /// This represents the time elapsed since the last synchronization point.
GetStepExecutionTime()145     double GetStepExecutionTime() const { return m_timer.GetTimeSeconds(); }
146 
147     /// Get the cumulative simulation execution time on this node.
GetTotalExecutionTime()148     double GetTotalExecutionTime() const { return m_cum_sim_time; }
149 
150     /// Initialize this node.
151     /// This function allows the node to initialize itself and, optionally, perform an initial data exchange with any
152     /// other node. A derived class implementation should first call this base class function.
153     virtual void Initialize();
154 
155     /// Synchronize this node.
156     /// This function is called at every co-simulation synchronization time to
157     /// allow the node to exchange information with any other node.
158     virtual void Synchronize(int step_number, double time) = 0;
159 
160     /// Advance simulation.
161     /// This function is called after a synchronization to allow the node to advance
162     /// its state by the specified time step.  A node is allowed to take as many internal
163     /// integration steps as required, but no inter-node communication should occur.
164     virtual void Advance(double step_size) = 0;
165 
166     /// Output logging and debugging data.
167     virtual void OutputData(int frame) = 0;
168 
169     /// Output post-processing visualization data.
170     /// If implemented, this function should write a file in the "visualization" subdirectory of m_node_out_dir.
171     virtual void OutputVisualizationData(int frame) = 0;
172 
173     /// Write checkpoint to the specified file (which will be created in the output directory).
WriteCheckpoint(const std::string & filename)174     virtual void WriteCheckpoint(const std::string& filename) const {}
175 
176     /// Utility function for creating an output file name.
177     /// It generates and returns a string of the form "{dir}/{root}_{frame}.{ext}", where {frame} is printed using the
178     /// format "%0{frame_digits}d".
179     static std::string OutputFilename(const std::string& dir,
180                                       const std::string& root,
181                                       const std::string& ext,
182                                       int frame,
183                                       int frame_digits);
184 
185   protected:
186     /// Mesh data
187     struct MeshData {
188         unsigned int nv;                       ///< number of vertices
189         unsigned int nn;                       ///< number of normals
190         unsigned int nt;                       ///< number of triangles
191         std::vector<ChVector<>> verts;         ///< vertex positions (in local frame)
192         std::vector<ChVector<>> norms;         ///< vertex normals (in local frame)
193         std::vector<ChVector<int>> idx_verts;  ///< mesh vertex indices (connectivity)
194         std::vector<ChVector<int>> idx_norms;  ///< mesh normal indices
195     };
196 
197     /// Mesh state
198     struct MeshState {
199         std::vector<ChVector<>> vpos;  ///< vertex positions (in absolute frame)
200         std::vector<ChVector<>> vvel;  ///< vertex velocities (in absolute frame)
201     };
202 
203     /// Mesh contact information
204     struct MeshContact {
205         int nv;                          ///< number of vertices in contact
206         std::vector<int> vidx;           ///< indices of vertices experiencing contact forces
207         std::vector<ChVector<>> vforce;  ///< contact forces on mesh vertices
208     };
209 
210   protected:
211     ChVehicleCosimBaseNode(const std::string& name);
212 
213     int m_rank;  ///< MPI rank of this node (in MPI_COMM_WORLD)
214 
215     double m_step_size;  ///< integration step size
216 
217     std::string m_name;          ///< name of the node
218     std::string m_out_dir;       ///< top-level output directory
219     std::string m_node_out_dir;  ///< node-specific output directory
220     std::ofstream m_outf;        ///< output file stream
221 
222     unsigned int m_num_mbs_nodes;
223     unsigned int m_num_terrain_nodes;
224     unsigned int m_num_tire_nodes;
225 
226     ChTimer<double> m_timer;  ///< timer for integration cost
227     double m_cum_sim_time;    ///< cumulative integration cost
228 
229     bool m_verbose;  ///< verbose messages during simulation?
230 
231     static const double m_gacc;
232 };
233 
234 /// @} vehicle_cosim
235 
236 }  // end namespace vehicle
237 }  // end namespace chrono
238 
239 #endif
240