Changes between Version 3 and Version 4 of Java API

Show
Ignore:
Timestamp:
09/04/12 19:15:19 (13 years ago)
Author:
jorisborgdorff
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Java API

    v3 v4  
    1212* terminal, for terminating empty conduits. 
    1313 
    14 These elements are all supported in the MUSCLE API. Conduits and parameters are used the same in MML-based or free-form code. 
     14These elements are all supported in the MUSCLE API. Of these elements, only submodels have the explicit concept of scale. In MUSCLE this is mapped to one temporal scale and multiple spatial scales. The temporal scale is used to determine the simulation time at the current iteration, see the [[CxA Documentation|configuration documentation]] on how to set the scales. Conduits and parameters are used the same in MML-based or free-form code. 
    1515 
    1616=== Conduits === 
     
    113113Reading this code step by step, a submodel can be restarted any number of times depending on the couplings, this is what the outer loop does. Next, each time a submodel is started, it has an initialization phase where it determines the initial state and what the simulation time of this initial state should be. Then it enters a while loop while some end condition is not met. Each iteration it sends some observation of the state, and it computes the next state. When the end condition is met, it is possible to do some cleaning and to send a final observation. At the end of the submodel it decides whether it should restart. 
    114114 
    115 In MUSCLE, endCondition is implemented as the willStop() method, which looks at all the messages sent and received and the message with the highest simulation time is compared with the total time in the timescale of the submodel, with parameter "submodelName:T". Restart submodel returns false by default, but could depend on whether one of the conduits will receive more messages. 
     115In MUSCLE, a submodel is created by extending {{{muscle.core.kernel.Submodel}}}. endCondition is implemented as the willStop() method, which looks at all the messages sent and received and the message with the highest simulation time is compared with the total time in the timescale of the submodel. Restart submodel returns false by default, but could depend on whether one of the conduits will receive more messages. 
     116 
     117The other methods are empty by default so to have a meaningful submodel they should be overridden. For example, if the model depends on some initial geometry which will be calculated by another submodel, the init() method could be implemented as such: 
     118{{{ 
     119int paramA; 
     120int[] geometry; 
     121 
     122@Override 
     123protected Timestamp init(Timestamp prevOrigin) { 
     124    paramA = getIntProperty("paramName"); 
     125    Observation<int[]> initialGeometry = in("geometry").receiveObservation(); 
     126    geometry = initialGeometry.getData(); 
     127    return initialGeometry.getTimestamp(); 
     128} 
     129}}} 
     130If no message is received in the init() function, the best way to return is {{{return super.init(prevOrigin);}}}. This will take the previous origin and return 0 if it was null, and prevOrigin plus the total time of the timescale if not null. It is not allowed to send messages during the initialization. 
     131 
     132After initialization the submodel continues by first calling intermediateObservation() and then solvingStep(). In intermediateObservation the model may send messages, in solvingStep it may only receive messages. Other than that they are regular functions. Although intermediateObservation is not necessarily overridden, solvingStep should be overridden: it should contain the core of the code. 
     133 
     134{{{ 
     135@Override 
     136protected void intermediateObservation() { 
     137    double[] dens = calculateDensity(geometry); 
     138    out("density").send(dens); 
     139} 
     140 
     141@Override 
     142protected void solvingStep() { 
     143    int[] changedLocations = (int[]) in("update").receive(); 
     144    geometry = updateGeometry(changedLocations); 
     145} 
     146}}} 
     147 
     148Finally, when this loop has iterated as often as the timescale says it should, the method finalObservation is called. Here any clean-up can be performed, and final messages may be sent. 
     149 
     150{{{ 
     151@Override 
     152protected void finalObservation() { 
     153    out("finalGeometry").send(geometry); 
     154} 
     155}}} 
     156 
     157If restartSubmodel is overridden and returns true, then again init will be called, etc. If the state needs to be stored after restarting, this can be done by setting a field of the class. 
     158 
     159=== Mappers === 
     160 
     161In MML, a mapper is a computational element that may have multiple in- and outbound ports and may perform any mapping on the data received. In principle it should be stateless, but in MUSCLE this is not enforced. Also, a mapper should first receive on all its ports and then send on all its ports. This is only partially enforced in MUSCLE. There are two specializations of the mapper: the fan-in mapper, which receives on multiple ports but sends on one; and the fan-out mapper which receives on a single port and   
     162 
     163A mapper is created in MUSCLE by extending {{{muscle.core.kernel.Mapper}}} or its subclasses {{{muscle.core.kernel.FanInMapper}}} and {{{muscle.core.kernel.FanOutMapper}}}. The mapper has the following loop 
     164{{{ 
     165init() 
     166while (continueComputation()) { 
     167    receiveAll() 
     168    sendAll() 
     169} 
     170}}} 
     171 
     172The default implementation of continueComputation() is to check whether all incoming ports will receive a next message, and returns false only if this is the case. If some of the ports are not mandatory, it is possible to override continueComputation(). In init any parameters may be read or initialization may be performed. In receiveAll() messages are received from the ports and in sendAll() messages are sent. The mapping may be performed in either of the two, whichever is convenient. Since the mapper is scaleless, the implementation should explicitly set the timestamps of the messages. 
     173 
     174{{{ 
     175Observation<int[]> input; 
     176 
     177@Override 
     178protected void receiveAll() { 
     179    input = in("geometry").receiveObservation(); 
     180} 
     181 
     182@Override 
     183protected void sendAll() { 
     184    double[] geomDouble = convertToDouble(input.getData()); 
     185    Observation<double[]> geomDoubleObs = input.copyWithData(geomDouble); 
     186    out("geometryDouble").send(geomDoubleObs); 
     187 
     188    out("geometryInt").send(input); 
     189} 
     190}}} 
     191In the code above a geometry is received, and one is passed on un-altered and the other converted to {{{int[]}}} with some function. The convenience method copyWithData is used to have the same timestamps as the original observation, but different data. 
     192 
     193In the fan-out mapper, the receiveAll method is already defined, and the result of the single port is saved in the {{{Observation value}}} field. Conversely, in the fan-in mapper the implementation of receiveAll should store a single observation in the field {{{Observation value}}}, which the mapper will then send. 
     194 
     195=== Filters === 
     196 
     197A conduit filter is like a mapper, but is only applied to a single conduit. The implementation is also more light-weight, and theoretically it is allowed to modify the timestamps of messages, or drop them all-together. To implement a filter, extend {{{muscle.core.conduit.filter.AbstractObservationFilter}}}. This uses generics to indicate what values it should convert between. It is allowed to define a constructor, this should then either be empty or take a single {{{double}}} argument. The only function that should be overridden is apply() and this should call put() as many times as it wants to send a message. A simple multiplication-filter would look as follows 
     198{{{ 
     199public class MultiplicationFilter extends muscle.core.conduit.filter.AbstractObservationFilter<double[], double[]> { 
     200    double factor; 
     201 
     202    public MultiplicationFilter(double factor) { 
     203        this.factor = factor; 
     204    } 
     205 
     206    @Override 
     207    public void apply(Observation<double[]> obs) { 
     208        double[] data = obs.getData(); 
     209 
     210        // perform multiplication 
     211        for (int i = 0; i < data.length; i++) { 
     212            data[i] *= factor; 
     213        } 
     214 
     215        // Create a new observable to send 
     216        Observation<double[]> multObs = obs.copyWithData(data); 
     217        put(multObs); 
     218    } 
     219} 
     220}}} 
     221 
     222In the {{{muscle.core.conduit.filter}}} are some examples of filters which are ready to use. 
     223 
     224=== Terminals === 
     225 
     226 
    116227 
    117228== MUSCLE free-form API ==