next up previous
Next: Random Numbers Up: A Discrete EVent system Previous: The Simulator Class


Models with Many Input/Output Types

It would be surprising if every component in a large model (or even a small one) had the same input and output requirements. Some models can be satisfactorily constructed with a single type of input/output object and, if this is the case, it will simplify the design of your simulator. If not, you'll need to address this problem when you design your simulation program.

One solution to this problem is to establish a base class for all input and output types and derive specific types from the common base. The simulator and all of its components exchange pointers to the base class and down cast specific objects as needed. The C++ dynamic_cast operator is particularly useful for this purpose. Although it is not without its problems, I have used this solution in many designs and it works well.

It is not always possible for every component in a model to share a common base class for its input and output type. This can happen if different sub-model have very different input and output needs or when models from earlier projects are reused. For example, to use a CellSpace model as a component of a Digraph model requires some means of converting the CellSpace's CellEvent objects into the PortValue objects required by the Digraph. Happily, there is a simple solution to this problem that makes clever use of the Simulator and EventListener classes to wrap a model with one input and output type inside of an atomic model with a different input and output type.

The Adevs ModelWrapper class is an Atomic model that encapsulates another model. The encapsulated model can be a Network or Atomic model. The ModelWrapper uses input/output objects of type ExternalType, but the encapsulated class uses input/output objects of type InternalType. Two abstract methods are provided for converting objects with one type into objects with the other type; these methods are

void translateInput(const Bag<ExternalType>& external_input, Bag<Event<InternalType> >& internal_input)
void translateOutput(const Bag<Event<InternalType> >& internal_output, Bag<ExternalType>& external_output)
Clean up of converted objects are managed with the gc_output method, which is inherited from the ModelWrapper's Atomic base class, and a new gc_input method for cleaning up objects created by the translateInput method; its signature is
void gc_input(Bag<Event<InternalType> >& g)

The model to encapsulate is passed to the ModelWrapper constructor. The ModelWrapper creates a Simulator for the model that is used to control its evolution; the ModelWrapper is a simulator inside of a model inside of a simulator! The ModelWrapper keeps track of the wrapped model's last event time, and it uses this information and the Simulator's nextEventTime method to compute its time advance. Internal, external, and confluent events cause the WrappedModel to invoke its Simulator's computeNextState method and thereby advance the state of the wrapped model. Internal events are simplest; the computeNextState method is invoked with the wrapped model's next event time and an empty input bag.

The delta_conf and delta_ext, however, must convert the incoming input events, which have the type ExternalType, into input events for the wrapped model, which have the type InternalEvent. This is accomplished with the translateInput method. The first argument to the method is the input bag passed to the ModelWrapper's delta_ext or delta_conf method. The second argument is an empty bag that the method implementation must fill. When the translateInput method returns this bag will be passed to the computeNextState method of the ModelWrapper's simulator. Notice that the internal_input argument is a Bag filled with Event objects; if the wrapped model is a Network then the translated events can be targeted at any of the Network's components. The ModelWrapper invokes the gc_input method when it is done with the events in the internal_input bag. This gives you the opportunity to delete objects that you created when translateInput was called.

A similar process occurs when the ModelWrapper's output_func is invoked, but in this case it is necessary to convert output objects from the wrapped model, which have type InternalType, to output objects from the ModelWrapper, which have type ExternalType. This is accomplished by invoking the translateOutput method. The method's first argument is the bag of output events produced collectively by all of the wrapped model's components; notice that the contents of the internal_output bag are Event objects. The model field points to the component of the wrapped model (or the wrapped model itself) that produced the event and the value field contains an output object produced by that model. These Events must be converted to objects of type ExternalType and stored in the external_output bag. The external_output bag is, in fact, the bag passed to the wrapper's output_func, and so its contents become the output objects produced by the wrapper. The gc_output method is used in the usual way to clean up any objects created by this process.

The Wrapper class shown below illustrates how to use the Adevs WrapperModel class. The Wrapper is derived from the WrapperModel and implements its four abstract methods: translateInput, translateOutput, gc_input, and gc_output. This class wraps an Atomic model that uses int* objects as its input/output. The Wrapper uses C strings as its input and output type. The translation methods merely convert integers to strings and vice versa. The Wrapper can be used just like any Atomic model; it can be a component in a network model or simulated by itself. The behavior of the Wrapper is identical to the model it wraps; the only change is in the interface.

// This class converts between char* and int* event types.
class Wrapper: public adevs::ModelWrapper<char*,int*> {
    public:
        Wrapper(adevs::Atomic<int*>* model):
            // Pass the model to the base class constructor
            adevs::ModelWrapper<char*,int*>(model){}
        void translateInput(const adevs::Bag<char*>& external,
                adevs::Bag<adevs::Event<int*> >& internal) {
            // Iterate through the incoming events
            adevs::Bag<char*>::const_iterator iter;
            for (iter = external.begin(); iter != external.end(); iter++) {
                // Convert each one into an int* and send it to the
                // wrapped model
                adevs::Event<int*> event;
                // Set the event value
                event.value = new int(atoi(*iter));
                // Set the event target
                event.model = getWrappedModel();
                // Put it into the bag of translated objects
                internal.insert(event);
            }
        }
        void translateOutput(const adevs::Bag<adevs::Event<int*> >& internal,
                adevs::Bag<char*>& external) {
            // Iterate through the incoming events
            adevs::Bag<adevs::Event<int*> >::const_iterator iter;
            for (iter = internal.begin(); iter != internal.end(); iter++) {
                // Convert the incoming event value to a string
                char* str = new char[100];
                sprintf(str,"%d",*((*iter).value));
                // Put it into the bag of translated objects
                external.insert(str);
            }
        }
        void gc_output(adevs::Bag<char*>& g) {
            // Delete strings allocated in the translateOutput method
            adevs::Bag<char*>::iterator iter;
            for (iter = g.begin(); iter != g.end(); iter++)
                delete [] *iter;
        }
        void gc_input(adevs::Bag<adevs::Event<int*> >& g) {
            // Delete integers allocated in the translateInput method
            adevs::Bag<adevs::Event<int*> >::iterator iter;;
            for (iter = g.begin(); iter != g.end(); iter++)
                delete (*iter).value;
        }
};

next up previous
Next: Random Numbers Up: A Discrete EVent system Previous: The Simulator Class
James J. Nutaro 2010-09-20