Simple Go Simulation System
Introduction
Simulation is a method in which to conduct experiments, the studied real system is replaced by a model. In such a model, you can lose both individual situations and many of them. The collected statistics can help draw conclusions about the processes in the system, as well as outline optimization paths.
Simulation is often considered as a kind of experimental tests, but at the same time it is less expensive, it allows you to quickly change the parameters and observe the simulated system in dynamics.
Already about half a century in simulation modeling, computer models have been used. A lot of different programs and frameworks have been created for their development, among which, in my opinion, the most developed are the tools for modeling queuing systems (QS). One of the most well-known and simple programs for simulation of QS - GPSS World (General Purpose Simulation System - a general-purpose modeling system), can be found in more detail at the links [1] , [2] .
The concept of this program was the basis of the simulation framework on Go.
Simulation in GPSS
A model in GPSS is a sequence of blocks (commands) describing the simulated objects between which transactions are moving. When a transaction enters the block, events are generated that lead either to a change in the state of the modeled object or to a change in the state / parameters of the transaction.
The main blocks of the order of ten: GENERATE, TERMINATE, ASSIGN, SEIZE, RELEASE, QUEUE, ADVANCE, DEPART, START. There are about three dozen blocks in total. Blocks have parameters, which can be numbers, function names, labels in the simulation program, variable names. More details about the blocks can be found, for example, here .
Objects in GPSS have a set of standard numerical attributes (NAV) and standard logical attributes (ALS). For example, for a queue, one of the NAV is the current length, and an example of an ALS for some equipment will be TRUE or busy (FALSE).
In some versions of GPSS there is a visualization of the modeling process, but most often it is absent. Based on the simulation results, a report is generated in GPSS, indicating the NAV and ALS for all objects.
Go implementation
Implementation in Go is the development of a set of objects similar in function to GPSS blocks. The first was created Pipeline - the object within which the simulation is performed.
Based on a
map
list of components for describing the simulated system. Since, during the simulation, transactions must go through a sequence of blocks in a strict order, the method of adding components Append
implemented the procedure of adding with the simultaneous indication of the addresses of the transactions from them. The map
name of the component is used as a key , so each component must have a unique name. After adding all the components, you can start the simulation using the method
Start
. Inside it, a cyclic bypass of all components is implemented for a given simulation time. At the end of the simulation, you can print a report containing NAV and ALS. The second important element is the actual components for describing the simulation. Were implemented: Generator - generates transactions, Advance - creates delays on the path of the transaction, Queue - queues of transactions, Facility - a device that is exclusively captured by the transaction for a while, Hole - a "hole" in which transactions fail at the end of the path. Of course, such a set is not enough to create complex simulation models, but enough to work out the solution and compare with the GPSS results. All components implement the IBaseObj interface, which covers the minimum required functionality.
Each component has a transaction queue. Directly as a queue, it is used only in Queue, for other components it is just a repository. The queue is implemented based on
map
. In the modeling process, the components pass in turn and check the readiness (fulfillment of a certain condition) of the transaction for transmission to the next component. The transfer is performed through the method of the AppendTransact
next component. If the transfer is successful, then the transaction is removed from the queue, respectively, the next component takes it in turn. Since several recipients are defined for each component, then if it was not possible to send a transaction to one recipient, we try to send it to another. To generate random variables when determining the time of the appearance of the transaction and creating delays, the PRNG functions in Go are used.
Since when modeling, at the same time, there can be many transactions moving between different components, the idea arose to use goroutines inside the components. Pipeline, passing through the components, launches a handler for each of them
HandleTransacts
, inside which a goroutine is created. After all goroutines have completed, the model time counter is incremented and re-called HandleTransacts
. The last key object is the transaction itself. He has an identifier, time of birth and death, the owner (in which component he is now), a number of parameters for calculating the NAV and NAV.
In fig. 1 is a structural diagram of the interaction of the main objects of the framework during modeling.
Fig. 1. Generalized structural diagram of the interaction of the main objects in the simulation
Simulation example
Suppose you need to simulate the work of a hairdresser. This is a famous example from GPSS. Visitors go randomly, with a frequency of 18 ± 6 minutes, their number is not known in advance. We have one hairdresser, he spends 16 ± 4 minutes on a haircut. So, how many people will he cut for a working day? How many people will be in line? What is the average time it takes for a haircut and how much time do people wait in line? A lot of questions and a simple simulation. The block diagram in Fig. 2.
Fig. 2. The structural scheme of modeling a hairdressing salon
The code for building the model will be as follows.
barbershop := NewPipeline("Barbershop", true) // Наша симуляция
clients := NewGenerator("Clients", 18, 6, 0, 0, nil) // Генератор клиентов
chairs := NewQueue("Chairs") // Очередь
master := NewFacility("Master", 16, 4) // Парикмахер
hole := NewHole("Out") // Выход
barbershop.Append(clients, chairs) // От генератора транзакты идут в очередь
barbershop.Append(chairs, master) // Из очереди идут в устройство
barbershop.Append(master, hole) // Из устройства в дыру
barbershop.Append(hole) // А из дыры транзакты никуда не уходят
barbershop.Start(480) // Моделируем рабочий день
<-barbershop.Done // Завершение симуляции
barbershop.PrintReport()
The simulation results can be found here.
Pipeline name "Barbershop"
Simulation time 480
Object name "Chairs"
Max content 1
Total entries 26
Zero entries 11
Persent zero entries 42.31%
In queue 0
Average time / trans 2.58
Average time / trans without zero entries 4.47
Object name "Clients"
Generated 26
Object name "Master"
Average advance 16.46
Average utilization 89.17
Number entries 26.00
Transact 26 in facility
Object name "Out"
Killed 25
Average advance 16.56
Average life 19.44
Simulation time 480
Object name "Chairs"
Max content 1
Total entries 26
Zero entries 11
Persent zero entries 42.31%
In queue 0
Average time / trans 2.58
Average time / trans without zero entries 4.47
Object name "Clients"
Generated 26
Object name "Master"
Average advance 16.46
Average utilization 89.17
Number entries 26.00
Transact 26 in facility
Object name "Out"
Killed 25
Average advance 16.56
Average life 19.44
Served 25 customers, the 26th at the time of completion of the simulation was still in the chair of the master. The queue was no more than 1 person, 11 people did not wait (zero pass) and immediately went for a haircut. On average, people spent 2.58 minutes in the queue, and of those who were waiting (not a zero pass), 4.47 minutes. 89.17% of his time, a hairdresser, intensively sheared.
Of course, if you conduct another simulation, the results will change. But during a series of simulations, the overall level of the wizard’s load and the number of clients served will be visible. A similar simulation in GPSS produces similar results.
Another example. There is an office, it has 10 employees and one toilet. People want to go to the toilet every 90 ± 60 minutes, go to the toilet for 5 ± 3 minutes, take a toilet for 15 ± 10 minutes, go back to the office for 5 ± 3 minutes. We will carry out the simulation for 9 hours (8 hours of work + 1 hour of lunch), in fig. 3 is a structural diagram.
Fig. 3. Structural diagram of the toilet employment model: on the left with one toilet, on the right with two.
On the left, a model with one toilet, on the right with two. The following is the model code.
waterclosetsim := NewPipeline("Water Closet Simulation", false)
office := NewGenerator("Office", 0, 0, 0, 10, nil)
wantToToilet:= NewAdvance("Wanted to use the toilet", 90, 60)
pathToWC := NewAdvance("Path to WC", 5, 3)
queue := NewQueue("Queue to the WC")
pathFromWC := NewAdvance("Path from WC", 5, 3)
wc := NewFacility("WC", 15, 10)
pathToOffice:= NewAdvance("Path from WC", 5, 3)
waterclosetsim.Append(office, wantToToilet)
waterclosetsim.Append(wantToToilet, pathToWC)
waterclosetsim.Append(pathToWC, queue)
waterclosetsim.Append(queue, wc)
waterclosetsim.Append(wc, pathFromWC)
waterclosetsim.Append(pathFromWC, wantToToilet)
waterclosetsim.Start(540)
<-waterclosetsim.Done
waterclosetsim.PrintReport()
The simulation results are as follows.
Pipeline name "Water Closet Simulation"
Simulation time 540
Object name "Office"
Generated 10
Object name "Path from WC"
Average advance 5.77
Object name "Path to WC"
Average advance 5.22
Object name "Queue to the WC"
Max content 4
Total entries 36
Zero entries 8
Persent zero entries 22.22%
Current contents 4
Average content 1.78
Average time / trans 24.11
Average time / trans without zero entries 31.00
Object name "WC"
Average advance 14.69
Average utilization 87.04
Number entries 32.00
Transact 2 in facility
Object name "Wanted to use the toilet"
Average advance 95.85
Simulation time 540
Object name "Office"
Generated 10
Object name "Path from WC"
Average advance 5.77
Object name "Path to WC"
Average advance 5.22
Object name "Queue to the WC"
Max content 4
Total entries 36
Zero entries 8
Persent zero entries 22.22%
Current contents 4
Average content 1.78
Average time / trans 24.11
Average time / trans without zero entries 31.00
Object name "WC"
Average advance 14.69
Average utilization 87.04
Number entries 32.00
Transact 2 in facility
Object name "Wanted to use the toilet"
Average advance 95.85
There were up to 4 people in line, 8 times a person immediately got into the toilet, during the working day the toilet was used at 87.04%. The most significant, in my opinion, is that people wait about half an hour (31 minutes) in line for the toilet. Perhaps this is due to the fact that there is only one toilet, and perhaps due to the fact that on average people sit in it for 14.69 minutes.
Having added another toilet, I saw that the queue was reduced to 3 people, 29 times people immediately got into the toilet. But most importantly, the expectation decreased almost three times.
Conclusion
The framework created on the knee is quite simple and still limited. Plans to increase its functionality to the level of GPSS. The practical value of the framework is the ability to quickly and easily assemble a simulation model on Go and get results.
The code is posted on GitHub .