/* * funCKit - functional Circuit Kit * Copyright (C) 2013 Lukas Elsner <open@mindrunner.de> * Copyright (C) 2013 Peter Dahlberg <catdog2@tuxzone.org> * Copyright (C) 2013 Julian Stier <mail@julian-stier.de> * Copyright (C) 2013 Sebastian Vetter <mail@b4sti.eu> * Copyright (C) 2013 Thomas Poxrucker <poxrucker_t@web.de> * Copyright (C) 2013 Alexander Treml <alex.treml@directbox.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.sep2011.funckit.model.simulationmodel; import de.sep2011.funckit.model.graphmodel.Brick; import de.sep2011.funckit.model.graphmodel.Circuit; import de.sep2011.funckit.model.graphmodel.Component; import de.sep2011.funckit.model.graphmodel.ComponentType; import de.sep2011.funckit.model.graphmodel.Element; import de.sep2011.funckit.model.graphmodel.Input; import de.sep2011.funckit.model.graphmodel.Output; import de.sep2011.funckit.model.graphmodel.Switch; import de.sep2011.funckit.model.graphmodel.Wire; import de.sep2011.funckit.observer.AbstractObservable; import de.sep2011.funckit.observer.SimulationModelInfo; import de.sep2011.funckit.observer.SimulationModelObserver; import de.sep2011.funckit.util.Pair; import de.sep2011.funckit.util.Profiler; import java.util.Deque; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.Map; import java.util.Set; import static de.sep2011.funckit.util.Log.gl; /** * Default implementation of {@link Simulation}. */ public class SimulationImpl extends AbstractObservable<SimulationModelObserver, SimulationModelInfo> implements Simulation { /** * Used as default value for {@link Input}s which are not connected. */ private static final boolean DEFAULT_INPUT_VALUE = false; /** * With these {@link Brick}s the simulation will begin to calculate because * those are providing values at the beginning. This means specifically all * {@link Switch}es and all {@link Brick}s with a delay greater zero. */ private Set<SimulationBrick> rootBricks; /** * These {@link Input}s belonging to the corresponding * {@link SimulationBrick} the simulation will give a default input value at * the beginning of each simulation step, because they don't get values, * because they are not connected to anything. */ private Set<Pair<Input, SimulationBrick>> freeInputs; /** * This assigns every {@link Brick} in the {@link Simulation} a * corresponding {@link SimulationState}. */ private Map<SimulationBrick, SimulationState> simulationStateMap; /** * Used for debugging (to verify that all bricks are simulated in one step). */ private final Set<SimulationBrick> simulatedBricks; /** * Used for debugging (to check the bricks against the simulated bricks). */ private final Circuit circuit; /** * This constructor creates a new Simulation based on the given * {@link Circuit}. * * @param circuit * the {@link Circuit} the Simulation will simulate. This has to * be non null. */ public SimulationImpl(Circuit circuit) { long time = 0; if (Profiler.ON) { time = System.currentTimeMillis(); } this.circuit = circuit; simulatedBricks = new LinkedHashSet<SimulationBrick>(); rootBricks = new LinkedHashSet<SimulationBrick>(); simulationStateMap = new LinkedHashMap<SimulationBrick, SimulationState>(); doBuildRootSet(new LinkedList<Component>(), circuit); Deque<Component> stack = new LinkedList<Component>(); Set<Input> connectedInputs = new LinkedHashSet<Input>(); freeInputs = getUnconnectedInputs(stack, circuit, connectedInputs); initInfo(SimulationModelInfo.getInfo()); if (Profiler.ON) { Profiler.simulation(Profiler.CREATE_SIMULATION, System.currentTimeMillis() - time); } } /** * Copy constructor. Does a deep copy of the given {@link SimulationImpl}. * * @param other * {@link SimulationImpl} to copy from. */ private SimulationImpl(SimulationImpl other) { long time = 0; if (Profiler.ON) { time = System.currentTimeMillis(); } simulatedBricks = new LinkedHashSet<SimulationBrick>(); this.circuit = other.circuit; // deep copy of the rootBricks rootBricks = new LinkedHashSet<SimulationBrick>(); for (SimulationBrick rootBrick : other.rootBricks) { Deque<Component> stack = new LinkedList<Component>( rootBrick.getStack()); rootBricks.add(new SimulationBrick(rootBrick.getBrick(), stack)); } // deep copy of the freeInputs freeInputs = new LinkedHashSet<Pair<Input, SimulationBrick>>(); for (Pair<Input, SimulationBrick> freeInput : other.freeInputs) { // copy the simulation brick Brick brick = freeInput.getRight().getBrick(); Deque<Component> otherStack = freeInput.getRight().getStack(); Deque<Component> stack = new LinkedList<Component>(otherStack); SimulationBrick simBrick = new SimulationBrick(brick, stack); // copy the pair freeInputs.add(new Pair<Input, SimulationBrick>( freeInput.getLeft(), simBrick)); } // deep copy of the simulationStateMap simulationStateMap = new LinkedHashMap<SimulationBrick, SimulationState>(); for (SimulationBrick otherKey : other.simulationStateMap.keySet()) { SimulationState otherState = other.simulationStateMap.get(otherKey); assert otherState != null; Deque<Component> stack = new LinkedList<Component>( otherKey.getStack()); SimulationBrick key = new SimulationBrick(otherKey.getBrick(), stack); simulationStateMap.put(key, otherState.clone()); } initInfo(SimulationModelInfo.getInfo()); if (Profiler.ON) { Profiler.simulation(Profiler.COPY_SIMULATION, System.currentTimeMillis() - time); } } /** * Constructs the {@link SimulationImpl#rootBricks} by finding all * {@link Switch}es and non {@link Component}s with delay and creating a * {@link SimulationBrick} for them. * * @param stack * The current path to the {@link Component} in which the * {@link Circuit} is in. * @param circuit * The {@link Circuit} from which the * {@link SimulationImpl#rootBricks} set is built. */ private void doBuildRootSet(Deque<Component> stack, Circuit circuit) { for (Element element : circuit.getElements()) { if (element instanceof Brick) { Brick brick = (Brick) element; // Add brick which have delay or are switches to the root set. if (brick instanceof Switch || brick.hasDelay()) { Deque<Component> newStack = new LinkedList<Component>(stack); rootBricks.add(new SimulationBrick(brick, newStack)); } // go recursively into components and continue building the set if (brick instanceof Component) { Component component = (Component) brick; stack.push(component); doBuildRootSet(stack, component.getType().getCircuit()); stack.pop(); } } } } /** * Calculates a {@link Set} of unconnected {@link Input}s in the given * {@link Circuit} with their corresponding {@link SimulationBrick}. This is * done recursively within {@link Component}s and their * {@link ComponentType}s and their {@link Circuit}s while ignoring * unconnected {@link Input}s in the {@link Circuit} if they belong to the * {@link ComponentType}. * * @param stack * The current path to the {@link Component} in which the * {@link Circuit} is in. * @param c * The {@link Circuit} for which the unconnected {@link Input}s * are calculated. * @param ignoredInputs * A {@link Set} of {@link Input}s to ignore even if they are * unconnected. * @return */ private Set<Pair<Input, SimulationBrick>> getUnconnectedInputs( Deque<Component> stack, Circuit c, Set<Input> ignoredInputs) { Set<Pair<Input, SimulationBrick>> inputs = new LinkedHashSet<Pair<Input, SimulationBrick>>(); for (Element e : c.getElements()) { if (e instanceof Brick) { Brick brick = (Brick) e; if (brick instanceof Component) { /* * go recursively into components and add those unconnected * inputs while ignoring inputs connected through the * component */ Component component = (Component) brick; stack.push(component); Set<Input> newIgnoredInputs = getConnectedInputsOfComponentType( component, ignoredInputs); inputs.addAll(getUnconnectedInputs(stack, component .getType().getCircuit(), newIgnoredInputs)); stack.pop(); } else { /* * look for unconnected inputs which are not ignored and add * them */ for (Input i : brick.getInputs()) { if (i.getWires().isEmpty() && !ignoredInputs.contains(i)) { inputs.add(new Pair<Input, SimulationBrick>(i, new SimulationBrick(brick, new LinkedList<Component>(stack)))); } } } } } return inputs; } /** * Calculates a {@link Set} of {@link Input}s of the {@link ComponentType} * of the given {@link Component} which are connected through the * {@link Component} because their corresponding {@link Input} at the * {@link Component} is connected. * * @param component * The {@link Component} where the resulting {@link Input}s * belong to its {@link ComponentType} * @param alreadyConnectedInputs * A {@link Set} of {@link Input}s of the component which are * somehow already connected. * @return A {@link Set} of {@link Input}s of the {@link ComponentType} of * the given {@link Component} which are connected through the * {@link Component} because their corresponding {@link Input} at * the {@link Component} is connected. */ private static Set<Input> getConnectedInputsOfComponentType(Component component, Set<Input> alreadyConnectedInputs) { Set<Input> connectedInputs = new HashSet<Input>(); for (Input i : component.getType().getInputs()) { Input outer = component.getOuterInput(i); // look if one of the inner inputs has wires on its outer inputs or // the outer input is otherwise connected => it is connected if (alreadyConnectedInputs.contains(outer) || !outer.getWires().isEmpty()) { connectedInputs.add(i); } } return connectedInputs; } /** * {@inheritDoc} * <p/> * This is done by beginning with the {@link SimulationBrick}s in * {@link SimulationImpl#rootBricks}, taking their {@link Output} values and * propagating them through the connected {@link Wire}s to the connected * {@link Input}s. {@link Input}s which are not connected will receive the * {@link SimulationImpl#DEFAULT_INPUT_VALUE}. */ @Override public void nextStep() { long time = 0; if (Profiler.ON) { time = System.currentTimeMillis(); } simulatedBricks.clear(); for (Pair<Input, SimulationBrick> inputTupel : freeInputs) { receiveValue(inputTupel, DEFAULT_INPUT_VALUE); } for (SimulationBrick identifier : rootBricks) { doSim(identifier); } if (Profiler.ON) { Profiler.simulation(Profiler.SIMULATION_STEP, System.currentTimeMillis() - time); } setChanged(); getInfo().setSimulationChanged(true); notifyObserversIfAuto(); // test simulation only with assertions enabled, but don't stop on // failure assert (testAllBricksSimulated(circuit, new LinkedList<Component>()) || Boolean .valueOf("true")); } /** * Checks if all {@link Brick}s in the given {@link Circuit} in the given * path of {@link Component}s were simulated at the last simulation step. If * not simulated {@link Brick}s are found a Warning is logged. * * @param circuit * {@link Circuit} to test. * @param stack * The current path to the {@link Component} in which the * {@link Circuit} is in. * @return test successful? (all {@link Brick}s were simulated) */ private boolean testAllBricksSimulated(Circuit circuit, Deque<Component> stack) { long time = 0; if (Profiler.ON) { time = System.currentTimeMillis(); } boolean allSimulated = true; for (Element e : circuit.getElements()) { if (e instanceof Brick) { Brick b = (Brick) e; SimulationBrick s = new SimulationBrick(b, new LinkedList<Component>(stack)); // Component? => check if the circuit in it was simulated if (b instanceof Component) { Component c = (Component) b; stack.push(c); testAllBricksSimulated(c.getType().getCircuit(), stack); stack.pop(); } else if (!simulatedBricks.contains(s) && !b.getOutputs().isEmpty()) { // warn if brick with outputs was not simulated gl().warn( "WARNING! The following SimulationBrick was not" + "simulated during this step: " + s); allSimulated = false; } } } if (Profiler.ON) { Profiler.simulation(Profiler.SIMULATION_TEST, System.currentTimeMillis() - time); } return allSimulated; } /** * Calculates the simulation for the given {@link SimulationBrick} by * propagating all the {@link Output} values through the connected * {@link Wire}s. * * @param identifier * the {@link SimulationBrick} to simulate. */ private void doSim(SimulationBrick identifier) { Brick brick = identifier.getBrick(); Deque<Component> stack = identifier.getStack(); SimulationState state = getSimulationState(identifier); for (Output o : brick.getOutputs()) { Boolean value = state.getNextValue(o); propagateValue(o, stack, value); propagateUpwards(o, stack, value); } // only add if assertions enabled assert (simulatedBricks.add(identifier) || Boolean.valueOf("true")); } /** * If the given {@link Output} belongs to an {@link Output} of the given * {@link Component} the value is propagated to this {@link Output} and * onwards from there. * * @param o * The {@link Output} where the value comes from. * @param stack * The path to the current {@link Component} in which we are at * this step in the Simulation. * @param value * The value to propagate. */ private void propagateUpwards(Output o, Deque<Component> stack, boolean value) { // propagate value to the outer component outputs if appropriate if (!stack.isEmpty()) { Component component = stack.peek(); Output outer = component.getOuterOutput(o); if (outer != null) { Deque<Component> stack2 = new LinkedList<Component>(stack); stack2.pop(); // delay it SimulationBrick identifier = new SimulationBrick(component, stack2); SimulationState s = getSimulationState(identifier); assert s instanceof ComponentSimulationState; ComponentSimulationState state = (ComponentSimulationState) s; state.addOutputValue(outer, value); if (!component.hasDelay()) { value = state.getNextValue(outer); propagateValue(outer, stack2, value); propagateUpwards(outer, stack2, value); } } } } /** * Propagates the given value from the given {@link Output} through all the * connected {@link Wire}s to the {@link SimulationBrick} the connected * {@link Input} belongs to. The calculation of the new {@link Output} * values is started if the {@link SimulationBrick} then has all * {@link Input} values it needs. If the {@link SimulationBrick} has no * delay it is simulated immediately after the calculation. * * @param source * The {@link Output} where the value comes from. * @param stack * The path to the current {@link Component} in which we are at * this step in the Simulation. * @param value * The value to propagate. */ private void propagateValue(Output source, Deque<Component> stack, boolean value) { for (Wire w : source.getWires()) { // other has to be an input. This is the case in a simulation ready // circuit Input input = (Input) w.getOther(source); Pair<Input, SimulationBrick> inputTupel = getInputTupel(input, stack); receiveValue(inputTupel, value); } } /** * Adds the given value to the {@link SimulationState} of the given * {@link SimulationBrick} at the given {@link Input}. The calculation of * the new {@link Output} values is started if the {@link SimulationBrick} * then has all {@link Input} values it needs. If the * {@link SimulationBrick} has no delay it is simulated immediately after * the calculation. * * @param inputTupel * The {@link SimulationBrick} and the {@link Input} which will * receive the given value. * @param value * The value the {@link Input} on the {@link SimulationBrick} * receives. */ private void receiveValue(Pair<Input, SimulationBrick> inputTupel, boolean value) { SimulationBrick identifier = inputTupel.getRight(); Input input = inputTupel.getLeft(); Brick brick = identifier.getBrick(); SimulationState state = getSimulationState(identifier); state.setInput(input, value); if (state.hasAllInputs()) { state.calculate(); if (!brick.hasDelay()) { doSim(identifier); } } } /** * Translates a given {@link Input} with its current path of * {@link Component}s where it is in to an {@link Input} of a non * {@link Component} and the corresponding {@link SimulatonBrick} of the * {@link Input}. Therefore it "goes down into Components". * * @param input * The {@link Input} to translate. * @param stack * The path to the current {@link Component} in which the given * {@link Input} is at this moment. * @return The resulting {@link Input} of a non {@link Component} and the * corresponding {@link SimulationBrick}. */ private Pair<Input, SimulationBrick> getInputTupel(Input input, Deque<Component> stack) { Pair<Input, SimulationBrick> inputTupel; Brick brick = input.getBrick(); // Component? => go deeper into the component if (brick instanceof Component) { Component component = (Component) brick; Input innerInput = component.getInnerInput(input); Deque<Component> stack2 = new LinkedList<Component>(stack); stack2.push(component); inputTupel = getInputTupel(innerInput, stack2); } else { // Non component => leave it as is inputTupel = new Pair<Input, SimulationBrick>(input, new SimulationBrick(brick, stack)); } return inputTupel; } /** * {@inheritDoc} */ @Override public SimulationState getSimulationState(SimulationBrick identifier) { SimulationState state = simulationStateMap.get(identifier); if (state == null) { Brick brick = identifier.getBrick(); state = SimulationStateFactory.getSimulationState(brick); assert state != null; simulationStateMap.put(identifier, state); } return state; } @Override public Simulation createSnapshot() { return new SimulationImpl(this); } @Override public void restoreFromSnapshot(Simulation snapshot) { long time = 0; if (Profiler.ON) { time = System.currentTimeMillis(); } if (snapshot instanceof SimulationImpl) { SimulationImpl snapshotCopy = (SimulationImpl) (snapshot .createSnapshot()); this.freeInputs = snapshotCopy.freeInputs; this.rootBricks = snapshotCopy.rootBricks; this.simulationStateMap = snapshotCopy.simulationStateMap; setChanged(); getInfo().setSimulationChanged(true); notifyObserversIfAuto(); } else { throw new IllegalArgumentException( "Restore is only supported for instances of SimulationImpl."); } if (Profiler.ON) { Profiler.simulation(Profiler.RESTORE_SIMULATION, System.currentTimeMillis() - time); } } @Override public String toString() { StringBuilder builder = new StringBuilder(""); for (Map.Entry<SimulationBrick, SimulationState> entries : simulationStateMap .entrySet()) { Brick brick = entries.getKey().getBrick(); Deque<Component> stack = entries.getKey().getStack(); SimulationState state = entries.getValue(); builder.append("Name: ").append(brick.getName()); builder.append("; Stack: "); for (Component component : stack) { builder.append(component.getName()).append(", "); } builder.append("; OutputValues: "); for (Output output : brick.getOutputs()) { builder.append(state.getValue(output)).append(", "); } } return builder.toString(); } @Override public void notifyObserver(SimulationModelInfo i, SimulationModelObserver obs) { obs.simulationModelChanged(this, i); } @Override public int hashCode() { return rootBricks.hashCode() + freeInputs.hashCode() + simulationStateMap.hashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof SimulationImpl)) { return false; } if (obj == this) { return true; } SimulationImpl other = (SimulationImpl) obj; boolean a = rootBricks.equals(other.rootBricks); boolean b = freeInputs.equals(other.freeInputs); boolean c = simulationStateMap.equals(other.simulationStateMap); return a && b && c; } }