Jonathan Caux
email: jonathan.caux@isima.fr

David Hill

Pridi Siregar
email: pridi.siregar@ibiocomputing.com

Accelerating 3D Cellular automata computation with GP-GPU in the context of integrative biology

Keywords: GP GPU, Parallel computing, 3D cellular automata, Integrative Biology. Résumé GP GPU, calcul parallèle, automate cellulaire 3D, biologie intégrative

In this paper we explore the possibility of using GP GPU technology (General Purpose Graphical Processing Unit) in the context of integrative biology. For more than a decade, 3D cellular automata represent a promising approach to handling multi-scale modeling of organs. However, the computing time of such huge automata has limited the experiments. Current GP GPUs now allow the execution of hundreds of threads with a regular PC hosting a device card. This capability can be exploited in the case of cellular automata where each cell has to compute the same algorithm. We have implemented two algorithms to compare different memory usage. The performances show very significant speedup even when compared to the latest CPU processors. The interconnection of GP GPU boards and servers will be considered to build a local grid of hybrid machines.

Introduction

Integrative biology has a large number of solutions available to simulate multi-scale models: Reaction-Diffusion Equations [START_REF] Lu | Multiscale Modeling in Rodent Ventricular Myocytes[END_REF], Dissipative particle dynamics [START_REF] Pivkin | Effect of Red Blood Cells on Platelet Aggregation[END_REF] or Finite element method [START_REF] Sander | Image-Bases Biomechanics of Collagen-Based Tissus Equivalents[END_REF]. But cellular automata can also be used [START_REF] John Von Neumann | The Theory of Self-reproducing Automata[END_REF], this has been shown for instance to simulate a part of an organ behavior [START_REF] Alarcón | Towards whole-organ modelling of tumour growth[END_REF] or the depolarization and repolarization of the heart muscle cells which can be seen as an excitable medium [START_REF] Siregar | An Interactive 3D Anisotropic Cellular Automata Model of the Heart[END_REF]. In this paper, we consider 3D cellular automata, consisting in a cube of cells where each cell can be in a specific state. A finite number of states are considered and a cell state evolves over time according to its current state, but also according to the states of the other cells in a defined neighborhood. When we simulate the evolution of an automaton, the future state of each cell is computed simultaneously according to the state of the cube cells at the current simulation time. With basic rules, it is possible to create a simplified model for natural behavior. The most common example is the Game of Life [START_REF] Gardner | Mathematical Games: The fantastic combinations of John Conway's new solitaire game "Life[END_REF]. This model, created in 1970 by John Conway, allows simulating bacteria reproduction on a 2D toric space. In this model, a cell can only have two states: dead or alive, and evolves according to the number of cells alive in the contacting neighborhood (named Moore's neighborhood). But cellular automata can't be reduced to Conway's Game of Life; the interested reader can consult [START_REF] Stephen | A new kind of Science[END_REF]. For instance, electronic circuits can be simulated with the WireWorld cellular automata [START_REF] Dewdney | The cellular automata programs that create wireworld, rugworld and other diversions, Computer Recreations[END_REF], and in biology, excitable medium, among others models, can also be simulated with cellular automata [START_REF] Ermentrout | Cellular automata approaches to biological modelling[END_REF]. Even if cellular automata are mostly very simple, large 3D models with an important number of iterations require a very long computing time. Moreover, in integrative biology, computation of cellular automata is often only a part of a more complex problem. Working on larger cubes can therefore improve the result sensitivity, and performing this result faster can speed up the global process. Our idea was to test cellular automaton implementation on GP-GPU (General Purpose Graphic Processing Unit)4 [START_REF] Luebke | GPGPU : General purpose computation on graphics hardware[END_REF]. This kind of processing unit was first designed to process graphics on a computer, but while the classical CPU computation performances evolution recently began to slow down, the GP-GPU continued to provide very significant speedup. Ten years ago, developers of high performance computing applications started to port scientific software from CPU to GP-GPU to make the most of it [START_REF] Trendall | General calculations using graphics hardware with application to interactive caustics[END_REF]. After the initial success, GPU manufacturers started to work on friendlier API for general purpose computation and we are now able to develop directly in languages which are close variants of the C language 56 . In the same way, other hardware accelerators like FPGA have been considered to speed up cellular automata computations [START_REF] Mike | Using FPGAs to Speed Up Cellular Automata Computations[END_REF]. The main difficulty lies in the memory manipulation since GP-GPU have various levels of memory with different performances. The GP-GPU global memory which can be accessed by any thread at any time has very important access latency. The shared memory available for each streaming multiprocessor inside a GP-GPU does not have such latency, but this memory is only shared by threads running on the same multiprocessor. In addition, the number of concurrent threads in a streaming multiprocessor is limited. Moreover, memory transfer between the host computer and the GP-GPU device can severely damage the global speedup if the computation time is not significant enough in comparison with the data transfer time. In this paper, we propose two cellular automata implementations on GP-GPU with different memory usage (Part 3). To understand the benefits of GP-GPU, we compare the results obtained with an Nvidia Tesla C1060 board to a sequential implementation on 2 kinds of CPU (Xeon Core 2 and Nehalem) (Part 4).

2

Accelerate cellular automata computation

3D Cellular automata

A simple heart model can be created using a cellular automaton. After selecting an appropriate neighborhood (a Moore cubic-shaped neighborhood with a range of one for example), we can simulate the propagation of an electric stimulation with 3 states:

A cell is in an "activated" state for an amount of predefined time. In this state, this cell stimulates its neighborhood cells which are in an "inactive" state. This state corresponds to cell depolarization. A cell is in a "refractory" state for a certain predefined time. It can't be stimulated by an "activated" cell. This state corresponds to cell repolarization. The rest of the time, a cell is in an "inactive" state.

But this heart model is far from perfect. A critical point is that the number of cells used will directly affect the model accuracy. If the whole heart is represented by a 10 3 cells cube, the accuracy will be low compared to the use of a cube with 100 3 cells. To compare both possibilities, a ventricle will be represented by a cube of 3 3 cells in the first case whereas in the second case we can use 30 3 cells (27 cells compared to 27000). In the second case the shape of the ventricle 3D model will be close to a real one. Getting a better accuracy cannot be reached only by a growth in cube size. Each time each dimension grows by a factor of 10, the total number of cells grows by a factor of 1000 (1000 cells compared to 1.000.000 cells in the second case with a 100 3 cube). Having more cells implies more computations and this significantly slows down the simulation performances. In addition, even if we assume that a 100 3 cells cube is a good dimension to represent a heart and that we have enough computing, it could be interesting to have a more representative model of the system. These improvements generally require a computation increase. For example, the state corresponding to the cell repolarization ("refractory") can be subdivided into two parts: a first one called the "absolute refractory state", where the cell can't be reactivated by an incoming impulse and a second, called the "relative refractory state" where it can. This small modification will only slightly increase computing power but more complex modifications can be considered with a wider impact on computing time.

One way or another, if the modeling technique retained is based on cellular automata, increasing the computation speed could be a good way to improve the model representation without damaging the global simulation speed and therefore achieve better accuracy.

GP-GPU utilization

In order to accelerate the computing time of 3D cellular automata simulation, we have tried a parallelization using a GP-GPU (General Purpose Graphical Processing Unit). GP-GPU is particularly powerful when used to process the same instruction set on an important number of independent data. This approach can be useful for cellular automata evolution if we consider the splitting of the cellular space in sub-cubes. However, neighborhood interactions are going to be a weighty problem for the program. To compute an iteration on a subcube, we need a fast access to cells' neighborhood, otherwise the new state of border cells can't be computed. In the next figure, the dark grey sub-cube will need the current state of every black cells present in other sub-cubes to compute the next state (a non-toric cube representation with a Moore cubic-shaped neighborhood with a range of one is used in this figure).

Figure 1:

Example of Moore cubic-shaped neighborhood in red with a range of one.

Problems induced by the current GP-GPU architectures

To correctly understand the consequences while programming with GP-GPU, it is important to realize that GP-GPU design is quite different from current CPU architectures. While a CPU possesses few cores, each of them allowing the execution of one thread at a time, a GP-GPU possesses a small number of streaming multiprocessors, each of them allowing the parallel execution of numerous threads, supporting vector computing in a SIMD approach (Single Instruction Multiple Data). For instance an Nvidia Tesla 10 (see Figure 2), will have 240 vector cores split in 30 streaming multiprocessors (SM) with 8 thread processors (SP Thread Processors) each 7 . Each thread processor can run four SIMD warps of 32 threads with the same control flow for different data leading to potentially 1024 threads on each of the thirty streaming multiprocessor.

With CUDA, all the threads needed to execute a kernel must be grouped in blocks and all these blocks must have the same, limited number of threads. All the threads of a block are executed simultaneously on the same multiprocessors and therefore can make use of its shared memory. To get the best results from GP-GPUs, we have to place data in shared memory. This memory is fast and managed by a streaming multiprocessor. The latency for accessing global memory is very high and we have to limit its access. Thus, dependant threads needing fast communications have to use the shared memory within the same streaming multiprocessor (SM). When mapping cellular automata to a GP-GPU architecture we will not be able to place all the threads on a single SM, and consequently we will have to communicate using more global memory accesses, implying a significant loss in performance. The difficulty lies in successfully managing memory copy from global to shared memory. Another important consideration is the memory transfer between the host and the GP-GPU which can be very significant if the following ratio: computation duration divided by the transfer duration, is too small.

GP-GPU Implementation

First implementation

The first implementation we tested doesn't use shared memory. Its goal is to use the GP-GPU computation capability to see if we can already obtain a speed-up of the global computation with a basic approach.

To make use of this GP-GPU computation capacity, an elementary thread is used to compute the new state of each cell. The algorithm is then kept simple and an excerpt of the code is presented below. In this version, we first copy the cell cube on the GP-GPU. Then, we run the kernel using a thread for each cube cell. Each thread can compute the new state of the cell it handles depending on the neighborhood state. Consequently, at the end of the last thread execution, all the cells are updated for an iteration. When all the iterations have been computed, we transfer the resulting cube back onto the CPU. This very simple algorithm has two major limiting operations. The first one is logically the copy of the initial cube from the CPU to the GP-GPU and the result cube from GPU to CPU. The second limit of this basic algorithm is almost masked in the previous code: it deals with memory access to the cells and their neighborhood.

For the first point, copying time costs will be very difficult to improve. A solution could be to take advantage of asynchronous memory transfer by dividing computation in multiple parts for example. But this solution would significantly affect the algorithm simplicity and reusability, and moreover it will not allow a significant global speed-up in most cases, particularly when we have a large number of iterations to compute. The more the iteration number will be important, the less the copy time costs will be significant in comparison with computing cost for all iterations.

The second point is related to global memory access. When we perform the Game of Life in three dimensions, it is necessary to know the neighborhood of each cell. This leads to a maximum of 27 cell states if we consider the current cell state. In this first version, all memory access is done in global memory with a very high access latency (between 400 and 600 clock cycles on a Tesla T10 for instance).

If we consider the handling of processing 2 cells at a time, we can notice that 2 neighboring cells share 18 cells in their common neighborhood. Thus we can obtain all their neighborhood with only 36 global memory access instead of 54. And results are even better if we increase the block size to small cubes: a 3 3 cube (27 cells) will need only 125 global memory access (the block including neighboring cells is a 5 3 cube) instead of accessing 729 cells if we consider the handling on a single cell basis (27 x 27). The following implementation tries to take advantage of this possibility.

Second implementation

To limit at minimum the global memory access, the code hereafter makes use of shared memory between threads of a same CUDA software block. Since the size of a software block is limited, it is impossible to share all the cell information between all the threads needed to handle a large cube. Each software block works on a part of the cube and each thread inside this block works on the generation of the new state of a single cell. Before computing an iteration, data required for a CUDA software b(lock must be loaded from global memory into shared memory. This is done in every software block. When the uploading is done, a synchronization of all the threads of a same block is necessary to ensure that all the data is correctly uploaded in shared memory. After this synchronization, the computing of the new state can be done in function of neighborhood states read in shared memory instead of global memory. The new state obtained for the local cube is written in global memory otherwise it would be lost after the end of the block computation. Code 2: Pseudo-code of the shared algorithm.

In this solution, the aim is the reduction of the global memory access but nothing has been done for the memory transfer between CPU and GP-GPU. With this approach we still have global memory access to load the shared memory and to update the global memory after an iteration. Even if there is a diminution in terms of global memory access, this approach results in an increase in the total number of memory access (global and shared) but most of them are shared memory with a final increase in performance.

To maximize the number of cells shared in a same block, the largest block possible has been used. Each block contains 512 threads, allowing the processing of an 8 3 size cube. To limit each thread access to the global memory, the state of each cube cell is loaded in shared memory. This choice implies that all threads handling the cells at the border of the 8 3 cube are not used to compute the next state. To obtain the next iteration state, only the threads processing the inner cube of 6 3 cells are considered.

Results

The two algorithms previously presented have been tested on personal computers, the main one using two Intel Nehalem CPUs (at 2.53 Ghz) with an NVidia Tesla C1060 board. To compare the benefits of hybrid computing, the CPU only algorithm computes for each cell the new state with a single core (a Core 2 Xeon and a Nehalem Xeon have been considered).

Memory transfer costs

Figure 3 confirms what has been said previously when explaining the basic algorithm: transfer costs between CPU and GPU are not significant anymore when we compute many iterations. In fact, for a constant cube size, the global simulation duration should be linear with the number of iterations; to this global computing time we naturally add two memory transfer costs for copying the initial cube from the CPU memory to the GPU global memory and for copying back the final result. In Figure 3, with a small 10 3 cube, the impact of this transfer time is important if we have a small number of iterations and becomes insignificant after 400 iterations.

Basic GPU implementation results

The first results deal with the basic implementation which uses only global memory.

Figure 5 shows a comparison between the pure CPU algorithm (Core2 Q9300 at 2.5GHz) and its hybrid implementation with a GP-GPU for a 20 3 cube (Tesla T10 -C1060). We see that the best results are obtained with a significant number of iterations. The performance improvement stops after reaching a threshold.

In Figure 5, we see that the sequential implementation using the CPU is always slower for a 20 3 cube when compared to the GP-GPU implementation. In Figure 6, we notice that when using a GP-GPU, the computing time of an iteration grows slowly with the cube size, whereas the computing time for an iteration according to the cube size shows a polynomial growth if we use the sequential CPU algorithm. Figure 7 presents the speed-up for different cube size depending on the number of iterations. For each cube size we have an upper limit; we also note that the speed-up first increases with cube size and stays approximately at the same level when the cube size reaches 80 3 and over. This last point can be seen more clearly on Figure 8 which represents the speed-up for 1000 iterations depending on the cube size. Now that the speed-up trend has been identified, we can discuss speed-up results. If we ignore cube sizes smaller than 80 3 , the average speed-up for 1000 iterations is 41x when compared to a single Q9300.

GPU implementation with shared memory results

The next implementation takes advantage of shared memory between threads of the same streaming multiprocessor to minimize global memory access.

Figure 9 shows the performance increase compared to the first implementation (using a 100 3 cube). It confirms that the two trends are really similar and also that the shared memory algorithm is better than the previous one. Figure 10 compares the speed-up for 1000 iterations with both implementations. Speed-ups are very similar for small cubes but as expected the shared memory algorithm has better speed-up for larger cubes (reaching 60x compared to 40x). With this algorithm the upper limit is reached later than with the previous algorithm (see Figure 11). These results can be generalized for larger cubes. Figure 12 shows speed-up evolution for 1000 iterations comparing the CPU algorithm for two different processors: the previous Core2 Q9300 but also a Nehalem 5500.

The first point we can notice is that the speed-up did not slow-down with the increase in cube size (the 800 3 cube size limit will be discussed in conclusion). The second point is that speed-up obtained with the GPU is far better when we compare it with a Q9300 Xeon processor (average speed-up of 62.8x) than in comparison with a Nehalem 5500 processor (average speed-up of 20.5x). This difference shows an impressive advance in CPU performances, at almost the same frequency, particularly if we consider that the Xeon Q9300 was still considered as a very good Intel processor at the beginning of year 2009.

Future research

In the case of 3D cellular automata, whatever the implementation, basic or more sophisticated, the use of hybrid computing shows interesting speed up. But improving this solution is still possible. Here are some possibilities which it could be interesting to explore.

First, it is possible to have all the threads of a block to work on computing the next status of a cell. Currently, all the threads corresponding to the block border are only used to transfer neighborhood status from global to shared memory. To make them work, it is necessary to make them load multiple cell status from global to shared memory. This solution, which has not been tested yet, does not give any guarantee of speed-up and it probably needs to be tested on different automata configurations. It would also be interesting to see if using another block size can improve the performance. At the moment a block of 512 threads is used to represent 8 3 cubes. But it could be interesting to test blocks of 256 threads to represent 6 3 cubes.

The second implementation was proposed to improve the memory access of the first version (which used only global memory). The fastest implementation uses mainly shared memory but it still needs to transfer data from the global memory to the shared memory and we think that this transfer could be done faster. In my implementation, each block of threads corresponds to a block of the cube cells in the example of Figure 13, the 125 threads of a block access 125 cube cells in global memory (the part framed in orange). When each thread accesses the state of a cell, it's clear that the global memory index will not be continuously accessed and with the CUDA architecture, we know that memory access can be done more efficiently by reading continuous data. A solution could be to reorganize the cube data to allow continuous global memory access for all the threads in a same block. This better memory access, implies that more computation would have to be done on each thread to compute the correct index in the cube but it would be interesting to see if it improves the performance. However this kind of dedicated implementation becomes quite obfuscated and difficult to maintain. Another solution to improve the results could be to create specific generation algorithm for each cellular automata algorithm by taking advantage of their particularities. For example, in the case of excitable medium, there are generally some important cube parts where no activities are present. It could be possible to locate these parts and therefore not do any computation because these parts won't evolve. But there is an important problem with this solution. It's logically the development cost. If many cellular automata algorithms are used, developing for each one a specific generation algorithm which takes advantage of its particularities could be an enormous task. And even if the number of cellular automata algorithms is limited, specific processing must be fast enough to not damage the global computation time. In the previous example, if searching for a large empty cube part is longer than computing a new generation on this part, performance increase can't be guaranteed.

Conclusion

We have implemented two cellular automata algorithms on a Tesla C1060 board. The basic principle is the same for both algorithms, a GPU thread is used to compute the new state of a cell. But to compute a new cell state, a thread has to read the states of the 27 neighbouring cells (if we consider a classic Moore neighborhood extended to a 3D automaton). The total number of memory accesses can imply a significant access time when relying on the GP-GPU global memory. A second implementation has been created to minimize the number of global memory accesses by using the fast shared memory present on a GP-GPU streaming multiprocessor. The states of all the cells needed by all the threads running on a same multiprocessor are transferred only once from global memory to shared memory. This is sufficient to compute a significant set of cells in parallel with efficient shared memory accesses.

We have tested both approaches and the results show that we have an interesting speed-up for any size of cube when enough iterations are computed. On small cubes, when a little number of iterations is considered, the transfer costs between the host and the GPU make the whole process significantly slower. When large cubes are considered (100 3 and above), the average speed-up for the first implementation compared to a sequential algorithm running on a Xeon Core 2 CPU at 2.5 GHz is around 41x (13x when compared to a Nehalem Xeon at 2.53 Ghz). If shared memory is used (second implementation), the speed-up grows to 65x when compared to a Xeon Core 2 CPU (and 20x a Nehalem Xeon).

The results observed show that even with the best CPUs such as Intel Nehalem, we obtain significant speedup. However, we have to consider the following limiting points. First, the CPU algorithm used for comparison with the GP-GPU algorithm is a sequential one. Now, most of the computers have multiple cores and taking the best of all of them will significantly increase the regular host performance. This point is not so important in integrative biology because cellular automata are only a part of the whole computation and the computation power of host cores will be used for other computations which have lower speed-up while computing on GP-GPU. The second point is the cube size. We have to take into account the current memory limit for Tesla GP-GPUs (4 GB). To compute a new iteration on a GP-GPU, we use two cubes, one handling the cells current statuses and the other to write the new statuses. A Tesla C1060 has 4GB of global memory, hence the size of the cube can't be too important. Two cubes with 900 3 cells can't be processed on this kind of GPU because it would need more than 5.8GB if 4 bytes are used to store a cell status (1000 3 can be considered if we store cell states on a single byte). The next Telsa architecture (Fermi T20) will significantly improve memory access performances with ECC memory, a configurable cache and it will also be able to address up to 1TB of memory. With this kind of architecture, a cube with 5000 3 cells will be handled on a single GP-GPU board, enabling a fine multi-scale modeling for simulated organs. Grids and clusters of GP-GPU will be considered to simulate interactions between organs [START_REF] Fan | GPU cluster for high performance computing[END_REF].

Figure 2 :Code 1 :

 21 Figure 2: Simplified architecture of an Nvidia Tesla 10.

Figure 3 :

 3 Figure 3: Average duration (in seconds) of one generation for a cube with 10 3 cells.The next figure shows the obvious importance of the cube size. For big cubes the transfer time measured became rapidly insignificant even with a small number of iterations. Now that the memory transfer problem has been analyzed, it is possible to start comparing CPU and GPU results.

Figure 4 :

 4 Figure 4: Average time for one iteration (100 replicates) for the following 3 tasks: allocation of 2 cubes (cube size), copy of a cube from CPU to GPU and copy of the final resulting cube (from GPU to CPU)

Figure 5 :

 5 Figure 5: Average duration of an iteration depending on the number of iterations for a 20 3 cube.

Figure 6 :

 6 Figure 6: Average duration of one generation for 1000 iterations depending on the cube size.

Figure 7 :

 7 Figure 7: Basic GPU implementation speed-up compared to a sequential CPU implementation for different cube sizes depending on the number of iteration.

Figure 8 :

 8 Figure 8: Speed-up evolution for 1000 iterations depending on the cube sizes (50 measures for each point).

Figure 9 :

 9 Figure 9: Average duration of an iteration depending on the number of iterations for a 100 3 cube (50 replicates).

Figure 10 :

 10 Figure 10: Comparison of speed-up evolution for 1000 iterations depending on the cube sizes (50 measures for each point).

Figure 11 :

 11 Figure 11: Shared memory GPU implementation speed-up for different cube sizes depending on the number of iterations.

Figure 12 :

 12 Figure 12: Speed-up evolution on large cube for 1000 iterations depending on the cube sizes.

Figure 13 :

 13 Figure 13: Improvement of data storage to increase global memory access.

For each iteration needed: for ( i = 0; i < nbIte; ++i )

	On CPU:
	// Initialize the cell grid.
	int	* matrice, * matriceGPU;
	initGrid( &matrice, size);
	cudaMalloc( ( void ** ) &matriceGPU,
			size * sizeof( int ) );
	// Copy the cell grid from the CPU to the GPU.
	cudaMemcpy(
			matriceGPU,
			matrice,
			size * sizeof( int ),
			cudaMemcpyHostToDevice
			);
	// 7	CUDA	Programming	Guide	v2.3	-	NVIDIA_CUDA_Programming_Guide_2.3.pdf:
	http://developer.download.nvidia.com/compute/cuda/2_3/toolkit/docs/NVIDIA_CUDA_Programming_Guide_2.3.pdf. Accession	date:
	20/01/2010.

Run the GPU kernel to compute iteration with // one thread for each cell. nextGeneration<<< nbBlock, nbThread, sharedSize >>>(…); […] On GPU kernel: // Upload from global memory to shared memory

	On CPU: […] int neighborSize = […]; int sharedSize = ( size / nbThread + neighborSize ) * sizeof ( int ); […] sharedMatrice[]; sharedMatrice[ threadIdx.x ] = matriceGPU[ … ]; __syncthreads(); // Compute the new state of the cell corresponding at // this index in function of its neighborhood states // present in shared memory. // int […]

Four-Dimensional Cellular Automata Acceleration Using GPGPU: http://eric_rollins.home.mindspring.com/gpgpu/index.html. Accession date: 20/01/2010.

What is CUDA ? -Official CUDA web site: http://www.nvidia.co.uk/object/cuda_what_is_uk.html. Accession date: 20/01/2010.

OpenCL Overview -Official OpenCL web site from Khronos : http://www.khronos.org/opencl/. Accession date: 20/01/2010.


Keywords: DEVS, template metaprogramming Résumé DEVS, métaprogrammation template

For several years, the DEVS community has been developing many tools for simulating DEVS models, ranging from local sequential to massively distributed and parallel simulation. In this paper, we present an innovative approach to local DEVS simulation. By using template metaprogramming, we developed the DEVS-MetaSimulator (DEVS-MS); instead of proposing one simulator meant to be used with every DEVS models, our library provides several metaclasses defining families of simulators. This way, each simulator instantiation is really specialized for a particular model. Doing so, we increase the detection of errors at compile-time, and we greatly reduce the execution time by removing several runtime computations that are instead performed by the compiler.

INTRODUCTION

Since its definition, the Discrete EVent System Specification (DEVS) [START_REF] Zeigler | Theory of Modelling and Simulation[END_REF] [START_REF] Zeigler | Theory of Modeling and Simulation: Integrating Discrete Event and Continuous Complex Dynamic Systems[END_REF] formalism has been studied by an important community of scientists. Many tools have been developed to create and simulate DEVS models [START_REF] Filippi | The JDEVS environmental modeling and simulation environment[END_REF] [START_REF] Kim | DEVSim++: C++ based Simulation with Hierarchical Modular DEVS Models[END_REF] [6] [START_REF] Quesnel | The Virtual Laboratory Environment -An Operational Framework for Multi-Modelling, simulation and analysis of complex dynamical systems[END_REF] [8] [START_REF] Wainer | CD++: a toolkit to develop DEVS models[END_REF].

However, due to the nature of the formalism, these applications often lack certain security guarantees, notably type safety. Indeed, messages exchanged between DEVS models can be of potentially any type. Therefore, the simulator must be able to deal with every possible type, most of the time resulting in breaches in the type system due to hazardous type casting, possibly generating hard to detect runtime issues. Other subtle errors can be introduced due to the extensive use of strings to represent the different model entities.

In this paper, we present a DEVS MetaSimulator (DEVS-MS) that resolves these issues by using a programming technique called template metaprogramming.

Template metaprogramming not only provides complete type safety and name checking, it can also be leveraged to carry out several computations at compile-time [START_REF] Abrahams | C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond[END_REF] [10] [START_REF] Veldhuizen | Using C++ template metaprograms[END_REF]. In this paper, we show that many simulation operations, usually made during execution, can be performed beforehand, during compilation, resulting in increased performance and robustness of the final simulation program at runtime.

DEVS FORMALISM

The Classic DEVS formalism, proposed by Zeigler in the mid-seventies [START_REF] Zeigler | Theory of Modelling and Simulation[END_REF], defines two kinds of models for representing a system. An atomic model is an entity holding a state and evolving via internal state changes and responses to external stimuli. After undergoing an internal event, the model generates an output event that is propagated to the outside. Formally, a DEVS atomic model is defined by a 7-uplet: M = ⟨ X, Y, S, ta, δ int , δ ext , λ ⟩ where:

-X is the set of inputs. Usually, X is decomposed into several sets representing model ports. For example, to model a lake that can receive a certain amount of pollutant and be repopulated with newly-hatched fishes (alevins), we could define the input set by X = { (pollutant, alevins) | pollutant ∈ ℝ, alevins ∈ ℕ } -Y is the set of outputs. It is defined similarly to X, using ports.

-S is the state set, containing all the possible characterizations of the system.

-ta is the time advance function. It determines how long the system stays in a given state before undergoing an internal transition and moving to another state. -δ int is the internal state transition function. It defines how the system evolves in an autonomous way.

δ ext is the external state transition function. It defines how the system reacts when stimulated by an external event. -λ is the output function. Invoked after each internal transition, it determines the events generated by the system, functions of the state it was in.

Atomic models can be combined to form coupled models, which can be in their turn put together, leading to a hierarchic model. A coupled model aggregates components, either atomic or coupled models, linked by their input and outputs ports; thus, the events generated by a model become stimuli for others. Formally, a DEVS coupled model is characterized by a structure:

N = ⟨ X, Y, D, {M d | d ∈ D}, {I d }, {Z i,d }, Select ⟩
where: -X is the set of input ports and values, in the manner of atomic models.

-Y is the set of output ports and values.

-D is the set of component names.

-For each d in D, M d is a DEVS model (coupled or atomic).

-I d is the influencer set of d, i.e. the names of the components that impact d, through their output ports.

-Z i,d is the coupling function. It maps N input ports to component input ports, component output ports to component input ports, and component output ports to N output ports. -Select is the tie-breaking function. Classic DEVS is fundamentally sequential. Therefore, when two models are supposed to undergo an internal transitionand consequently generate an event -, only one of them must be activated. This arbitrage is performed via the Select function.

The operational semantic of these models is defined by Zeigler [START_REF] Zeigler | Theory of Modeling and Simulation: Integrating Discrete Event and Continuous Complex Dynamic Systems[END_REF] through abstract simulators, meaning algorithms that correctly simulate DEVS models. The simulation software presented in this paper, DEVS-MS, implements these algorithms in an innovative way, using several metaprogramming techniques.

RATIONALE

The main idea behind DEVS-MS is to provide a framework where each coordinator and simulator 4 would be specialized for the model it handles. To illustrate this, we will consider the classic Switch Network coupled model, proposed by Zeigler in [START_REF] Zeigler | Theory of Modeling and Simulation: Integrating Discrete Event and Continuous Complex Dynamic Systems[END_REF] and described in Erreur ! Source du renvoi introuvable.. This model is composed of three components: a switch s that forwards its input alternatively to its "left" port and "right" port, and two processors p 1 and p 2 , which accept an input job, await some time and output the job. In our example, we will assume that jobs are represented by strings.

Now imagine that we could have one specialized simulator for each component: as presented in Code 1, we could end up with a much more secure and efficient simulator. In this code, which presents potential code snippets, we call sim_s, sim_p1 and sim_p2 the simulators for s, p1 and p2 respectively.

The simulator of s has two methods, one for receiving an event on the left port, and the other one for the right port. These member functions accept a string, based on the port types of Switch. Since the simulator is specialized for s, it can directly forward the events to the correct simulators, i.e. sim_p1 and sim_p2, relieving the coordinator from the burden of computing the couplings at runtime. 4 In the rest of the document, we will refer to simulators and coordinators as model handlers, or simply handlers. Similar things could be done in the coordinator of the network switch: for example, the Code 2 shows how we can process the model internal transitions when we have an a priori knowledge of the components and their priorities (i.e. the Select function). Here, p 1 always take priority over p 2 , which always take priority over s. The main idea behind TMP is to make the compiler execute certain operations during compilation through template instantiation. The most common use is code generation, notably in generic programming, where data structures and algorithms are written once and for all but can work with any type meeting a small set of requirements (e.g. supporting the dereferencing operator '*'). However, the template mechanism can be further leveraged to make the compiler perform several computations at compile-time instead of runtime. The canonical example is the factorial: as described in Code 3, we can use recursion and template specialization to compute the value of a factorial during compilation, reducing the execution time (but of course increasing the compilation time).

Code 3. Factorial computation using TMP

As a matter of fact, templates have been proven to be Turing complete [START_REF] Veldhuizen | Expression templates[END_REF]; however, the range of computations that can be practically written is narrower. Most of them deal with type manipulation, as we'll see in the next paragraph.

Boost libraries

Boost is a set of free peer-reviewed C++ libraries, most of them being highly regarded by the community. In order to facilitate metaprogramming, we extensively used four of them in our work: TypeTraits, MetaProgramming Library (MPL), Fusion and Preprocessor.

Type-traits

The type-traits library contains a set of convenience classes for manipulating types. Each of them encapsulates a specific trait from the C++ system; for example, there are classes for determining whether a type is an integral type, whether it is const, whether it has a trivial constructor, etc. The library also contains some classes for performing very simple type transformations, e.g. adding a reference or removing a pointer to a type.

The classes of that kind represent functions invocable at compile-time, and hence are called metafunctions. Calling a metafunction boils down to instantiating a class template and if need be getting the result by accessing a typedef type in it. The metafunctions proposed by the type-traits library are rather simple, but they are essential primitives 6 to create more complex frameworks, such as the MPL or the Fusion library.

MetaProgramming Library (MPL)

The MPL is a metaprogramming framework; in the manner of the Standard Template Library (STL), it provides the user with a set of sequences, algorithms and metafunctions for doing compile-time computation.

MPL sequences supply an easy way to create, transform and query compile-time sequences of types. For example, the following code defines a sequence containing the types int, float and char: typedef boost::mpl::vector<int, float, char> seq;

7
Since vector is a model of the Random Access Sequence concept, we can use the at_c metafunction to retrieve the nth element in the sequence, in amortized constant compilation time: mpl::at_c<seq, 1>::type f1 = 0.0f; // <=> float f2 = 0.0f;

Now, assume that we need to transform this sequence so that it contains only integral types. To do so, we can use the remove_if algorithm, a higher-order metafunction that removes all the elements in a sequence that satisfy a given predicate:

typedef mpl::remove_if< seq, // sequence mpl::not_< boost::is_integral<mpl::_> > // predicate >::type int_seq; // result //int_seq is equivalent to mpl::vector<int, char> MPL proposes many sequences (list, map, set…), metafunctions (at, begin, end…) and algorithms (find, copy_if, transform…). Most of them are very similar to their STL counterpart, from the way they are used to their complexity guarantee, except that instead of dynamically dealing with sequences of values at runtime, they statically deal with sequences of types at compile-time.

Fusion

As we explain above, MPL provides a set of powerful tools for performing operations on sequences of types, during compilation. However, at some point in time, we will need to map these types to values to make them useful in the runtime world. That's where Fusion comes into play: this library aims at uniting compile-time metaprogramming with runtime programming.

Concretely, the library provides a set of heterogeneous containers (tuples) that can hold elements with arbitrary different types, along with several functions and algorithms to operate on them. Each operation has its "meta" counterpart, which performs the same kind of computation at compile-time. For example, the begin function will return the first element of a sequence, while the metafunction result_of::begin will return the type of the first element in a sequence. However, the metaprogramming aspect is a bit more tedious in Fusion than in MPL; a common practice is to do all the pure type calculations using MPL, then instantiate Fusion sequences with the results of these calculations.

Preprocessor

Unlike the three libraries presented previously, the Preprocessor library does not provides tools for manipulating types, but a set of macros for making the C++ preprocessor generates repetitive code that would be hard and painful to come up with if it was to be written by hand.

A classic (andfor oncereally useful) example is overcoming the lack of variadic templates in C++.

Especially when doing metaprogramming, we are often confronted with the need to write a class or function template that can accept an arbitrary number of parameters. Take the mpl::vector class evoked before: the number of template parameters it should be able to deal with ranges from zero to theoretically infinity. One solution would be to define the class template with a great number of defaulted template parameters. However, this is not always practical or even possible. Consequently, we end up having to define multiple classes or functions that are almost identical, which is a tedious and error-prone task.

The Preprocessor library tackles this issue by proposing a set of macros for performing repetitions (repeatedly invoking a given macro with an incrementing argumentthink for loop), arithmetic, logical and comparison operations and conditional macro invocation. It even defines several data types for storing sequences of macro arguments.

IMPROVING MODEL DEVELOPMENT AND SECURITY CHECKS

The first improvements that can be achieved using TMP concerns model implementation. The aim is to prevent the modeler from making certain accidental mistakes by detecting them as soon as possible in the development cycle, i.e. during compilation.

Ports compatibility (type safety)

The DEVS formalism is very generic, and does not define any constraints on the type of model inputs and outputs. Consequently, the value associated with some event can be of potentially any type. The simulator must be able to deal with this heterogeneity and provide a generic way of handling messages. To achieve this, the classic polymorphic approach is to require a common base class for all the types used in messages. However, this solution has many drawbacks.

First of all, this constraint prevents the modeler from using certain types, like primitive types that are not classes (e.g. int, char... in Java and C++) or existing classes that cannot be modified to integrate the hierarchy (e.g. C++ string). Often, wrappers are proposed to encapsulate this kind of value into objects: in Java, they are supported directly by the language, in other languages they are written by the library authors. However, using these wrappers induces a small runtime overhead due to the conversions, but more importantly they do not solve the true issue behind the polymorphic solution: type-safety.

Indeed, the use of a "universal" base class completely defeats the purpose of the language type system. After being upcasted, the values become of little use; they need to be downcasted back to their original type before being really exploited. If the cast is not correct, a runtime error will be produced. This can happen when coupled ports are not compatible, for example coupling a model outputting a string with a model accepting an integer. These errors can be due to a mistake from the modeler, but can also happen when using imported models that can undergo future changes, possibly breaking their interface.

The solution we developed uses TMP to achieve static typing of messages. To do so, we created a class template Message, parameterized by an MPL sequence of types 8 . When constructed, objects of this class instantiate a Fusion sequence that will hold the values carried out by the message. This way, type information is never lost and message exchanges are guaranteed to be type-safe. The compiler finds the inconsistencies and produces errors messages underlining the incompatibility between the incriminated ports. In case of problems, the modeler is warned much sooner in the development process, without having to run test suites to explore every possible case (or worse, perform no tests and stumble upon strange errors at a later point in time).

Names checking

Another improvement of our DEVS simulator concerns names. In Classic and Parallel DEVS, names used throughout a model are not meant to be modified during simulation. Once a port or a component has been identified, its designation will not change. Yet, names are usually represented by strings, which are runtime values. Once again, this leads to additional computations, notably string comparisons; but more important, the compiler has no way to check whether the names are consistent in the entire model. If the modeler makes a mistake when reusing a name, for example when defining the couplings or when writing lambda functions, he will not know until he executes the simulation. If he is lucky, the error will show up pretty soon, but if the mistake was made in a part of the model which is scarcely used, it can keep buried a long time before being revealed and corrected.

Once again, this can be solved using TMP. The main idea is to use types to denote ports and components instead of using strings. For example, if a model contains a port named "in", we will create a type in:

struct in;

From now on, each time we will need to refer to this port, we will use this type. This leads to some changes in the Message class presented above: instead of being parameterized by a sequence of types, it will accept a sequence of pairs. Each pair contains the name of the port (as a type), and the type of value it accepts/generates. To facilitate the use of this information, we store the ports in an MPL map whose key is the port name and value the type of the port. Runtime values are held in a Fusion map which statically associates port names with their corresponding value.

The C++ Code 4 presents a sample member function of the Message class. We followed the example of the Fusion library by proposing each functions in two flavors: compile-time and runtime. Here, the compile-time version of GetValueOnPort is a metafunction returning the type held on a given port, whereas the runtime version returns the actual value.

An important fact to stress is that accessing values this way is equivalent to accessing data members of the class, all the map operations are performed at compile-time so no runtime overhead occurs.

As we will see in section 6, using types for representing names not only provides a better model verification, it also opens the doors to many important optimizations. Another aside benefit concerns the ease of development: instead of having to remember every single name used in the model, the user can rely on its IDE code completion tool.

template <typename

Additional checks

Still in the perspective of preventing accidental mistakes, we implemented several other consistency checks. For example, when a simulator receives a message from its atomic model, it checks that this message is consistent with the output ports defined for the model. We achieve this by requiring that ports (i.e. pairs of name and type) be explicitly specified in the model through a typedef, along other information such as the type of the model, using a tag (atomic or coupled). To release the modeler from the burden of writing himself all these typedefs, we created a class template for coupled and atomic model. Therefore, the user only has to derive his model from one of these templates with the proper parameters, meaning the sequence of input and output ports, to automatically inherit the required typedefs. With this information, we can statically (at compile-time) assert that messages outputted are consistent with the definition of the model ports.

We also ensure that simulators and coordinators are properly used by "specializing" them according to the inputs and outputs of models. Concretely, we remove functions that shouldn't be called in a correct simulation. Thus, the handler of a model with no input ports will not have any function for handling input messages; similarly, if an atomic model has no outputs ports, its simulator will not expose a function for receiving internal events, nor call the model lambda function.

IMPROVING SIMULATION PERFORMANCES

We saw in the previous section that using TMP, we increased the robustness of the simulation by making the compiler performs several consistency checks. In the same time, we also introduced optimizations, for example removing the need for all the type casting. We will see now that we can make the most of the metaprogramming approach to further increase simulation performances, notably by computing several simulation operations at compile-time.

Static polymorphism

The first impact on performances is a direct consequence of the developments presented above. As we said, the only requirements on models are providing the needed typedefs, and of course the mandatory members functions required by the formalism. Therefore, models can be completely independent classes, and do not necessarily share a common base class. In fact, even if the modeler decides to inherit from the coupled or atomic model classes proposed in the library, they will still be unrelated because the instantiated classes will be different. As a consequence, simulators and coordinators need to be parameterized by the type of the model they handle. This Each function has two kinds of parameters: compile-time parameters, which are enclosed in brackets, and runtime parameters, between parentheses. To filter the imminent components, we call the recursive_filter function with an empty sequence, which will contains the names of the candidate components, and an iterator pointing to the beginning of the components map (components are stored in a Fusion map that associates their name with the actual instance). This function is specialized to handle the end of recursion: if we reach the end of the components list, then we "statically return" the list of candidate components. Otherwise, we test whether the current component is ready to perform its next transition. In this case, we recursively call recursive_filter with the input sequence of candidates, completed with the component name, and the incremented iterator. If not, we don't modify the sequence of imminent components and only increment the iterator. The star_message_continuation function "statically receive" the list of candidates and select the correct component, which will be activated at runtime.

Forwarding messages

The select function is not the only constant piece of information we can exploit. Indeed, except in Dynamic Structure DEVS, the couplings between components remains the same during the entire simulation. Consequently, we can check couplings and forward messages to the influenced components by means of the compiler.

In DEVS-MS, couplings are defined for a coupled model via an MPL sequence. Each coupling in this list is a pair containing two pairs: the source component name and port, and the destination component name and port. We statically transform this sequence into an MPL map, in order to facilitate the look-up when forwarding messages.

When receiving an event, the coordinator can deduce from its type the set of influenced components, based on the ports list of the message. It can also carry out the port mapping between output and input ports, always at compile-time. Therefore, the only operations that remain to be done dynamically are sending the messages to the corresponding simulators. Assuming some aggressive inlining from the compiler, we can end up with simulators or even componentsexchanging messages directly, without going up the coordinators hierarchy, as described in section 3.

Filtering input events

As we saw previously, messages encapsulate information about the ports they concern. In the case of an input event, the message contains the name of the input port he is sent to. We can make use of this knowledge to perform static filtering of messages in the destination model. Because they are instantiated classes, messages heading to different ports are of different types. Thanks to this property, the modeler can overload the model external transition function to segregate the different cases: Here, current compilers are smart enough to detect that only one branch will always be true, and simply remove the test and the other branches.

Conclusion and future works

We proposed an innovative approach to DEVS simulation. By using a technique called Template MetaProgramming, we developed a simulator that makes extensive use of the compiler for two tasks: removing many potential bugs as early as possible in the model development cycle, and increasing simulation performances by transferring certain computations from runtime to compile-time.

To avoid accidental mistakes, we made the compiler performs several consistency checks over the model, most notably verifying the type compatibility of coupled ports, and ensuring the consistency of ports and components names. These kinds of errors are now caught by the compiler instead of producing bugs, potentially hard to find and correct.

Regarding performances, we took advantage of the constant nature of certain model parts to carry out some simulation operations at compile-time. These include arbitrating the occurrence of simultaneous events through the select function, checking couplings and forwarding messages to influenced components, and filtering input messages by port name.

Our future works will consist primarily in measuring the performance gain on a set of significant examples to compare DEVS-MS with other existing simulators. Then, we may develop versions supporting other formalisms, notably Parallel DEVS. Further developments could also be done to optimize DEVS-MS with regard to compilation time and executable size.

Figure 1 .

 1 Figure 1. Switch network coupled model.

  Factorial<5>::result; // int i = 120;

Code 4 :

 4 Message<mpl::vector<mpl::pair<in, float> > > m; float f = m.getValueOnPort<in>(); Small part of the Message class template.

  void delta_ext(const Message<Job> & x) {...} void delta_ext(const Message<On_off> & x) {...}The choice of the correct function will be made through overload resolution by the compiler.In fact, compile-time filtering will happen even if the modeler decides to write a unique external transition function with a nested if:template <typename Msg> void delta_ext(const Msg & x) { if ( x.hasValueOnPort<Job>() ) ... else if ( x.hasValueOnPort<On_off>() ) ... }

Code 1: General switch model and specialized switch simulator.

	class Switch
	{
	...
	// In this case, sim would refer to sim_s
	void lambda()
	{
	if (...)
	sim.putOnLeft(output);
	else
	sim.putOnRight(output);
	}
	string output;
	};
	class SimulatorSwitch_s
	{
	...
	void putOnLeft(string output)
	{
	sim_p1.putOnJob_in(output);
	}
	void putOnRight(string output)
	{
	sim_p2.putOnJob_in(output);
	}
	};

Some other languages support or are extended to support TMP; however, C++ remains in this domain the most developed, documented and used one.

Actually, the C++ committee found them useful enough to integrate them into the next ISO C++ standard (C++0x).

In the rest of the document, we'll assume the namespace boost::mpl has been aliased to mpl., and boost::fusion to fusion.

In Classic DEVS, a message represents only one event on one port. However, we wanted to be able to reuse the Message class for Parallel DEVS; this is why it accepts a sequence of types instead of a single one.

Inline expansion, or inlining, is a compiler optimization where a function call is replaced by the actual body of the function, avoiding several stack operations and making possible further optimizations.

Code 2: Specialized switch network coordinator.

Existing DEVS simulators, whether they are written in C++ [START_REF] Kim | DEVSim++: C++ based Simulation with Hierarchical Modular DEVS Models[END_REF] [7] [START_REF] Wainer | CD++: a toolkit to develop DEVS models[END_REF], in Java [START_REF] Filippi | The JDEVS environmental modeling and simulation environment[END_REF] [START_REF] Sarjoughian | DEVSJAVA: Basis for a DEVS-based collaborative M&S environment[END_REF] or in Python [START_REF] Bolduc | The modelling and simulation package PythonDEVS for classical hierarchical DEVS[END_REF], propose "one-fits-all" coordinator and simulator. The only exception is the ADEVS simulator [START_REF] Nutaro | ADEVS website[END_REF], where models are parameterized by their input and output types; thus, simulators and coordinators know the type of message they should receive.

In DEVS-MS, we provide an endless number of specialized model handlers. Indeed, we developed several metaclasses that define entire families of simulators and coordinators. When such a meta-class is instantiated with a particular model, the compiler generates a specialized version of the corresponding handler, along the lines of the code presented above. To achieve this, we use some metaprogramming techniques that are described in the next section.

METAPROGRAMMING

Presentation

The simplest definition of a metaprogram is "a program that creates or manipulates one or more other programs". Very broad, this definition embraces many different things, such as reflection, runtime expression evaluation or code generation. In our work, we used a particular technique called Template MetaProgramming (TMP hereafter) [START_REF] Abrahams | C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond[END_REF] [2] [10] [START_REF] Veldhuizen | Using C++ template metaprograms[END_REF], and more particularly C++ TMP [START_REF] Kim | DEVSim++: C++ based Simulation with Hierarchical Modular DEVS Models[END_REF] .

method is called static polymorphism, in opposition with the more classic dynamic polymorphism built-in all object-oriented languages.

Using static polymorphism in our simulation framework induces several advantages: first, the handlers obtain useful compile-time information that will be later exploited; second, no more virtual function calls are needed. Doing so, we removed a layer of indirection for calling functions and allowed the compiler to apply inline expansion 9 where it sees fit, both consequences leading to a decrease of runtime overhead.

Computing the select function

In Classic DEVS, the Select function of coupled models is defined once and for all and thus can be encoded in a compile-time constant. We represent the select function by an ordered set containing ordered sets of component names. When several components should undergo their internal transition at the same time, we look up in the Select set the first list including all the candidate names. The first element of this list has the highest priority and will be activated.

Obviously, the set of imminent components need to be computed at runtime since it depends on their time of next event. On the other hand, the choice of the component to activate can be made by the compiler. We achieve this by intertwining compile-time and runtime parameters. This is exposed in the pseudo-code Code 5.


Luc Touraille
email: touraille@isima.fr

Mamadou K Traoré
email: traore@isima.fr

David R C Hill

SimStudio : une Infrastructure pour la Modélisation, la Simulation et l'Analyse de Systèmes Dynamiques Complexes

Keywords: Modeling & Simulation, interoperability, framework, DEVS, MDE Résumé Modélisation & Simulation, interopérabilité, cadriciel, DEVS, IDM

SimStudio is a Modeling & Simulation framework based on the DEVS formalism (Discrete EVent Systems Specification). SimStudio architecture aims at integrating in a single platform tools for modeling, simulation, analysis and collaboration, by proposing model transformation features (code generation, among others) in order to smooth the modeling and simulation cycle. To achieve this, SimStudio is built upon the DEVS formalism, recognize by many as a "universal" simulation formalism, and upon a unifying language for representing DEVS models. Models are at the heart of the infrastructure we propose, in line with the Model-Driven Engineering (MDE) approach, at the boundary between software engineering and simulation.

Introduction

Les scientifiques français s'accordent sur un certain nombre de difficultés que rencontre le domaine de la Modélisation et de la Simulation (M&S) [CGTI 2005[START_REF] Gsat | Enquête sur les frontières de la simulation numérique en France. La situation en France et dans le monde. Diagnostic et propositions[END_REF]]. Celles-ci portent sur plusieurs points :

-Le manque de collaborations interdisciplinaires. En effet, les équipes de modélisation et les informaticiens paraissent souvent travailler en parallèle et non en collaboration, ce qui mène d'un côté à des logiciels peu aboutis et rarement robustes, et de l'autre à des logiciels coupés de la réalité du métier, ou trop peu diffusés.

-La simulation de modèles de plus en plus compliqués, soit parce qu'ils approchent de plus en plus la complexité du réel (multi-échelles, niveau de granularité plus fin), soit parce qu'ils intègrent plus d'aspects que par le passé.

-Le manque en grands systèmes de M&S pérennes. Il existe à ce jour peu de suites logicielles complètes, stables et reconnues pour la réalisation d'un projet de M&S.

Le projet SimStudio [START_REF] Traore | SimStudio: a Next Generation Modeling and Simulation Framework[END_REF]] a pour objet la conception d'un atelier logiciel qui réponde en partie à ces problématiques. Cette plate-forme autorise la conception de modèles de façon collaborative via des interfaces web, en ayant accès à des bibliothèques de modèles développés par la communauté. Grâce à des transformations de modèles, et notamment à des générations de code, la création de solutions opérationnelles est facilitée. SimStudio exploite le potentiel de l'ingénierie des modèles et du formalisme DEVS (Discrete EVent System Specification), présenté en section 2. Plusieurs travaux ont déjà été réalisés dans cette optique, et sont évoqués en section 3. Outre les composants de modélisation et de simulation, l'architecture SimStudio est également conçue pour inclure des fonctionnalités d'analyse et de visualisation. Son architecture, exposée en section 4, est basée sur un système de plugins qui permettra l'ajout de modules et l'intégration d'outils existants, via des transformations de modèles. Les communications entre ces différents modules se feront grâce à un langage de représentation des modèles intitulé DML (DEVS Markup Language) [START_REF] Touraille | A Markup Language for the Storage, Retrieval, Sharing and Interoperability of DEVS Models[END_REF], dont nous donnons un aperçu en section 5.

Le formalisme DEVS

Basé sur la théorie des systèmes, le formalisme DEVS [START_REF] Zeigler | Theory of Modeling and Simulation: Integrating Discrete Event and Continuous Complex Dynamic Systems[END_REF]] fournit une base solide pour la modélisation et la simulation des systèmes à évènements discrets, mais également pour l'intégration de modèles hétérogènes. Il en existe de nombreuses variantes, mais seules deux sont fondamentales : Classic DEVS (C-DEVS) et Parallel DEVS (P-DEVS). Ces deux formalismes décrivent la même classe de systèmes ; nous concentrons nos travaux sur P-DEVS car il permet plus facilement de paralléliser les simulations que C-DEVS, qui est totalement séquentiel. DEVS a été utilisé depuis plusieurs années dans de nombreuses applications et nous l'avons appliqué à des domaines très différents [START_REF] Kim | Modeling and Simulation of Supply Chain Management Based on DEVS and CORBA Framework[END_REF]] [Muzy et al. 2005] [Farooq et al. 2007] [Innocenti et al. 2009].

DEVS permet de représenter un système de deux façons [START_REF] Zeigler | Theory of Modeling and Simulation: Integrating Discrete Event and Continuous Complex Dynamic Systems[END_REF]]. La première consiste à concevoir un modèle atomique, qui décrit le système comme une entité possédant un état (S), un comportement autonome, via des changements d'état internes (δ int ) et un processus de gestion du temps (ta), une réactivité par rapport à l'environnement (δ ext ), et un mécanisme de génération d'évènements (λ). Les communications avec l'environnement se font via des ports d'entrées (X) et des ports de sorties (Y), qui reçoivent ou génèrent des évènements auxquels sont associés des valeurs. La deuxième façon de spécifier un système avec DEVS est de définir un modèle couplé. Celui-ci possède des ports d'entrées et de sorties, et agrège des composants, qui peuvent être soit des modèles atomiques, soit eux-mêmes des modèles couplés. Ces composants sont reliés par leurs ports de sortie et d'entrée (couplages) ; ainsi, les évènements générés par un modèle deviennent des stimuli pour d'autres.

La force de DEVS réside dans son pouvoir d'expression. En effet, il a été construit de façon incrémentale à partir de spécifications minimales, en se plaçant à chaque étape à un niveau d'abstraction apportant des connaissances supplémentaires, tout en démontrant les morphismes avec les niveaux inférieurs. On peut donc prouver [START_REF] Zeigler | Theory of Modeling and Simulation: Integrating Discrete Event and Continuous Complex Dynamic Systems[END_REF]] qu'il existe un modèle DEVS pour tout système à évènements discrets. Mais on peut aller plus loin ; en effet, le protocole DEVS peut être utilisé comme simulateur « universel », permettant le couplage de modèles décrits dans des formalismes et selon des paradigmes hétérogènes. Deux méthodes existent pour intégrer un modèle non-DEVS dans le cadre DEVS : la co-simulation et la transformation de modèles [Schmidt 2006]. Cette dernière consiste à transformer des modèles non-DEVS vers le formalisme DEVS, afin d'obtenir une spécification des modèles dans un langage uniforme. Vangheluwe représente les différentes transformations possibles à l'aide d'un arbre de transformation de formalismes (Formalism Transformation Graph, FTG) [Vangheluwe 2000].

Travaux apparentés

Les simulateurs DEVS étant précisément définis par le formalisme, leur implémentation est assez simple. On peut par conséquent trouver de nombreuses bibliothèques de simulation DEVS. Parmi celles-ci, DEVSJava [START_REF] Devsjava | ACIMS software site[END_REF] propose plusieurs outils, par exemple une interface graphique de modélisation. Cependant, DEVSJava ne prend pas encore en compte le cycle complet de M&S, et reste spécifique à DEVS. Dans le même ordre d'idées, JDevs [START_REF] Filippi | The JDEVS hybrid modelling and simulation environment[END_REF] inclut un module graphique d'édition de modèles, ainsi qu'un simulateur DEVS et deux outils de visualisation 2D et 3D.

En ce qui concerne l'intégration de modèles hétérogènes, on peut distinguer deux approches :

-Une approche centrée sur les modèles, qui consiste à standardiser la représentation des modèles afin de pouvoir les échanger et les composer facilement, comme nous l'avons fait avec DML. Plusieurs travaux avaient déjà été entrepris dans cette direction, mais étaient soit trop restrictif pour profiter pleinement du pouvoir d'expression de DEVS [START_REF] Risco-Martín | A W3C XML schema for DEVS scenarios[END_REF]] [Janoušek et al. 2006], soit spécifique à un langage de programmation donné [Mittal et al. 2007a].

-Une approche centrée sur les simulateurs (co-simulation), dans laquelle ce ne sont pas les modèles qui sont standardisés mais les communications inter-simulateurs. Ce domaine a été exploré par le Department of Defense américain, qui a proposé HLA (High Level Architecture) [START_REF] Hla | IEEE Standard for Modeling and Simulation (M&S) High Level Architecture (HLA) -Framework and Rules[END_REF]] comme solution au problème d'interopérabilité. Cette solution semble très utilisée pour les simulations militaires qui font intervenir des simulateurs hétérogènes et distribués géographiquement, mais nécessite un processus de mise en oeuvre relativement lourd. Une approche plus souple, utilisant une architecture orientée service et plus précisément des services web, peut être trouvée dans [Mittal et al. 2007b].

Le projet Virtual Laboratory Environment (VLE) [START_REF] Quesnel | The Virtual Laboratory Environment -An Operational Framework for Multi-Modelling, simulation and analysis of complex dynamical systems[END_REF]] s'inscrit lui aussi dans cette optique de multi-modélisation, en permettant à l'utilisateur de spécifier ses modèles selon différentes variantes de DEVS (Cell-DEVS, DS-DEVS…) et de les coupler via le principe de co-simulation. Il comporte des modules de conception et de visualisation graphiques. Cependant, les aspects collaboration et accès aux ressources de calcul intensif ne sont pas pris en compte, et la plate-forme reste spécifique DEVS.

James II [START_REF] Himmelspach | Konzeption, Realisierung und Verwendung eines allgemeinen Modellierungs-, Simulations-und Experimentiersystems -Entwicklung und Evaluation effizienter Simulationsalgorithmen[END_REF]] est un cadriciel de M&S générique qui accueille des plugins définissant des formalismes et des simulateurs, sous forme de classes Java. Cependant, à notre connaissance, cette plate-forme ne permet pas de coupler des modèles définis dans des formalismes hétérogènes. D'autre part, les plugins sont très fortement couplés à l'architecture, limitant l'intégration aisée d'outils existants.

Enfin, dans [START_REF] Posse | Generation of DEVS Modelling & Simulation Environments[END_REF]], les auteurs définissent un métamodèle DEVS à l'aide de l'outil AToM 3 [De Lara et al. 2002], une plate-forme de métamodélisation et de transformation de modèles. Ils génèrent alors un environnement graphique de modélisation DEVS, et spécifient également les transformations nécessaires pour générer du code Python utilisable dans le simulateur PythonDEVS.

Architecture SimStudio

Basé sur le formalisme DEVS, le cadriciel SimStudio vise à proposer un environnement web complet de Modélisation & Simulation, qui assiste le modélisateur depuis la conception de son modèle jusqu'à l'analyse des résultats. Afin de pouvoir incorporer les outils existants et ajouter aisément de nouvelles fonctionnalités, SimStudio utilise une architecture logicielle modulaire à base de plugins. La plate-forme est constituée d'un noyau de simulation, sur lequel viennent se greffer des composants logiciels qui se déclinent selon plusieurs axes (Erreur ! Source du renvoi introuvable.) :

-Un axe Modélisation, qui regroupe les outils graphiques et textuels pour la conception de modèles. On peut également avoir des modules « adaptateurs », qui sont chargés de transformer une spécification de modèle fourni par un outil existant en un format compréhensible par SimStudio.

-Un axe Analyse, dans lequel on peut trouver des outils d'analyse formelle, d'analyse statistique, etc.

-Un axe Visualisation, gérant l'affichage des résultats de simulation sous forme de graphiques, d'animations, mais pouvant également fournir des fonctionnalités de réalité virtuelle, en laissant l'utilisateur agir sur le déroulement de la simulation.

-Un axe de Gestion, qui fournit les services transversaux tels que la gestion des utilisateurs, le stockage et la recherche de modèles, l'installation de nouveaux plugins, etc.

Le rôle du noyau de simulation est de générer automatiquement une solution opérationnelle à partir de la spécification d'un modèle, puis de gérer son exécution sur différents types de plates-formes, selon le choix de l'utilisateur. Cela peut aller d'une simple exécution sur un serveur ou en local à une distribution sur grille de calcul ou sur cluster.

Cependant, pour que tous ces modules puissent communiquer entre eux, SimStudio doit se baser sur un langage commun ; nous pensons que le formalisme DEVS est l'outil le plus adapté pour remplir ce rôle, en raison de sa puissance d'expression. En effet, l'utilisation de DEVS comme dénominateur commun apporte deux avantages très importants :

-La possibilité de coupler des modèles hétérogènes, qui peuvent utiliser des paradigmes différents (continu/discret), des formalismes différents (équations différentielles, réseaux de Petri, automates cellulaires,...), des niveaux d'abstraction différents, etc.

-L'accès à une sémantique opérationnelle parfaitement définie. DEVS sépare strictement modèles et simulateurs. Par conséquent, un simulateur DEVS est « universel », dans le sens où il peut simuler tout modèle DEVS. En mettant à la disposition des modélisateurs les transformations nécessaires pour convertir un modèle en modèle DEVS, et l'accès à un simulateur performant et configurable, on évite les efforts d'implémentation sans cesse renouvelés : l'utilisateur peut se concentrer sur la modélisation, sans se soucier de l'aspect simulation.

Un pré-requis indispensable est par conséquent la définition d'un métamodèle DEVS qui soit assez générique pour supporter les modèles existants et assez flexible pour être utilisable en pratique. Ce dernier permettra une approche d'Ingénierie Dirigée par les Modèles, dans laquelle la définition de nouveaux plugins (à l'exception des modules de gestion) sera réduite à l'écriture de transformations depuis (resp. vers) un modèle DEVS, conforme au métamodèle, vers (resp. depuis) d'autres modèles, représentant autant de vues sur le même système étudié.

Dans la section suivante, nous présentons le métamodèle DEVS que nous avons développé, intitulé DML (DEVS Markup Language).

DML, un métamodèle DEVS unificateur

L'architecture SimStudio est basée sur l'utilisation d'une représentation standardisée de tous les aspects des modèles DEVS, définie par un métamodèle. Ce dernier doit prendre en compte les différents cas d'utilisation possibles, et doit donc spécifier les éléments suivants :

-La structure des modèles DEVS, c'est-à-dire les variables d'états, les ports d'entrée et de sorties et les couplages.

Figure 1. Vue d'ensemble de l'architecture SimStudio

-La dynamique des modèles atomiques, décrite par les différentes fonctions de transitions (interne, externe et confluente).

-Le paramétrage et l'initialisation des modèles.

-Le comportement des modèles, correspondant à des résultats de simulation, sous forme de trajectoires.

-Éventuellement des attributs graphiques, pour la visualisation des modèles et des résultats.

D'autre part, pour remplir sa fonction unificatrice, ce métamodèle doit définir une famille de modèles indépendants de toute plate-forme [START_REF] Bézivin | MDA at the Age of Seven: Past, Present and Future[END_REF], rejoignant le principe de Platform-Independent Model (PIM) du MDA (Model Driven Architecture de l'Object Management Group). En effet, les modélisateurs doivent disposer d'un « langage commun » pour s'échanger et faire interagir leurs modèles, qui peuvent avoir été développés pour des simulateurs et dans des langages de programmation différents. La principale difficulté réside donc dans la représentation de la dynamique des modèles : comment décrire un algorithme indépendamment de tout langage de programmation, sans pour autant priver le modélisateur de toutes les bibliothèques et fonctionnalités propres à ces langages ? Avant de répondre à cette question, nous devons choisir le métamétamodèle auquel se conformera DML. Pour des raisons évidentes de standardisation et d'interopérabilité, nous effectuons les échanges entre les composants de SimStudio via des documents XML (eXtensible Markup Language) [START_REF] Abiteboul | Data on the Web: From Relational to Semistructured Data and XML[END_REF]]. Dans un but de prototypage rapide, nous avons donc spécifié notre métamodèle DEVS à l'aide de XML Schema, en raison de sa facilité de mise en oeuvre et des nombreux outils existants, notamment pour la réalisation de transformations, entre autres grâce au langage Extensible Stylesheet Language Transformation (XSLT). Dans un second temps, il pourrait être intéressant d'utiliser un standard de métamodélisation plus « générique », par exemple le MetaObject Facility (MOF) [START_REF] Bézivin | MDA at the Age of Seven: Past, Present and Future[END_REF]] en association avec le standard XML Metadata Interchange (XMI).

Dans les sous-sections suivantes, nous présentons plus précisément DML selon les différents axes évoqués précédemment.

Structure des modèles

La structure des modèles DEVS repose sur la notion d'ensemble ; cette notion est facilement capturée en XML par le concept de type, largement utilisé en programmation. Les ports d'entrée et de sortie, les variables d'état et les paramètres sont donc représentés par des instances de types, qui peuvent être :

intrinsèques, c'est-à-dire supportés par défaut, comme les entiers, les flottants ou les chaînes de caractères ; définis par l'utilisateur, à l'aide de schémas XML. Ce dernier peut ainsi créer ses propres types afin de les utiliser dans ses modèles. Les schémas XML peuvent aussi être générés à partir de classes ou structures, dans le cas où le modèle fait l'objet de rétro-ingénierie depuis un langage de programmation ; dépendants des bibliothèques. En effet, dans des applications réelles, le modélisateur peut avoir besoin d'utiliser certains types définis dans des bibliothèques d'un langage donné. Pour trouver un compromis entre l'indépendance vis-à-vis de la plate-forme et la liberté nécessaire au modélisateur, nous proposons une notion proche de celle de type abstrait [START_REF] Liskov | Programming with abstract data types[END_REF]

Représentation des algorithmes

L'approche utilisée pour représenter la dynamique des modèles se base sur des principes similaires. En effet, nous avons besoin de représenter la logique des fonctions du modèle de façon indépendante de la plate-forme, tout en laissant le modélisateur libre d'utiliser les fonctionnalités propres à un langage ou à une bibliothèque. Par conséquent, nous utilisons pour décrire ces fonctions un mélange de code générique, qui possède une correspondance directe dans les langages impératifs existants, et de fragments de code abstraits, qui doivent être implémentés pour chaque langage cible (cf. Code 4).

Dans cet exemple, on teste la valeur de la variable sigma, puis, si elle est égale à zéro, on ajoute un élément dans le buffer. L'opération d'ajout dans la file d'attente ne peut être décrite de façon générique car elle dépend de la bibliothèque utilisée. Par exemple, en Java, l'ajout se fera avec la méthode offer de la classe java.util.LinkedList, tandis qu'en C++, avec la STL, il correspondra à l'appel de la méthode push de std::queue. D'autre part; certaines portions de code ne dépendent pas uniquement du langage utilisé mais également du simulateur cible. Par exemple, récupérer une valeur sur un port d'entrée ne se fait pas de la même façon selon les implémentations. Il est donc nécessaire de pouvoir spécifier des fragments de code qui soient dépendants du simulateur, comme getValueOnPort dans notre exemple. Ainsi, nous utilisons trois niveaux d'indépendances : vis-à-vis du langage de programmation, des bibliothèques et des simulateurs. Comme précédemment, ces indépendances devront être « résolues » à l'aide de modèles de plates-formes et de transformations.

<if> <test> <binaryExpr op="equality"> <lhs><var>sigma</var></lhs> <rhs><literal>0</literal></rhs> </binaryExpr> </test> <then> <codeSnippet kind="libDependent" id= "enqueue"> <param id="queue">buffer</param> <param id="element"> <codeSnippet kind="simDependent" id= "getValueOnPort"> <param id="port">in</param> </codeSnippet> </param> </codeSnippet> </then> </if> Code 4. Représentation de code « semi-générique » en XML Grâce à cette solution, DML laisse une grande liberté dans l'écriture de modèles. Il permet de représenter la plupart des modèles DEVS déjà développés dans les cadriciels existants, et minimise la quantité de code à écrire pour porter un modèle d'une plate-forme à une autre. Seuls les types et les fragments de code abstraits nécessitent d'être concrétisés en une implémentation spécifique. Cette étape peut même être simplifiée en construisant collectivement des « catalogues » de types et de fragments accompagnés de leurs implémentations dans les langages les plus populaires. De même, les fournisseurs de cadriciels DEVS peuvent publier le modèle de leur plate-forme pour permettre l'intégration de leur outil à SimStudio.

Outre les fonctions propres aux modèles DEVS, ce type de code « semi-générique » est également utilisé dans DML pour décrire les phases de paramétrage et d'initialisation. En effet, ces deux étapes se résument parfois à de simples assignations de valeurs, mais peuvent également être plus compliquées (génération de nombres aléatoires, lecture de fichiers, etc.).

Résultats de simulation

Le comportement d'un modèles DEVS est entièrement représenté par l'ensemble de toutes ses trajectoires d'entrée/sortie possibles, c'est-à-dire tous les couples (trajectoire d'entrée, trajectoire de sortie) que le modèle peut générer, une trajectoire d'entrée (respectivement de sortie) étant l'ensemble des évènements générés sur les ports d'entrée (respectivement de sortie) lors d'une session de simulation. Cependant, il est généralement utile au modélisateur de connaître l'évolution des différentes variables d'état, ainsi que le comportement des sousmodèles dans le cas d'un modèle couplé. Ces résultats de simulation sont représentés en DML par des ensembles de valeurs, annotées par un temps de simulation. Nous pouvons alors utiliser ces trajectoires pour analyser le comportement du modèle, représenter graphiquement la simulation, ou encore les donner en entrée à un autre modèle.

Vue d'ensemble des relations de conformité et transformations

Dans les paragraphes précédents, nous avons plusieurs fois évoqué les transformations nécessaires pour obtenir une solution opérationnelle à partir d'un modèle DML. La Erreur ! Source du renvoi introuvable. présente ces différentes transformations, ainsi que les relations de conformité entre les modèles, métamodèles et métamétamodèles utilisés dans SimStudio. Dans cet exemple, nous transformons un modèle DML quelconque en code compréhensible par DEVS-MetaSimulator (DEVS-MS), le simulateur que nous avons développé pour le noyau de SimStudio, écrit en C++.

Nous reprenons dans cette figure les notations utilisées dans [START_REF] Favre | L'ingénierie dirigée par les modèles. Au delà du MDA[END_REF]] :

-χ signifie « est conforme à » ; -τ signifie « est transformé en ». D'autre part, nous dénotons les modèles de transformation par des rectangles pointillés. Ceux-ci sont reliés par une ligne pointillée aux relations « est transformé en » (τ) pour lesquelles ils sont mis en oeuvre.

La meilleure façon d'analyser ce schéma consiste à le lire de bas en haut et de gauche à droite. Au coeur de l'architecture se situent les modèles DML ; ceux-ci sont conformes au métamodèle DML présenté précédemment, lui-même conforme au métamétamodèle XML Schema. L'obtention d'une solution opérationnelle s'effectue par raffinements successifs (transformations vers une plate-forme cible), jusqu'à générer le code source du modèle pour un simulateur donné.

Dans un premier temps, nous effectuons une transformation d'un modèle DML vers un modèle dit « DML-Lang », spécifique à un langage de programmation cible. Cette transformation, représentée par un modèle XSLT, consiste à remplacer les portions de code « génériques » par leurs équivalents dans le langage de destination. Sont concernés les éléments de type assignation, branchement conditionnel, bouclage, etc., ainsi que les définitions de types utilisateurs (classes).

Le métamodèle « DML-Lang » est très similaire à DML, à l'exception des éléments codes qui contiennent maintenant du contenu mixte (texte et balises XML) : la structure générique des fonctions a été générée, mais les fragments dépendants des bibliothèques et des simulateurs n'ont pas été transformés. Cela fait l'objet d'une seconde transformation.

Cette dernière fait intervenir deux types de modèles de plates-formes : les modèles de bibliothèque, et les modèles de simulateur, tous étant conforme au métamodèle « Fragments de code » et spécifiant les correspondances entre les opérations abstraites utilisées dans le modèle DML et les opérations concrètes à effectuer. Les développeurs de cadriciels DEVS doivent avoir la possibilité de redéfinir des correspondances exprimées dans le modèle de bibliothèque, par exemple pour utiliser certaines structures de données s'intégrant dans leur hiérarchie d'héritage, ou encore s'ils souhaitent éviter l'utilisation de certains modules de la bibliothèque ; c'est la raison pour laquelle nous gérons les deux types d'opérations de façon conjointe, grâce à l'utilisation d'une transformation d'ordre supérieur (« Fusion des fragments ») qui, à partir des deux modèles de plate-forme, crée une nouvelle transformation (« DML2DEVS-MS »). Concrètement, cette transformation génère un modèle XSLT qui applique les remplacements définis par les deux modèles (bibliothèque et simulateur) en donnant la priorité à ceux spécifiés pour le simulateur. La transformation générée peut alors être appliquée au modèle DML-Lang afin d'obtenir un modèle DML-Sim. A l'issue de cette transformation, tous les Reste enfin à générer le code complet permettant l'intégration du modèle au cadriciel cible. Généralement, cela passe par la génération d'une classe, de la définition des ports d'entrée et de sorties, des couplages, etc. Il faut d'autre part prendre en compte les contraintes inhérentes au cadriciel : dérivation d'une classe de base, attributs requis, etc. Les opérations à effectuer pouvant être très hétérogènes, nous laissons les concepteurs de simulateurs DEVS libres de définir leurs propres modèles de transformation, conformes à XSLT.

A terme, nous disposerons de différents dépôts de modèles, donnant accès à :

-des modèles DML, ayant vocation à être réutilisés soit en l'état, soit en tant que composants de modèles couplés ;

-des modèles de bibliothèques, offrant ainsi un large choix à l'utilisateur ; des modèles de simulateurs et les modèles de transformation associés, afin d'élargir le spectre d'application de DML au maximum d'outils existants.

Conclusion et travaux futurs

Dans cet article nous avons présenté l'architecture de SimStudio, un environnement de M&S qui agrège dans une seule plate-forme des outils de modélisation, d'analyse, de simulation et de visualisation. Accessible à travers un navigateur internet, il permettra de capitaliser les avancées théoriques du domaine, et accélérera le cycle de M&S : la conception de modèles sera facilité grâce aux modules de modélisation et à la mise à disposition de bibliothèques de modèles ; la création de code exécutable, parallélisable sur des ressources de calcul intensif, sera automatisée au maximum, grâce à des transformations de modèle et à de la génération de code ; et enfin, l'analyse des modèles et des résultats de simulation sera effectuées dans la plate-forme même, via des plugins d'analyse. Doté d'une architecture modulaire, SimStudio permet l'ajout de nouvelles fonctionnalités et l'intégration d'outils existants grâce à une approche d'ingénierie dirigée par les modèles et plus précisément à l'utilisation d'un métamodèle DEVS intitulé DEVS Markup Language.

La suite de nos travaux portera sur plusieurs points. Tout d'abord, à partir des travaux présentés nous envisageons la retro-ingénierie et la refonte de modèles que nous avons déjà réalisés avec DEVS. De plus, nous souhaitons définir le métamodèle DEVS non plus sous forme de schéma XML, mais avec un environnement de métamodélisation plus complet, en utilisant par exemple le MOF. Cela permettrait notamment de spécifier plus précisément la sémantique associée au métamodèle, qui n'est ici qu'implicite. D'autre part, nous essaierons également de définir des patrons de M&S (solution générique à un problème récurrent auquel sont confrontés les modélisateurs). Nous pensons que l'identification de ces situations et l'intégration de solutions appropriées dans SimStudio permettra de réduire la complexité du développement de bon nombre de modèles et de simulations.

Figure 2 .

 2 Figure 2. Modèles, métamodèles, métamétamodèles et modèles de transformation mis en jeu dans le noyau de SimStudio

  ]. Avant de pouvoir être utilisés, ces types doivent être liés à une implémentation spécifique dans le langage de programmation cible. Par exemple, un type abstrait Le Code 1 montre un exemple de description des variables d'état d'un modèle atomique. Ce modèle possède deux variables : sigma, un nombre réel, et buffer, une file d'attente, instance du type abstrait Queue ; afin de pouvoir générer le code correspondant dans les langages cibles, il est nécessaire de spécifier les liaisons entre types abstraits et types concrets. Pour cela, nous définissons des modèles de bibliothèques, qui indiquent les types à utiliser pour remplir le rôle des types abstraits. Les Codes 2 et 3 donnent la correspondance du type Queue dans la bibliothèque standard du Java et dans celle du C++, la Standard Template Library (STL).

	<state>
	<variable>
	<name>buffer</name>
	<type kind="libDependent">Queue</type>
	</variable>
	<variable>
	<name>sigma</name>
	<type>double</type>
	</variable>
	</state>
	RandomNumberGenerator	pourrait	être	implémenté	en	Java	par	la	classe
	umontreal.iro.lecuyer.rng.MT199937 [L'Ecuyer et al. 2002] et en C++ par boost::mt19937.

Code 1. Description des variables d'état <type id="Queue"> java.util.LinkedList<String> </type> Code 2. Extrait du modèle « Standard Java Library » <type id="Queue"> std::queue<std::string> </type>

Code 3. Extrait du modèle STL

Ces modèles de bibliothèques seront utilisés lors des transformations de modèles DEVS vers une solution opérationnelle, permettant ainsi de générer automatiquement un modèle spécifique dans le langage cible (rejoignant la notion de Platform Specific Model du MDA). Ainsi, les instances du type Queue seront en Java des listes chaînées de chaînes de caractères, et en C++ des files de chaînes de caractères. Les différentes transformations nécessaires sont détaillées dans le paragraphe 5.4.