| 1 | == About the API == |
| 2 | |
| 3 | The Multiscale Modeling Language (MML) defines several computational elements. The names of these elements are: |
| 4 | * conduit; |
| 5 | * submodel; |
| 6 | * mapper; |
| 7 | * filter; and |
| 8 | * terminal. |
| 9 | |
| 10 | These elements are all supported in the MUSCLE API. |
| 11 | |
| 12 | There are two ways to exploit the MUSCLE API: either by using these constructs, or by writing free-form code, where all orchestration and deadlock detection is your own responsibility. The former gives code that is easy to read and to debug, while the latter makes it possible to optimally exploit any parallelism in the model. This page section is split in two parts, one for each approach. With care, it is also possible to combine these approaches in a model, choosing the more appropriate form per submodel. |
| 13 | |
| 14 | In both cases, conduits are defined the same, and parameters are passed in the same way. |
| 15 | |
| 16 | === Conduits === |
| 17 | |
| 18 | The conduit is the mechanism in the MUSCLE runtime environment to send data. In the API, the ConduitEntrance and ConduitExit are accessible. The ConduitEntrance is used to send data, while the ConduitExit receives data. These entrances and exits, as we call them, can be accessed in two ways. Either they are created in the addPortals() method, or they are called on the fly in the code using the out() and in() methods. |
| 19 | |
| 20 | The addPortals method is called before the submodel starts, so the exits and entrances are stored as fields of your class. The conduit exit and entrance uses Java Generics to define what kind of data will be received or sent, both in the field declaration and in the addExit or addEntrance method. This allows for compile-time and run-time checking of data that is sent over the conduit. |
| 21 | {{{ |
| 22 | ConduitExit<double[]> exitA; |
| 23 | ConduitEntrance<double[]> entranceA; |
| 24 | |
| 25 | @Override |
| 26 | protected void addPortals() { |
| 27 | exitA = addExit("exitName", double[].class); |
| 28 | entranceA = addEntrance("entranceName", double[].class); |
| 29 | } |
| 30 | }}} |
| 31 | To send or receive, use the fields that were initialized in addPortals(). |
| 32 | {{{ |
| 33 | double[] dataA = exitA.receive(); |
| 34 | entranceA.send(dataA); |
| 35 | }}} |
| 36 | |
| 37 | If addPortals() is not overridden it is possible to use the out() and in() methods to send or receive data. It is then not necessary to store the ConduitExit and ConduitEntrance as a field. |
| 38 | {{{ |
| 39 | double[] dataA = (double[]) in("exitName").receive(); |
| 40 | out("entranceName").send(dataA); |
| 41 | }}} |
| 42 | In this case received data needs to be cast to double[] to use it, and in the send statement, the Java compiler will give a warning that an unchecked conversion is being done. If the cast is not correct, a ClassCastException will be thrown by Java. If the exit or entrance name is not configured, an IllegalArgumentException will be thrown by MUSCLE. |
| 43 | |
| 44 | The advantage of the first method is that it is type-safe, so there is no need to cast the data. Also if the code and the configuration file do not match this is detected immediately. The advantage of the second method is that it is less verbose and leads to smaller classes. |
| 45 | |
| 46 | === Parameters === |
| 47 | |
| 48 | Parameters that are defined in the configuration file are accessed through a range of get*Property() methods. The most basic form is |
| 49 | {{{ |
| 50 | String parameter = getProperty("propName"); |
| 51 | }}} |
| 52 | In this form the property is read as a string. It first tries to find the instance property by searching for "instanceName:propName" and then for a global property named "propName". All get*Property() methods will throw an IllegalArgumentException if the property does not exist, this can be prevented by first calling hasProperty(). |
| 53 | If a instance property is specifically required, this can be checked by first calling |
| 54 | {{{ |
| 55 | String parameter = null; |
| 56 | if (hasInstanceProperty("propName")) { |
| 57 | parameter = getProperty("propName"); |
| 58 | } |
| 59 | }}} |
| 60 | If on the other hand only a global property is wanted, write |
| 61 | {{{ |
| 62 | String parameter = getGlobalProperty("propName"); |
| 63 | }}} |
| 64 | |
| 65 | To get other types of parameters than strings, use getPathProperty for a File, getIntProperty for an int, getDoubleProperty for a double and getBoolProperty for a boolean. |
| 66 | |
| 67 | == MUSCLE MML API == |
| 68 | |
| 69 | == MUSCLE free-form API == |
| 70 | |
| 71 | In the free-form API all kernels extend muscle.core.kernel.Instance. At least the execute() method of Instance should be overridden. In this method any sequence of send and receive is possible, and the conceptual coupling between different submodels can be as small or large as wanted. |
| 72 | |
| 73 | One notable difference with the MUSCLE MML API is that it can quit a submodel more directly. If there should still be some synchronization with other submodels, the willStop() method checks when the conduits have sent messages that are larger than the total timescale of the current submodel, or larger than the "max_timesteps" property. An example submodel Sender with name "w" (see src/java/examples/simplejava for the full code), which sends data each iteration would look as follows when using willStop(): |
| 74 | |
| 75 | {{{ |
| 76 | import muscle.core.ConduitEntrance; |
| 77 | import muscle.core.kernel.Instance; |
| 78 | |
| 79 | /** |
| 80 | a simple java example kernel which sends data |
| 81 | */ |
| 82 | public class Sender extends Instance { |
| 83 | private ConduitEntrance<double[]> entrance; |
| 84 | |
| 85 | @Override |
| 86 | protected void addPortals() { |
| 87 | entrance = addEntrance("data", double[].class); |
| 88 | } |
| 89 | |
| 90 | @Override |
| 91 | protected void execute() { |
| 92 | double[] dataA = new double[5]; |
| 93 | |
| 94 | while (!this.willStop()) { |
| 95 | // process data |
| 96 | for(int i = 0; i < dataA.length; i++) { |
| 97 | dataA[i] = i; |
| 98 | } |
| 99 | |
| 100 | // send the data |
| 101 | entrance.send(dataA); |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | } |
| 106 | }}} |
| 107 | By changing the "max_timesteps" and the "w:dt" property, this submodel will send different number of messages. For example, if max_timesteps is 4, and w:dt is 1 it will send 4 messages. |