package sushi.simulation;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import sushi.bpmn.element.AbstractBPMNElement;
import sushi.bpmn.element.BPMNProcess;
import sushi.bpmn.element.BPMNTask;
import sushi.bpmn.element.BPMNXORGateway;
import sushi.bpmn.monitoringpoint.MonitoringPoint;
import sushi.event.SushiEvent;
import sushi.event.SushiEventType;
import sushi.event.attribute.SushiAttribute;
import sushi.eventhandling.Broker;
import sushi.process.SushiProcess;
import sushi.util.Tuple;
/**
* The central class for simulation
*/
public class Simulator {
private int numberOfInstances;
private AbstractBPMNElement startEvent;
private List<InstanceSimulator> instanceSimulators;
private Map<SushiAttribute, List<Serializable>> attributesAndValues;
private Map<SushiAttribute, List<Serializable>> correlationAttributesMap;
private Date currentSimulationDate;
private Map<AbstractBPMNElement, Long> elementExecutionDurations;
private Map<AbstractBPMNElement, DerivationType> elementTimeDerivationTypes;
private Random random = new Random();
private Map<AbstractBPMNElement, Long> elementDerivations;
private Map<BPMNXORGateway, List<Tuple<AbstractBPMNElement, Integer>>> xorSplitsWithSuccessorProbabilities;
private List<SushiAttribute> identicalAttributes;
private List<SushiAttribute> differingAttributes;
private Map<AbstractBPMNElement, Tuple<Integer, Integer>> unexpectedEvents;
public Simulator(SushiProcess process, BPMNProcess bpmnProcess, Map<SushiAttribute, List<Serializable>> attributesAndValues, Map<AbstractBPMNElement, String> tasksDurationString, Map<AbstractBPMNElement, String> tasksDerivationString, Map<AbstractBPMNElement, DerivationType> tasksDerivationTypes, Map<BPMNXORGateway, List<Tuple<AbstractBPMNElement, Integer>>> xorSplitsWithSuccessorProbabilities){
this.startEvent = bpmnProcess.getStartEvent();
this.instanceSimulators = new ArrayList<InstanceSimulator>();
this.attributesAndValues = attributesAndValues;
this.correlationAttributesMap = getCorrelationAttributesMap(process);
this.numberOfInstances = 0;
this.currentSimulationDate = new Date();
this.elementExecutionDurations = SimulationUtils.getDurationsFromMap(tasksDurationString);
this.elementDerivations = SimulationUtils.getDurationsFromMap(tasksDerivationString);
this.elementTimeDerivationTypes = tasksDerivationTypes;
this.xorSplitsWithSuccessorProbabilities = xorSplitsWithSuccessorProbabilities;
this.identicalAttributes = new ArrayList<SushiAttribute>();
this.differingAttributes = new ArrayList<SushiAttribute>();
}
private Map<SushiAttribute, List<Serializable>> getCorrelationAttributesMap(SushiProcess process) {
correlationAttributesMap = new HashMap<SushiAttribute, List<Serializable>>();
for(SushiAttribute correlationAttribute : process.getCorrelationAttributes()){
for(SushiAttribute attribute : attributesAndValues.keySet()){
if(correlationAttribute.equals(attribute)){
correlationAttributesMap.put(correlationAttribute, attributesAndValues.get(attribute));
break;
}
}
}
return correlationAttributesMap;
}
/**
* Simulates the process numberOfInstances over 1 Day
* @param numberOfInstances
*/
public void simulate(int numberOfInstances) {
simulate(numberOfInstances, 1);
}
/**
* Simulates the process numberOfInstances over a numberOfDays
* @param numberOfInstances
* @param numberOfDays
*/
public void simulate(int numberOfInstances, int numberOfDays){
int timeDifferenceInMs = timeDifferenceInMs(numberOfInstances, numberOfDays);
for(int i = 0; i < numberOfInstances; i++){
Map<SushiAttribute, List<Serializable>> instanceAttributes = createNewInstanceAttributesMap();
InstanceSimulator instanceSimulator = new InstanceSimulator(startEvent, this, instanceAttributes, currentSimulationDate, differingAttributes);
currentSimulationDate = new Date(currentSimulationDate.getTime() + timeDifferenceInMs);
instanceSimulators.add(instanceSimulator);
}
startSimulation();
}
/**
* starts the simulation bei repating to
*/
private void startSimulation(){
while(!instanceSimulators.isEmpty()){
getEarliestInstanceSimulator().simulateStep();
}
}
private Map<SushiAttribute, List<Serializable>> createNewInstanceAttributesMap() {
//für jede Instanz wird eine neue Map erstellt mit bestimmten werten
List<Serializable> valueList;
Map<SushiAttribute, List<Serializable>> instanceCorrelationAttributes = getNextCorrelationAttributes();
Map<SushiAttribute, List<Serializable>> instanceAttributes = new HashMap<SushiAttribute, List<Serializable>>();
for(SushiAttribute attribute : attributesAndValues.keySet()){
instanceAttributes.put(attribute, new ArrayList<Serializable>(attributesAndValues.get(attribute)));
}
addIdenticalAttributes(instanceAttributes);
addCorrelationAttributes(instanceCorrelationAttributes, instanceAttributes);
return instanceAttributes;
}
private void addIdenticalAttributes(Map<SushiAttribute, List<Serializable>> instanceAttributes) {
Random random = new Random();
int index;
List<Serializable> valueList;
for(SushiAttribute identicalAttribute : identicalAttributes){
for(SushiAttribute existingAttribute : instanceAttributes.keySet()){
if(identicalAttribute.equals(existingAttribute)){
valueList = new ArrayList<Serializable>();
index = random.nextInt(instanceAttributes.get(existingAttribute).size());
Serializable chosenObject = attributesAndValues.get(existingAttribute).get(index);
valueList.add(chosenObject);
instanceAttributes.put(existingAttribute, valueList);
break;
}
}
}
}
/**
* overwrites the correlation attributes to make same identical per instance and unique between instances
*/
private void addCorrelationAttributes(Map<SushiAttribute, List<Serializable>> instanceCorrelationAttributes, Map<SushiAttribute, List<Serializable>> instanceAttributes) {
//Korrelationsattribute überschreiben andere ausgewählte
for(SushiAttribute correlationAttribute : instanceCorrelationAttributes.keySet()){
for(SushiAttribute existingAttribute : instanceAttributes.keySet()){
if(correlationAttribute.equals(existingAttribute)){
instanceAttributes.put(existingAttribute, instanceCorrelationAttributes.get(correlationAttribute));
break;
}
}
}
}
/**
* creates new correlation attributes for each instance by counting and stepping throught the possible values
*/
private Map<SushiAttribute, List<Serializable>> getNextCorrelationAttributes() {
HashMap<SushiAttribute, List<Serializable>> instanceCorrelationAttributes = new HashMap<SushiAttribute, List<Serializable>>();
//die Zahl wird jedes mal inkrementiert, um neue Werte zu erzeugen
int index = numberOfInstances;
int nextIndex;
for(SushiAttribute correlationAttributeKey : correlationAttributesMap.keySet()){
//TODO: werte aus event... holen
List<Serializable> correlationAttribute = correlationAttributesMap.get(correlationAttributeKey);
nextIndex = 0;
int mapsize = correlationAttribute.size();
while(index >= mapsize){
nextIndex ++;
index = index - mapsize;
}
Serializable value = correlationAttribute.get(index);
List<Serializable> valueList = new ArrayList<Serializable>();
valueList.add(value);
instanceCorrelationAttributes.put(correlationAttributeKey, valueList);
index = nextIndex;
}
numberOfInstances++;
return instanceCorrelationAttributes;
}
public void unsubscribe(InstanceSimulator simulator) {
instanceSimulators.remove(simulator);
}
public int timeDifferenceInMs(int numberOfInstances, int numberOfDays){
int totalSimulationPeriodInMs = numberOfDays * 24 * 60 * 60 * 1000;
return totalSimulationPeriodInMs / numberOfInstances;
}
public long getMeanDurationForBPMNElement(AbstractBPMNElement element){
long meanDuration;
if(this.elementExecutionDurations != null && this.elementExecutionDurations.containsKey(element)){
meanDuration = elementExecutionDurations.get(element);
}
else{
meanDuration = 0;
}
return meanDuration;
}
public long getDerivationForBPMNElement(AbstractBPMNElement element){
long derivation;
if(this.elementDerivations != null && this.elementDerivations.containsKey(element)){
derivation = elementDerivations.get(element);
}
else{
derivation = 0;
}
return derivation;
}
/**
* returns the duration for executing a bpmn-element, calculation depends on the derivationType
*/
public long getDurationForBPMNElement(AbstractBPMNElement element){
long mean = getMeanDurationForBPMNElement(element);
long derivation = getDerivationForBPMNElement(element);
if(elementTimeDerivationTypes == null ||(elementTimeDerivationTypes.get(element) == null)){
return mean;
}
if(elementTimeDerivationTypes.get(element).equals(DerivationType.NORMAL)){
double dur = mean + (random.nextGaussian() * derivation);
return (long) dur;
}
else{
return mean;
}
}
/**
* randomly chooses a path with the given probabilities
*/
public AbstractBPMNElement choosePath(AbstractBPMNElement currentElement) {
List<Tuple<AbstractBPMNElement, Integer>> successorsWithProbability = xorSplitsWithSuccessorProbabilities.get(currentElement);
Random random = new Random();
int index = random.nextInt(100);
for(Tuple<AbstractBPMNElement, Integer> tuple : successorsWithProbability){
if(index < tuple.y){
return tuple.x;
}
}
return null;
}
private InstanceSimulator getEarliestInstanceSimulator() {
InstanceSimulator earliestInstanceSimulator = instanceSimulators.get(0);
for(InstanceSimulator instanceSimulator : instanceSimulators){
if(instanceSimulator.getEarliestDate().before(earliestInstanceSimulator.getEarliestDate())){
earliestInstanceSimulator = instanceSimulator;
}
}
return earliestInstanceSimulator;
}
public void addAdvancedValueRules(List<ValueRule> valueRules){
for(ValueRule valueRule : valueRules){
if(valueRule.getRuleType().equals(ValueRuleType.EQUAL)){
identicalAttributes.add(valueRule.getAttribute());
}
else{
differingAttributes.add(valueRule.getAttribute());
}
}
}
}