Skip to content
/ uia-sim Public

A Java port of SimPy, process-based discrete event simulation framework.

License

Notifications You must be signed in to change notification settings

uia4j/uia-sim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

67 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

UIA-SIM for Java, DESim4J

Build Status codecov Codacy Badge License

DESim4J is a Java port of SimPy, process-based discrete event simulation framework.

DESim4J aims to port the concepts used in SimPy to the Java world. Because there is no yield keyword in Java, the framework also implements a yield-like API in package uia.cor to meet some coroutine scenarios.

The ROAD is a sub-project that build a abstract simulator of the manufacturing factory.

API

package uia.cor

The package provides yield-like API. The main concept is

Generator gen = Yield.accept(yield -> ::function);
  • Yield

    • yield.call(Object) - pass a value to paired Generator.
    • yield.close() - stop iteration.
  • Generator

    • gen.next() - notify ::function to prepare next value.
    • gen.errorNext() - notify ::function to prepare next value.
    • gen.getValue() - Get the value from yield.setValue(Object)
    • gen.error(ex) - send back an exception.
    • gen.send(Object) - send back a value to yield.
    • gen.close() - stop yield iterable.

Below is a simple workflow of Yield-Generator:

public class YieldTest {

    @Test
    public void testCallFor() {
        // 1
        Generator<Integer> gen = Yield.accept(this::callFor); 
            // 2, 5
            while(gen.next()) {
                // 4
                System.out.println("value=" + gen.getValue());
        }
    }

    /**
     * iterable work
     */
    public void callFor(Yield<Integer> yield) {
        for(int i = 0; i < 10; i++) {
            // 3
            yield.call(i);
        }
    }
}
  1. Generator<Integer> gen = Yield.accept(this::callFor) - Create a Yield object and pass to callFor method. Return paired Generator.
  2. gen.next() - Ask if there is a new value or not.
  3. yield.call(i) - Pass a new value to the generator and block until invoking gen.next() again.
  4. gen.getValue() - Get the new value passed by yield.call(i).
  5. while(gen.next()) - Repeat until completing the for loop.

Use Yield2Way if iteration needs to get a result from yield.call(value).

public class Yield2WayTest {

    @Test
    public void testSum2() {
        Generator2Way<Integer, Integer> gen = Yield2Way.accept(this::sum2);
        int i = 0;
        // 4
        while(gen.next()) {
            // 2
            i = gen.getValue();
            System.out.println("value=" + i);
            // 3
            gen.send(i * i);
        }
    }

    /**
     * iterable work
     */
    public void sum2(Yield2Way<Integer, Integer> yield) {
        int i = 1;
        int sum = 0;
        while(i <= 10) {
            // 1, 5
            int v = yield.call(i++);    // waiting a result
            sum += v;
        }
        System.out.println("  sum=" + sum);
    }
}
  1. yield.call(i++) - Pass a new value to the generator and block until invoking gen.next() again.
  2. gen.getValue() - Get the new value passed by yield.call(i).
  3. gen.send(i * i) - Send back a result but step 1 is still blocking.
  4. gen.next() - Ask if there is a new value or not and release step 1 at the same time.
  5. int v = yield.call(i++) - Get the result passed by gen.send(i * i).

Class Diagram

classDiagram
    Generator --> Yield
    Consumer ..> Yield
    Yield <-- Yieldable

    <<Iterable>> Consumer

    Generator: next() boolean
    Generator: next(R) boolean
    Generator: getValue() T
    Yield: call(T)
    Yield: send(R)
    Yield: next(boolean stop) boolean
    Yield: getValue() T
    Consumer: accept(Yield)
    Yieldable: run()
Loading

Sequence Diagram

sequenceDiagram
    autonumber
    Controller --) Consumer: new()
    Controller ->> +Yield: accept()
    Yield -->> +Generator: new()
    Yield --) -Controller: generator

    Yield --) Consumer: accept()
    note over Yield: in a new Thread
    loop thread-iterable
        Consumer ->> +Yield: call(T)
        note right of Consumer: send a value to the Controller
        Yield ->> Yield: notifyAll()
        note over Yield: notify Step-14 to get next value
        Yield --) Yield: wait()
        Yield --) -Consumer: 
        note over Yield: wait Step-12 to notify
    end

    loop thread-main
        Controller ->> Generator: next()
        Generator ->> +Yield: next()
        Yield ->> Yield: notifyAll()
        note over Yield: notify Step-9 to build next value
        Yield --) Yield: wait()
        Yield --) -Generator: 
        note over Yield: wait Step-7 to notify
        Controller ->> Generator: T getValue()
        Generator ->> +Yield: T getValue()
        Yield --) -Generator: value
        note right of Controller: get last value from the Generator
    end    

Loading

package uia.sim

The package is core framework of process-based discrete event simulation.

Some documents

Core Concept

  1. Create a event stream
flowchart LR;
    id1[[*E00]]-->E20;
    E20-->E50;
    E50-->E90;
Loading
  1. Execute head event E00, and create a new event E55. The event stream becomes
flowchart LR;
    id1[[*E20]]-->E50;
    E50-->id2([E55]);
    id2([E55])-->E90;
Loading
  1. Execute head event E20, and create a new event E53. The event stream becomes
flowchart LR;
    id1[[*E50]]-->id2([E53]);
    id2([E53])-->id3([E55]);
    id3([E55])-->E90;
Loading
  1. Execute all events with ordering.
flowchart LR;
    id1[[*E53]]-->id2([E55]);
    id2([E55])-->E90;
Loading
flowchart LR;
    id1[[*E55]]-->E90;
Loading
flowchart LR;
    id1[[*E90]];
Loading

Class Diagram

classDiagram

    Env --* Job
    Initialize --|> Event
    Job --> Event
    Event <.. Yield
    Event -- * Callback 
    Callback -- Process
    Event <|-- Process
    Event <.. Processable
    Yield <-- Consumer
    Yield <-- Generator
    Consumer <|-- Processable
    Processable --> Process  
    Generator <-- Process

    <<coroutine>> Yield
    <<coroutine>> Generator
    <<coroutine>> Consumer
    <<case>> Processable

    Env: PriorityBlockingQueue~Job~ jobs

    Process: +resume(Event)

    Processable: +initial()
    Processable: +run()

    Event: List callables
    Event: +callback()

Loading

Sequence Diagram

  1. Work flow of events

    sequenceDiagram
        autonumber
        loop
            Env ->> +Env: poll()
            Env ->> Event: callback()
            Event ->> Process: resume(Event)
            Process --) Processable: // notify
            Processable ->> Env: // schedule a new event if needed
            Env --) Event: // new
            Env ->> -Env: // add a new event into the queue
            Processable --) Process: // notify
            Process ->> Event: addCallable()
        end
    
    Loading
  2. Startup of a process

    sequenceDiagram
        autonumber
        Env ->> Event["Initialize"]: callback()
        Event["Initialize"] ->> +Process: resume(Event)
    
            Process ->> Event["Initialize"]: getValue()
            Process ->> +Generator: next(Object)
            Generator ->> Yield: send(Object)
            Generator ->> Yield: next(Boolean)
            Yield --) Processable: notifyAll()
            Processable ->> +Env: // schedule a new event if needed
            Env --) Event: new
            Env ->> -Env: // add a new event into the queue
            Processable ->> Yield: call(Event)
            Yield --) Generator: notifyAll()
            Generator --) -Process: 
            Process ->> Generator: getValue()
            Process ->> -Event: addCallable(Process::resume) // hook the Process and Event together
    
    Loading

Test Case

Below is a Java test case compares with Python version.

Python

class School:
    def __init__(self, env):
        self.env = env
        self.class_ends = env.event()
        self.pupil_procs = [env.process(self.pupil()) for i in range(3)]
        self.bell_proc = env.process(self.bell())

    def bell(self):
        while True:
            yield self.env.timeout(45)
            self.class_ends.succeed()
            self.class_ends = self.env.event()
            print()

    def pupil(self):
        while True:
            yield self.class_ends
            print(r' \o/', end='')

env = Environment()
school = School(env)
env.run(200)

Java

public class SchoolTest {

    private Env env;

    private Event classEnd;

    public SchoolTest() {
        this.env = new Env();
        this.classEnd = this.env.event("classEnd");
        env.process("pupil-1", this::pupil);
        env.process("pupil-2", this::pupil);
        env.process("pupil-3", this::pupil);
        env.process("bell", this::bell);
    }

    public void bell(Yield<Event> yield) {
        while(yield.isAlive()) {
            yield.call(env.timeout(45));
            this.classEnd.succeed(null);
            this.classEnd = this.env.event("classEnd");
            System.out.println(String.format("\n%3d> bell is ringing...", this.env.getNow()));
        }
    }

    public void pupil(Yield<Event> yield) {
        while(yield.isAlive()) {
            yield.call(this.classEnd);
            System.out.print("\\o/ ");
        }
    }

    @Test
    public void test1() throws Exception {
        this.env.run(200);
    }
}

The framework is still building and testing. The next tasks are

  1. Add resources implementation.
  2. More stable of uia.cor package.
  3. More reasonable Exception control.
  4. More test cases to prove the framework.

Reference

SimPy Home

SimPy GitLab

Discrete-Event Simulation Wiki