A "model" is a class that derives from dsim_model (the highest superclass). An "object" is an actual instance of a model inside a simulation.
All models must descend, either directly or indirectly, from dsim_model. If you'd like to subclass from a model that provides rigid body dynamics, you can subclass from dsim_rigid_body.
All variables and states start out as a member variable of your class, of type "dsim_variable". For instance, to create a variable, state, or outlet called "foo", your class header file would contain "dsim_variable foo;" in your list of member variables in your class.
What you do with that variable during initialization will determine how DSim handles it. If you'd like it to be an integrated state, you call dsim_model::create_integrated_state() and store the result in your member variable. If you want to publish it as a variable, but not have it be integrated, you call one of the other create_* functions: dsim_model::create_parameter(), dsim_model::create_input(), dsim_model::create_output(), dsim_model::create_state(). All of these are functionally identical; the only difference is that each marks the variable for different usage. The usages are merely hints to users of your model when it is being configured in a simulation (and potentially in whatever application is running the simulation, like VisualCommander); the variables are otherwise identical. (In fact, dsim_model::create_integrated_state() is also nearly identical- it just sets a different usage and registers the variable with your object's integrator.) If you want a variable to be an outlet, you call dsim_model::create_outlet() with the variable as the first parameter.
Note that in all of these cases, the variable started life the same way: a dsim_variable member variable of your class. This means that once you've set it up (an outlet, an integrated state, etc), you can interact with it in the same way regardless of what type you've chosen for it. Call the various set methods on it to set its value or derivative, and use the get functions to get its current value. This also means that (with the exception of outlets), all of your variables appear the same way to external entities: state or not, it can be logged, looked up by other objects, and marked as telemetry or as a command. The entire variable/state/outlet system has been unified, and all of the functionality is encapsulated by the create_* functions and by the member functions of dsim_variable.
You can initialize member variables in your constructor, but not dsim_variable. Your dsim_model::initialize_data() function should take care of all of your initial DSim calls- notably, setting up your variables and outlets. Do not attempt to *use* the variables or outlets yet, or attempt to look up anything else; your custom setup parsing will not have been called yet, and not all of the objects in the simulation will have been initialized when initialize_data() is called.
The dsim_model::initialization_complete() function is the place to put any initialization that needs to happen at a time when the simulation is entirely constructed and set up. When this function is called, all of your custom setup lines will have been parsed, any outlets, targets, and networks will have been fully connected and populated, etc. This is also where you might call the configure_timestep() function, if you need to configure what timestep methods DSim calls on your object. (Turning off function calls is entirely a performance optimization, but it can be a very important one, especially in multithreaded or distributed environments.)
Your model implements the rhs() function, and within that function the derivatives of your integrated variables are calculated and stored. You call dsim_variable::set_derivative() on the variable for your state to store the derivative you have calculated. For model interactions affecting the right-hand-side such as forces and torques in a physical system, use a message to send the values to the destination object.
The first question you might ask is, why messages? The simple answer is because they allow the receiving object to perform an action as many times per timestep as a message arrives, and to do so the moment the message arrives, rather than only being able to do so at predefined times during the timestep. By having the rigid body accept apply_force and apply_torque messages, each force and torque can be summed together, and then the sum can be used during the next rhs() call on the parent rigid body.
Setting a model up to handle incoming messages is quite trivial: simply override the dsim_model::handle_message() function. Doing so is generally straightforward, and quite similar to overriding dsim_model::parse_setup() for handling setup commands. The following is the dsim_rigid_body handle_message function, which allows it to handle apply_force and apply_torque commands:
dsim_value dsim_rigid_body::handle_message(const std::string &sender_path,const std::string &message_name,const dsim_value &argument) { if (message_name == "apply_force") { force+=argument.matrix_value(); return dsim_value(); } else if (message_name == "apply_torque") { torque+=argument.matrix_value(); return dsim_value(); } else return dsim_model::handle_message(sender_path,message_name,argument); }
Sending messages is even more trivial than receiving them. Your model simply needs to know the path of the object to which the message should be sent. The easiest way to do that is to create a target with dsim_model::create_target(), and then have the target path be set up in the setup file. An example of sending messages via target is below, taken from the builtin dsim_ideal_spring object. (In the example, target_pos, spring_base and spring_k are all dsim_variables. The code is taken from the spring's rhs() function.)
ml_matrix p = target_pos.value_as_matrix(); ml_matrix force = (spring_base.value_as_matrix() - p) * spring_k.value_as_double(); dsim_value fval(force); const char *target_path = lookup_target("mass"); send_message(target_path,"apply_force",fval);
Managed networks enable models to create a web of connections, such as for a power network. See the Model Networks for the functions needed in the dsim_model and the dsim_network class page.
For example in your model's initialize_data() function, you would create the network and declare its variables.
// The power network power_network_dsim = create_managed_network("power", "Power system network" ); // The variable in the network power_network_dsim->declare_variable("power", sd_type_double, "Power input or output");
which creates the network and declares a variable in the network. Then to implement the network, you iterate over its elements. The nodes, in this case, are dsim_variables of type "power".
dsim_connection_iterator iterator = power_network_dsim->connection_iterator(); double net_power = 0; int c_count = power_network_dsim->connection_count(); // If there are any connections find the power consumed or produced for (int k = 0; k < c_count; k++ ) { dsim_connection *c = iterator.connection(); std::string variable_path = std::string(c->destination_path()) + ":power"; dsim_variable var = request_variable(variable_path.c_str()); dsim_value v = var.value(); // or var.cointegrated_value() if desired if (v.valid()) { net_power += v.double_value(); } iterator.next(); }