trick/trick_source/sim_services/Integrator/src/IntegLoopScheduler.cpp
2024-08-13 10:51:24 -05:00

703 lines
22 KiB
C++

// Local includes
#include "trick/IntegLoopScheduler.hh"
#include "trick/IntegLoopManager.hh"
#include "trick/IntegJobClassId.hh"
// Trick includes
#include "trick/exec_proto.h"
#include "trick/message_proto.h"
#include "trick/message_type.h"
#include "trick/JobData.hh"
// System includes
#include <iostream>
#include <iomanip>
#include <cstdarg>
#include <math.h>
// Anonymous namespace for local functions
namespace {
/**
* Process the enabled job in the provided job queue.
* @param job_queue Job queue to be processed.
*/
inline void call_jobs (
Trick::ScheduledJobQueue & job_queue)
{
Trick::JobData * curr_job;
job_queue.reset_curr_index();
while ((curr_job = job_queue.get_next_job()) != NULL) {
curr_job->call();
}
}
}
/**
The singleton IntegrationManager object.
It is a protected static member of the IntegLoopScheduler class.
*/
Trick::IntegrationManager Trick::IntegLoopScheduler::manager;
/**
The Integrator currently being processed.
*/
Trick::Integrator* trick_curr_integ = NULL;
/**
Non-default constructor.
@param in_cycle The time interval at which the loop's integrate function is called
@param in_parent_so Pointer to the parent sim object.
*/
Trick::IntegLoopScheduler::IntegLoopScheduler(
double in_cycle, Trick::SimObject * in_parent_so)
:
Scheduler (),
verbosity (0),
last_step_deriv (false),
first_step_deriv (false),
integ_ptr (NULL),
sim_objects (),
nominal_cycle (in_cycle),
next_cycle (in_cycle),
parent_sim_object (in_parent_so),
pre_integ_jobs (),
deriv_jobs (),
integ_jobs (),
dynamic_event_jobs (),
post_integ_jobs ()
{
complete_construction();
}
/**
Default constructor
*/
Trick::IntegLoopScheduler::IntegLoopScheduler()
:
Scheduler (),
verbosity (0),
last_step_deriv (false),
first_step_deriv (false),
integ_ptr (NULL),
sim_objects (),
nominal_cycle (),
next_cycle (),
parent_sim_object (NULL),
pre_integ_jobs (),
deriv_jobs (),
integ_jobs (),
dynamic_event_jobs (),
post_integ_jobs ()
{
complete_construction();
}
/**
Complete the construction of an integration loop.
All constructors but the copy constructor call this method.
*/
void Trick::IntegLoopScheduler::complete_construction()
{
// Create the class_name to class_id mapping.
class_map["derivative"] = Trick::DerivativeJobClassId;
class_to_queue[Trick::DerivativeJobClassId] = &deriv_jobs;
class_map["integration"] = Trick::IntegrationJobClassId;
class_to_queue[Trick::IntegrationJobClassId] = &integ_jobs;
class_map["dynamic_event"] = Trick::DynamicEventJobClassId;
class_to_queue[Trick::DynamicEventJobClassId] = &dynamic_event_jobs;
class_map["post_integration"] = Trick::PostIntegrationJobClassId;
class_to_queue[Trick::PostIntegrationJobClassId] = &post_integ_jobs;
class_map["pre_integration"] = Trick::PreIntegrationJobClassId;
class_to_queue[Trick::PreIntegrationJobClassId] = &pre_integ_jobs;
}
/**
Find the specified sim object in the set of objects managed by this loop.
@return Iterator pointing to the object, or to sim_objects.end() if not found.
@param sim_obj Object to be found.
*/
Trick::IntegLoopScheduler::SimObjectVector::iterator
Trick::IntegLoopScheduler::find_sim_object(Trick::SimObject & sim_obj)
{
return std::find (sim_objects.begin(), sim_objects.end(), &sim_obj);
}
/**
Add jobs from specified object.
@param in_obj Pointer to the sim object from which the integration jobs
should be added.
Deprecated!
*/
int Trick::IntegLoopScheduler::add_integ_jobs_from_sim_object (
Trick::SimObject * in_obj)
{
unsigned int ii ;
std::map<std::string, int>::iterator class_id_it ;
std::map<int, Trick::ScheduledJobQueue *>::iterator queue_it ;
Trick::ScheduledJobQueue * curr_queue ;
JobData * job ;
// TODO: add check to make sure the integ job is not set to another object before adding job to queue
// TODO: Check other simobjects for jobs that are to be added to this integration
// TODO: check all integration schemes for integrate structures are the same
// For each of the jobs in the SimObject
for ( ii = 0 ; ii < in_obj->jobs.size() ; ii++ ) {
job = in_obj->jobs[ii] ;
// if the job's job_class_name is in the class map then set its job_class to the corresponding class_id
if ( (class_id_it = class_map.find(job->job_class_name)) != class_map.end() ) {
// set this integration loop frequency to all its individual jobs
job->cycle = nominal_cycle;
job->job_class = class_id_it->second ;
if ( (queue_it = class_to_queue.find(job->job_class)) != class_to_queue.end() ) {
curr_queue = queue_it->second ;
curr_queue->push_ignore_sim_object( job ) ;
}
}
}
return 0;
}
// Overload of Scheduler::add_sim_object that does nothing.
int Trick::IntegLoopScheduler::add_sim_object(Trick::SimObject * sim_obj __attribute((unused))) {
return 0;
}
/**
Add a sim object to the set of objects integrated by this integration loop.
The job queues for this loop are rebuild after adding the sim object.
@param sim_obj Address of the sim object that should be added to scheduler.
*/
int Trick::IntegLoopScheduler::add_sim_object(Trick::SimObject & sim_obj)
{
// Disallow duplicate objects.
if (find_sim_object (sim_obj) != sim_objects.end()) {
message_publish (
MSG_ERROR,
"Integ Scheduler ERROR: "
"SimObject '%s' is already managed by this integration loop.\n",
sim_obj.name.c_str());
return 1;
}
// Object is not in the set. Add it.
sim_objects.push_back (&sim_obj);
// Rebuild jobs, but only if the manager has been initialized.
// Adding jobs prior to the initial build are legal; they are addressed
// by an S_define-level call to rebuild_jobs.
if (! manager.is_empty()) {
// If this object is already managed by a different integration loop,
// remove it from that other loop to avoid integrating the object twice.
IntegLoopScheduler * other = manager.get_integrated_by (&sim_obj);
if (other != NULL) {
other->remove_sim_object (sim_obj);
}
// Now rebuild our jobs.
rebuild_jobs();
}
return 0;
}
/**
Remove a sim object from the objects integrated by this integration loop.
The job queues for this loop are rebuilt after removing the sim object.
@param sim_obj Address of sim object that should be removed from scheduler.
*/
int Trick::IntegLoopScheduler::remove_sim_object(Trick::SimObject & sim_obj)
{
// Find the object in the set of objects managed by this loop;
// The object must be in that set if it is to be removed.
SimObjectVector::iterator iter = find_sim_object (sim_obj);
if (iter == sim_objects.end()) {
message_publish (
MSG_ERROR,
"Integ Scheduler ERROR: "
"SimObject '%s' is not managed by this integration loop.\n",
sim_obj.name.c_str());
return 1;
}
// Object is in the set. Remove it.
sim_objects.erase (iter);
// Rebuild jobs, but only if the manager has been initialized.
// Calls to remove prior to the initial build are legal (but silly).
if (! manager.is_empty()) {
rebuild_jobs();
}
return 0;
}
/**
Prepare for a restart.
*/
void Trick::IntegLoopScheduler::restart_checkpoint()
{
manager.clear_sim_object_info();
}
/**
Rebuild the integration loop's job queues.
This method is called internally upon adding or deleting sim objects,
and is called externally (S_define level) during initialization and
after a checkpoint restart.
*/
void Trick::IntegLoopScheduler::rebuild_jobs ()
{
// Rebuild the manager.
manager.build_sim_object_info();
// Reset the manager's notion of which objects this integrator integrates.
manager.clear_integrated_by (this);
for (SimObjectVector::iterator so_iter = sim_objects.begin();
so_iter != sim_objects.end();
++so_iter) {
Trick::SimObject * sim_object = *so_iter;
manager.set_integrated_by (sim_object, this);
}
// Rebuild each of this integration loop's job queues.
for (std::map<std::string, int>::iterator id_iter = class_map.begin();
id_iter != class_map.end();
++id_iter) {
const std::string & job_class_name = id_iter->first;
int job_class_id = id_iter->second;
Trick::ScheduledJobQueue & queue = *(class_to_queue.at(job_class_id));
// Rebuild the queue by first tearing it down to nothing
// and then rebuilding from scratch.
clear_queue (queue);
for (SimObjectVector::iterator so_iter = sim_objects.begin();
so_iter != sim_objects.end();
++so_iter) {
Trick::SimObject * sim_object = *so_iter;
manager.add_jobs_to_queue (
job_class_name, job_class_id, sim_object, queue);
}
}
}
/**
Empty a job queue in anticipation of the queue being rebuilt.
@param job_queue Address of job queue that should be cleared.
*/
void Trick::IntegLoopScheduler::clear_queue (
Trick::ScheduledJobQueue & job_queue)
{
if (job_queue.size() != 0) {
Trick::JobData * curr_job;
job_queue.reset_curr_index();
while ((curr_job = job_queue.get_next_job()) != NULL) {
curr_job->set_handled (false);
}
job_queue.clear ();
}
}
/**
Call the enabled derivative jobs.
*/
void Trick::IntegLoopScheduler::call_deriv_jobs ()
{
call_jobs (deriv_jobs);
}
/**
Integrate objects to the current simulation time.
Note that sim_time is advanced before integration, so integration has to
be from sim_time-cycle to sim_time.
*/
int Trick::IntegLoopScheduler::integrate()
{
double t_end = exec_get_sim_time();
double t_start = t_end - next_cycle; // This is the time of the current state vector.
int status;
// Call all of the jobs in the pre-integration job queue.
call_jobs (pre_integ_jobs);
// Integrate sim objects to the current time.
status = integrate_dt (t_start, next_cycle);
if (status != 0) {
return status;
}
// Process dynamic events, if any.
if (! dynamic_event_jobs.empty()) {
status = process_dynamic_events (t_start, t_end);
if (status != 0) {
return status;
}
}
// Call the jobs in the derivative job queue one more time if indicated.
if (get_last_step_deriv()) {
call_jobs (deriv_jobs);
}
// Call all of the jobs in the post-integration job queue.
call_jobs (post_integ_jobs);
// We should now be back in sync with the nominal cycle rate.
next_cycle = nominal_cycle;
return 0;
}
/**
Determine whether derivative jobs should be called on the first step.
True if any integrator needs derivatives on the first step, false otherwise.
*/
bool Trick::IntegLoopScheduler::get_first_step_deriv_from_integrator()
{
// We need derivatives if the integ_ptr says so.
if ((integ_ptr != NULL) && integ_ptr->first_step_deriv) {
return true;
}
// Other integrators may be set in sup_class_data. Check them.
else {
Trick::JobData * curr_job;
void* sup_class_data;
Trick::Integrator* trick_integrator;
integ_jobs.reset_curr_index();
while ((curr_job = integ_jobs.get_next_job()) != NULL) {
if ((curr_job->job_class == Trick::IntegrationJobClassId) &&
((sup_class_data = curr_job->sup_class_data) != NULL) &&
((trick_integrator =
*(static_cast<Trick::Integrator**>(sup_class_data))) != NULL) &&
trick_integrator->first_step_deriv) {
return true;
}
}
}
return false;
}
/**
Determine whether derivative jobs should be called prior to exiting the
main integration function. True if this object's last_deriv_flag is set,
or if any integrator's last_deriv_flag is set. False otherwise.
*/
bool Trick::IntegLoopScheduler::get_last_step_deriv()
{
// We need to calculate post-integration derivatives if this object
// or the integ_ptr says to do so.
if (last_step_deriv ||
((integ_ptr != NULL) && integ_ptr->last_step_deriv)) {
return true;
}
// Other integrators may be set in sup_class_data. Check them.
else {
Trick::JobData * curr_job;
void* sup_class_data;
Trick::Integrator* trick_integrator;
integ_jobs.reset_curr_index();
while ((curr_job = integ_jobs.get_next_job()) != NULL) {
if ((curr_job->job_class == Trick::IntegrationJobClassId) &&
((sup_class_data = curr_job->sup_class_data) != NULL) &&
((trick_integrator =
*(static_cast<Trick::Integrator**>(sup_class_data))) != NULL) &&
trick_integrator->last_step_deriv) {
return true;
}
}
}
return false;
}
/**
Integrate over the specified time interval.
*/
int Trick::IntegLoopScheduler::integrate_dt ( double t_start, double dt) {
int ipass = 0;
int ex_pass = 0;
bool need_derivs = get_first_step_deriv_from_integrator();
double target_time = t_start + dt;
do {
ex_pass ++;
// Call all of the jobs in the derivative job queue if needed.
if (need_derivs) {
call_jobs (deriv_jobs);
}
need_derivs = true;
// Call all of the jobs in the integration job queue.
Trick::JobData * curr_job;
integ_jobs.reset_curr_index();
while ((curr_job = integ_jobs.get_next_job()) != NULL) {
void* sup_class_data = curr_job->sup_class_data;
// Jobs without supplemental data use the default integrator.
if (sup_class_data == NULL) {
trick_curr_integ = integ_ptr;
}
// Non-null supplemental data:
// Resolve as a pointer-to-a-pointer to a Trick::Integrator.
else {
trick_curr_integ =
*(static_cast<Trick::Integrator**>(sup_class_data));
}
if (trick_curr_integ == NULL) {
message_publish (
MSG_ERROR,
"Integ Scheduler ERROR: "
"Integrate job has no associated Integrator.\n");
return 1;
}
if (ex_pass == 1) {
trick_curr_integ->time = t_start;
trick_curr_integ->dt = dt;
trick_curr_integ->target_integ_time = target_time;
}
if (verbosity || trick_curr_integ->verbosity) {
message_publish (MSG_DEBUG, "Job: %s, target_integ_time: %f, integ_time: %f, dt: %f, ipass = %d\n",
curr_job->name.c_str(), target_time, t_start, dt, ipass);
}
ipass = curr_job->call();
// Trick integrators are expected to advance from step one to step
// two, etc., and then back to zero to indicate completion.
// All integrators are expected to march to the same beat.
// FIXME, future: This restricts Trick to using only rather
// simple integration techniques.
if ((ipass != 0) && (ipass != ex_pass)) {
message_publish (
MSG_ERROR,
"Integ Scheduler ERROR: Integrators not in sync.\n");
return 1;
}
}
} while (ipass);
return 0;
}
int Trick::IntegLoopScheduler::process_dynamic_events ( double t_start, double t_end, unsigned int depth) {
bool fired = false;
double t_to = t_end;
double t_from = t_start;
Trick::JobData * curr_job;
dynamic_event_jobs.reset_curr_index();
while ((curr_job = dynamic_event_jobs.get_next_job()) != NULL) {
// Call the dynamic event job to calculate an estimated time-to-go.
double tgo = curr_job->call_double();
if (tgo < 0.0) { // If there is a root in this interval ...
fired = true;
while (tgo != 0.0) {
if (tgo > 0.0) {
t_from = t_to;
t_to = t_to + tgo;
int status = integrate_dt( t_from, (t_to-t_from));
if (status != 0) { return status; }
} else {
#define FORWARD_INTEGRATION 0
#if FORWARD_INTEGRATION
integ_ptr->state_reset(); // We always want to integrate forward.
t_to = t_to + tgo;
#else
t_from = t_to;
t_to = t_from + tgo;
#endif
int status = integrate_dt( t_from, (t_to-t_from));
if (status != 0) { return status; }
}
tgo = curr_job->call_double();
}
}
}
/*
Integrate to the end of the integration cycle using the updated derivatives.
*/
if (fired) {
t_from = t_to;
t_to = t_end;
/*
Because the derivatives have likely changed, we need to recursively
check for new events in the remaining interval.
*/
int status = integrate_dt( t_from, (t_to-t_from));
if (status != 0) { return status; }
status = process_dynamic_events(t_from, t_to, depth+1);
if (status != 0) { return status; }
}
return 0;
}
/**
Utility function to get an integrator.
@param alg The integration algorithm to use.
@param state_size the total number of integration variables and derivative variables.
*/
Trick::Integrator * Trick::IntegLoopScheduler::getIntegrator (
Integrator_type alg, unsigned int state_size)
{
integ_ptr = Trick::getIntegrator (alg, state_size);
trick_curr_integ = integ_ptr;
return integ_ptr;
}
/**
Set the interval at which this job is called.
@param in_cycle The frequency for the integration cycle.
*/
int Trick::IntegLoopScheduler::set_integ_cycle (
double in_cycle)
{
JobData * found_job = NULL;
int loops_found = 0;
// Search through the parent sim object looking for integ_loop jobs.
// There should be exactly one such job.
for (std::vector<Trick::JobData *>::iterator vit =
parent_sim_object->jobs.begin();
vit != parent_sim_object->jobs.end();
++vit) {
JobData * vit_job = *vit;
if (! vit_job->job_class_name.compare("integ_loop")) {
++loops_found;
found_job = vit_job;
}
}
// Not finding exactly one integ_loop job is an error.
if (loops_found != 1) {
message_publish (
MSG_ERROR,
"Integ Scheduler ERROR: %s\n",
(loops_found == 0) ? "integ_loop job not found." :
"multiple integ_loop jobs found.");
return 1;
}
// Found exactly one integ_loop job.
// Set the cycle rate for that one job and for this object.
// Note: This assumes that the one found job pertains to this scheduler.
else {
long long prev_tics = found_job->next_tics - found_job->cycle_tics;
long long curr_tics = exec_get_time_tics();
found_job->set_cycle (in_cycle);
found_job->set_next_call_time (curr_tics);
nominal_cycle = in_cycle;
next_cycle = double((found_job->next_tics - prev_tics)) /
double(found_job->time_tic_value);
return 0;
}
}
/**
Write the content of the S_job_execution file.
@param fp The pointer to the file to write the S_job_execution file.
*/
int Trick::IntegLoopScheduler::write_s_job_execution(FILE *fp)
{
if (fp == NULL) {
return 0;
}
fprintf(fp, "\n===================================================================================================\n") ;
fprintf(fp, "Integration Loop:\n\n") ;
pre_integ_jobs.write_sched_queue(fp) ;
deriv_jobs.write_sched_queue(fp) ;
integ_jobs.write_sched_queue(fp) ;
dynamic_event_jobs.write_sched_queue(fp) ;
post_integ_jobs.write_sched_queue(fp) ;
return 0;
}
/**
Adds the incoming instrumentation job before target job if specified, or all jobs in the list.
@param instrument_job Pointer to the job that should be run previously.
*/
int Trick::IntegLoopScheduler::instrument_job_before (
Trick::JobData * instrument_job)
{
int count = 0 ;
/** @par Detailed Design */
count += pre_integ_jobs.instrument_before(instrument_job) ;
count += deriv_jobs.instrument_before(instrument_job) ;
count += integ_jobs.instrument_before(instrument_job) ;
count += dynamic_event_jobs.instrument_before(instrument_job) ;
count += post_integ_jobs.instrument_before(instrument_job) ;
/** @li Return how many insertions were done. */
return count;
}
/**
Adds the incoming instrumentation job after target job if specified, or all jobs in the list.
@param instrument_job Pointer to the job that should be run after each job.
*/
int Trick::IntegLoopScheduler::instrument_job_after (
Trick::JobData * instrument_job)
{
int count = 0 ;
count += pre_integ_jobs.instrument_after(instrument_job) ;
count += deriv_jobs.instrument_after(instrument_job) ;
count += integ_jobs.instrument_after(instrument_job) ;
count += dynamic_event_jobs.instrument_after(instrument_job) ;
count += post_integ_jobs.instrument_after(instrument_job) ;
return count;
}
/**
Removes all jobs in the list that match the name in_job.
@param in_job name of the job that should be removed.
*/
int Trick::IntegLoopScheduler::instrument_job_remove(std::string in_job)
{
pre_integ_jobs.instrument_remove(in_job) ;
deriv_jobs.instrument_remove(in_job) ;
integ_jobs.instrument_remove(in_job) ;
dynamic_event_jobs.instrument_remove(in_job) ;
post_integ_jobs.instrument_remove(in_job) ;
return 0;
}