diff --git a/pipe-gui/src/main/java/pipe/gui/plugin/concrete/RCATModule.java b/pipe-gui/src/main/java/pipe/gui/plugin/concrete/RCATModule.java new file mode 100644 index 00000000..68987f6b --- /dev/null +++ b/pipe-gui/src/main/java/pipe/gui/plugin/concrete/RCATModule.java @@ -0,0 +1,38 @@ +package pipe.gui.plugin.concrete; + +import pipe.gui.widget.RCATForm; +import pipe.gui.plugin.GuiModule; +import uk.ac.imperial.pipe.models.petrinet.PetriNet; + +import javax.swing.*; +import java.awt.*; + +/** + * RCAT Module that is dynamically loaded into the GUI + */ +public class RCATModule implements GuiModule{ + /** + * Starts the RCAT module + * @param petriNet current Petri net to use + */ + @Override + public void start(PetriNet petriNet) { + JFrame frame = new JFrame("RCAT for Stochastic Petri Nets"); + FileDialog selector = new FileDialog(frame, "Select petri net", FileDialog.LOAD); + frame.setContentPane(new RCATForm(petriNet, selector).getPrimPanel()); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + frame.pack(); + frame.setVisible(true); + } + + /** + * + * @return RCAT Module + */ + @Override + public String getName() { + return "RCAT for Stochastic Petri Nets"; + } + + +} diff --git a/pipe-gui/src/main/java/pipe/gui/widgets/ArcWeightEditorPanel.java b/pipe-gui/src/main/java/pipe/gui/widgets/ArcWeightEditorPanel.java index 0be820a3..7376d0f9 100644 --- a/pipe-gui/src/main/java/pipe/gui/widgets/ArcWeightEditorPanel.java +++ b/pipe-gui/src/main/java/pipe/gui/widgets/ArcWeightEditorPanel.java @@ -231,13 +231,12 @@ private void nameTextFieldFocusLost(java.awt.event.FocusEvent evt) { public void createEditorWindow(String token) { Window window = SwingUtilities.getWindowAncestor(rootPane); - EscapableDialog guiDialog = new EscapableDialog(window, "PIPE2", true); + EscapableDialog guiDialog = new EscapableDialog(window, "PIPE5", true); ArcFunctionEditor feditor = new ArcFunctionEditor(this, guiDialog, petriNetController.getPetriNet(), arcController, token); guiDialog.add(feditor); guiDialog.setSize(270, 230); guiDialog.setVisible(true); - guiDialog.dispose(); } public void setWeight(String func, String id) { diff --git a/pipe-gui/src/main/java/pipe/gui/widgets/PlaceEditorPanel.java b/pipe-gui/src/main/java/pipe/gui/widgets/PlaceEditorPanel.java index 0c07fc1f..0b9acb67 100644 --- a/pipe-gui/src/main/java/pipe/gui/widgets/PlaceEditorPanel.java +++ b/pipe-gui/src/main/java/pipe/gui/widgets/PlaceEditorPanel.java @@ -378,8 +378,8 @@ private boolean canSetCapacity() { * * @return the capacity spinner value */ - private Double getCapacitySpinnerValue() { - return (Double) capacitySpinner.getValue(); + private int getCapacitySpinnerValue() { + return (int) capacitySpinner.getValue(); } /** @@ -484,7 +484,8 @@ private void cancelButtonHandler(java.awt.event.ActionEvent evt) { * @param evt */ private void capacitySpinnerStateChanged(javax.swing.event.ChangeEvent evt) { - Double capacity = (Double) capacitySpinner.getValue(); + + int capacity = (int) capacitySpinner.getValue(); setCapacityVisible(capacity); } diff --git a/pipe-gui/src/main/java/pipe/views/ArcView.java b/pipe-gui/src/main/java/pipe/views/ArcView.java index c9d4f82e..4c5d34e7 100644 --- a/pipe-gui/src/main/java/pipe/views/ArcView.java +++ b/pipe-gui/src/main/java/pipe/views/ArcView.java @@ -91,8 +91,8 @@ private void addIntermediatePoints() { for (ArcPoint arcPoint : model.getArcPoints()) { if (!arcPath.contains(arcPoint)) { arcPath.insertIntermediatePoint(arcPoint, index); + index++; } - index++; } } diff --git a/pipe-module-gui/src/main/java/pipe/gui/rcat/BuildingBlock.java b/pipe-module-gui/src/main/java/pipe/gui/rcat/BuildingBlock.java new file mode 100644 index 00000000..718b8d91 --- /dev/null +++ b/pipe-module-gui/src/main/java/pipe/gui/rcat/BuildingBlock.java @@ -0,0 +1,133 @@ +package pipe.gui.rcat; + +import uk.ac.imperial.pipe.models.petrinet.*; + +import java.text.AttributedString; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; + +/** + * Class for generating Building Blocks - a set of Places and Transitions + * such that for every input transition there exists an output transition. + * + * @author Tanvi Potdar + */ +public class BuildingBlock { + /** + * collection of places in the building block + */ + private Collection places; + /** + * collection of transitions in the building block + */ + private Collection transitions; + /** + * symbolic input transition rates stored as strings + */ + private Map inputRates; + /** + * symbolic output transition rates stored as strings + */ + private Map outputRates; + + + /** + * constructor for the building block + * @param places + * @param transitions + */ + public BuildingBlock(Collection places, Collection transitions){ + this.places = places; + this.transitions = transitions; + } + + /** + * gets the places in the building block + * @return places + */ + public Collection getPlaces() { + return places; + } + + /** + * sets the places in the building block to the collection provided + * @param places + */ + public void setPlaces(Collection places) { + this.places = places; + } + + /** + * gets the transitions in the building block + * @return transitions + */ + public Collection getTransitions() { + return transitions; + } + + /** + * sets the transitions in the building block to the collection provided + * @param transitions + */ + public void setTransitions(Collection transitions) { + this.transitions = transitions; + } + + /** + * gets all the places and transitions in the building block + * @return places and transitions + */ + public Collection getConnectables(){ + Collection connectables = new HashSet<>(); + connectables.addAll(places); + connectables.addAll(transitions); + return connectables; + + } + + /** + * Unknown input rates represented as strings + * @param petriNet + * @return input rates of the building block + */ + public Map getInputRates(PetriNet petriNet) { + for(Transition transition: getTransitions()){ + if(petriNet.outboundArcs(transition).size()>0){ + inputRates.keySet().add(transition); + inputRates.values().add("x_" + transition.getId()); + } + } + return inputRates; + } + + /** + *Returns the known rates of the output transitions in the building block + * @param petriNet + */ + public Map getOutputRates(PetriNet petriNet) { + for(Transition transition: getTransitions()){ + if(petriNet.inboundArcs(transition).size()>0){ + outputRates.keySet().add(transition); + outputRates.values().add(transition.getRateExpr()); + } + } + return inputRates; + } + + /** + * get input rates in the building block + * @return input rates + */ + public void setInputRates(Map inputRates) { + this.inputRates = inputRates; + } + + /** + * get output rates in the building block + * @return output rates + */ + public void setOutputRates(Map outputRates) { + this.outputRates = outputRates; + } +} diff --git a/pipe-module-gui/src/main/java/pipe/gui/rcat/BuildingBlockCreator.java b/pipe-module-gui/src/main/java/pipe/gui/rcat/BuildingBlockCreator.java new file mode 100644 index 00000000..e1b704bc --- /dev/null +++ b/pipe-module-gui/src/main/java/pipe/gui/rcat/BuildingBlockCreator.java @@ -0,0 +1,162 @@ +package pipe.gui.rcat; + +import pipe.gui.widget.RCATForm; +import uk.ac.imperial.pipe.exceptions.PetriNetComponentException; +import uk.ac.imperial.pipe.models.petrinet.*; + +import java.awt.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +/** + * Controller for the RCAT Module + * @author Tanvi Potdar + */ +public class BuildingBlockCreator { + + /** + * called by the splitBB Jbutton to split the Petri Net into building blocks + * @param petriNet + * @return + * @throws PetriNetComponentException + */ + public Collection splitIntoBuildingBlocks(PetriNet petriNet) throws PetriNetComponentException { + + Collection listOfBuildingBlocks = new HashSet<>(); + + RcatPlaceVisitor rcatPlaceVisitor = new RcatPlaceVisitor(petriNet); + Collection visitedPlaces = rcatPlaceVisitor.visitedPlaces; + + for(Place place: petriNet.getPlaces()){ + if(! visitedPlaces.contains(place)){ + place.accept(rcatPlaceVisitor); + BuildingBlock buildingBlock = rcatPlaceVisitor.buildingBlock; + listOfBuildingBlocks.add(buildingBlock); + } + } + + return listOfBuildingBlocks; + + } + + /** + * class that creates a Visitor for the places in a Petri Net + * so as to delineate the actions required of a place when it + * is visited, i.e., + * 1. get its neighbours + * 2. create a building block using the place and its neighbours + */ + + private static class RcatPlaceVisitor implements PlaceVisitor { + /** + * current petri net + */ + private PetriNet petriNet; + /** + * set of places that the visitor has already visited + */ + private final Collection visitedPlaces = new HashSet<>(); + /** + * building block created for the place in question + */ + private BuildingBlock buildingBlock; + + /** + * Constructor for the RCATPlaceVisitor class + * @param petriNet in use + */ + RcatPlaceVisitor(PetriNet petriNet) { + this.petriNet = petriNet; + } + + /** + * specifies the actions that should happen when a + * place is visited by the RCATPlaceVisitor + * @param place + * @throws PetriNetComponentException + */ + @Override + public void visit(Place place) throws PetriNetComponentException { + visitedPlaces.add(place); + searchForBuildingBlock(place); + } + + /** + *creates a building block for the place in question + * @param place + * @throws PetriNetComponentException + */ + private void searchForBuildingBlock(Place place) throws PetriNetComponentException { + Collection bbPlaces = new HashSet<>(); + bbPlaces.add(place); + + for(Place neighbour: getNeighbours(place)){ + if(!visitedPlaces.contains(neighbour)){ + this.visit(neighbour); + } + bbPlaces.add(neighbour); + } + + buildingBlock = new BuildingBlock(bbPlaces, getAllTransitionsInBuildingBlock(bbPlaces)); + } + + /** + * gets all the transitions in a building block + * @param places in the building block + * @return all the transitions in each place in a Building Block + */ + public Collection getAllTransitionsInBuildingBlock(Collection places){ + Collection allTrans = new HashSet<>(); + for(Place place: places){ + for(Arc arc: petriNet.getArcs()){ + if(place.equals(arc.getSource())){ + allTrans.add((Transition)arc.getTarget()); + } + if(place.equals(arc.getTarget())){ + allTrans.add((Transition)arc.getSource()); + } + } + } + return allTrans; + } + + /** + *gets the neighbours of the place in question + * @param place + * @return the neighbours of the place, i.e , all the places that have the ~ relation with p + * The ~ relation: p1~p2 if they share inbound arcs + */ + public Iterable getNeighbours(Place place){ + Collection neighbours = new HashSet<>(); + Collection visitedTransitions = new HashSet<>(); + Collection outputTransitionsForSinglePlace = new ArrayList<>(); + + for(uk.ac.imperial.pipe.models.petrinet.Arc arc: petriNet.getArcs()){ + if(place.equals(arc.getSource())){ + outputTransitionsForSinglePlace.add((Transition) arc.getTarget()); + } + } + + for(Transition transition: outputTransitionsForSinglePlace){ + if(! (visitedTransitions.contains(transition))){ + Collection inboundArcs = petriNet.inboundArcs(transition); + for(InboundArc inboundArc : inboundArcs){ + Place inboundPlace = inboundArc.getSource(); + if(!place.equals(inboundPlace)){ + neighbours.add(inboundPlace); + } + } + + } + visitedTransitions.add(transition); + } + return neighbours; + } + + + } + + + +} diff --git a/pipe-module-gui/src/main/java/pipe/gui/widget/RCATForm.form b/pipe-module-gui/src/main/java/pipe/gui/widget/RCATForm.form new file mode 100644 index 00000000..3d95db90 --- /dev/null +++ b/pipe-module-gui/src/main/java/pipe/gui/widget/RCATForm.form @@ -0,0 +1,191 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pipe-module-gui/src/main/java/pipe/gui/widget/RCATForm.java b/pipe-module-gui/src/main/java/pipe/gui/widget/RCATForm.java new file mode 100644 index 00000000..754ac778 --- /dev/null +++ b/pipe-module-gui/src/main/java/pipe/gui/widget/RCATForm.java @@ -0,0 +1,392 @@ +package pipe.gui.widget; + +import pipe.gui.rcat.BuildingBlock; +import pipe.gui.rcat.BuildingBlockCreator; +import uk.ac.imperial.pipe.exceptions.PetriNetComponentException; +import uk.ac.imperial.pipe.io.PetriNetIOImpl; +import uk.ac.imperial.pipe.io.PetriNetReader; +import uk.ac.imperial.pipe.models.petrinet.*; + + +import javax.swing.*; +import javax.xml.bind.JAXBException; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * View class for the RCAT Module + * @author Tanvi Potdar + * + */ +public class RCATForm { + /** + * main JPanel + */ + private JPanel primPanel; + /** + * radio button to choose to use existing petri net + */ + private JRadioButton useExisting; + /** + * radio buttons to enable the user to load petri nets from file + */ + private JRadioButton loadPetriNetFromRadioButton; + /** + * text field into which the petri net name appears + */ + private JTextField loadpn; + /** + * JButton to calculate rates for passive transitions in the Building Block + */ + private JButton calculatePassiveTransitionRates; + /** + * JButton to split appropriate petri netz into Building Blocks + */ + private JButton splitBB; + private JLabel petriNetTextLabel; + private JPanel resultsP; + /** + * Text area to print results + */ + private JTextArea evalTextArea; + private JLabel resultsLabel; + private JTextArea ratesTextArea; + private final FileDialog loadPetriNetFromFile; + /** + * petri net loaded via the load dialog + */ + private PetriNet lastloaded; + /** + * default petri net/ existing petri net + */ + private PetriNet defaultPetriNet; + /** + * petri net being evaluated currently + */ + private PetriNet petriNet; + + private static final Logger LOGGER = Logger.getLogger(RCATForm.class.getName()); + + /** + * Sets up the load Petri net options with the "use current Petri net" disabled + * @param loadDialog creates file dialog to select petri net + */ + public RCATForm(FileDialog loadDialog){ + this.loadPetriNetFromFile = loadDialog; + useExisting.setEnabled(false); + setUp(); + } + /** + * Sets up the load Petri net options with "use existing Petri net" set to + * the petriNet parameter + * + * @param petriNet current Petri net + * @param loadDialog + */ + public RCATForm(PetriNet petriNet, FileDialog loadDialog) { + defaultPetriNet = petriNet; + this.loadPetriNetFromFile = loadDialog; + setUp(); + } + + /** + * Defines action listeners for the radio and J-buttons: + * 1.load petri net from file + * 2.split petri net into building blocks + * 3.calculate rates for passive transitions + */ + public void setUp(){ + loadPetriNetFromRadioButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + loadData(); + } + }); + splitBB.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setPetriNet(); + splitIntoBuildingBlocks(); + } + }); + calculatePassiveTransitionRates.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + calculatePassiveTransitionRates(); + } + }); + + } + + /** + * Converts the list of building blocks into String + * @param buildingBlocks + * @return list of building blocks in string form + */ + public String toString(Collection buildingBlocks){ + StringBuilder stringBuilder = new StringBuilder(); + for(BuildingBlock buildingBlock: buildingBlocks){ + for(Connectable c: buildingBlock.getConnectables()){ + stringBuilder.append(" "+c.getId()+" "); + } + stringBuilder.append("\n"); + } + return stringBuilder.toString(); + + } + + /** + * checks if the petri net satisfies POSPN conditions + * @param petriNet is the petri net in use + * @return true if the petri net satisfies POSPN conditions and false + * if it doesn't + */ + //TODO: Fill in conditions for POSPN + public boolean checkIfPOSPN(PetriNet petriNet){ + return false; + } + + /** + * calls the BuildingBlockCreator class to split the current PN into Building Blocks + * sets text area to the places in each building block + * tests if a PN satisfies RCAT by checking if it can be split into Building Blocks + */ + private void splitIntoBuildingBlocks() { + try { + Collection listOfBuildingBlocks = new BuildingBlockCreator().splitIntoBuildingBlocks(petriNet); + if(checkIfPOSPN(petriNet)==true){ + evalTextArea.setText("This Petri Net cannot be split into Building Blocks because it is not a POSPN. " + + "\n"+ "In a POSPN, for every input transition, there exists an output transition. " + "\n" + + "This Petri Net does not satisfy those conditions and hence its rate equations cannot be solved by RCAT." + ); + } + else evalTextArea.setText("Here are the building blocks of this Petri Net"+"\n"+toString(listOfBuildingBlocks) + + "\n"+ "Each Building Block satisfies the conditions:" + "\n" + "1.Every transition is an input or an output transition" + + "\n" + "2. For every input transition, there exists an output transition" + "\n" + "3. The Petri Net has to be completely connected." + + "\n" +"As a result, the rate equations of this Petri Net can be solved by RCAT."); + } + catch (PetriNetComponentException e) { + e.printStackTrace(); + } + } + + /** + * sets the petriNet variable to the petriNet in use(existing vs loaded) + */ + private void setPetriNet() { + petriNet = useExisting.isSelected() ? defaultPetriNet : lastloaded; + } + + + /** + * Gets the current petri net + * @return petri net in use + */ + public PetriNet getPetriNet(){ + return petriNet; + } + + /** + * Opens the file dialog and saves the selected Petri net into lastLoadedPetriNet + * for use when calculating the state space exploration + */ + private void loadData() { + useExisting.setSelected(false); + loadPetriNetFromRadioButton.setSelected(true); + loadPetriNetFromFile.setMode(FileDialog.LOAD); + loadPetriNetFromFile.setTitle("Select petri net"); + loadPetriNetFromFile.setVisible(true); + File[] files = loadPetriNetFromFile.getFiles(); + if (files.length > 0) { + File path = files[0]; + try { + loadpn.setText(path.getName()); + PetriNetReader petriNetIO = new PetriNetIOImpl(); + lastloaded = petriNetIO.read(path.getAbsolutePath()); + } catch (JAXBException | FileNotFoundException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + } + } + } + + /** + * returns the Primary Panel in the UI + * @return primary panel + */ + public JPanel getPrimPanel(){ + return primPanel; + } + + /** + * calculates the rates for passive transitions + */ + public void calculatePassiveTransitionRates(){ + StringBuilder outputProductForm = new StringBuilder(); + Collection stringBuilders = new HashSet<>(); + try { + Collection listOfBuildingBlocks = new BuildingBlockCreator().splitIntoBuildingBlocks(petriNet); + for(BuildingBlock buildingBlock: listOfBuildingBlocks){ + if(buildingBlock.getPlaces().size()==1){ + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(generateSSPDForMM1(buildingBlock)); + stringBuilders.add(stringBuilder); + } + if(buildingBlock.getPlaces().size()>1){ + stringBuilders.addAll(ProductForm(buildingBlock)); + outputProductForm.append(ProductForm(buildingBlock)); + } + + } + } catch (PetriNetComponentException e) { + e.printStackTrace(); + } + + ratesTextArea.setText(stringBuilders.toString()); + + } + + /** + * Returns all the arcs in the building block provided + * @param buildingBlock + * @return all the arcs in the building block + */ + public Collection getBuildingBlockArcs(BuildingBlock buildingBlock){ + Collection arcs = new HashSet<>(); + for(Arc arc: petriNet.getArcs()){ + for(Connectable c: buildingBlock.getConnectables()){ + if(c.equals(arc.getSource())||c.equals(arc.getTarget())){ + arcs.add(arc); + } + } + } + return arcs; + } + + /** + * Returns all the inbound arcs(from place to transition) in the building block + * @param buildingBlock + * @return all inbound arcs + */ + public Collection getInboundArcsInBuildingBlock(BuildingBlock buildingBlock){ + Collection inboundArcsinBuildingBlock = new HashSet<>(); + for(Arc arc: getBuildingBlockArcs(buildingBlock)){ + for(Transition transition: buildingBlock.getTransitions()){ + for(Place place: buildingBlock.getPlaces()){ + if(transition.equals(arc.getTarget())&&place.equals(arc.getSource())){ + inboundArcsinBuildingBlock.add((InboundArc)arc); + } + } + } + } + return inboundArcsinBuildingBlock; + } + + /** + * Returns all outbound arcs(from transition to place) in the building block + * @param buildingBlock + * @return all outbound arcs + */ + public Collection getOutboundArcsInBuildingBlock(BuildingBlock buildingBlock){ + Collection outboundArcsinBuildingBlock = new HashSet<>(); + for(Arc arc: getBuildingBlockArcs(buildingBlock)){ + for(Transition transition: buildingBlock.getTransitions()){ + for(Place place: buildingBlock.getPlaces()){ + if(transition.equals(arc.getSource())&&place.equals(arc.getTarget())){ + outboundArcsinBuildingBlock.add((OutboundArc)arc); + } + } + } + } + return outboundArcsinBuildingBlock; + } + + /** + * Generates the Steady State Probability Distribution for a building block + * with one place, i.e., an MM1 queue + * @param buildingBlock + * @return the sspd equation for an MM1 queue in the form of a string + */ + public String generateSSPDForMM1(BuildingBlock buildingBlock){ + String output = new String(); + StringBuilder numerator = new StringBuilder(); + StringBuilder denominator = new StringBuilder(); + if(buildingBlock.getTransitions().size()==2) { + for (OutboundArc outboundArc : getOutboundArcsInBuildingBlock(buildingBlock)) { + numerator.append(outboundArc.getSource().getId()); + } + for (InboundArc inboundArc : getInboundArcsInBuildingBlock(buildingBlock)) { + denominator.append(inboundArc.getTarget().getId()+"\n"); + } + } + else if(buildingBlock.getTransitions().size()>2) { + for (OutboundArc outboundArc : getOutboundArcsInBuildingBlock(buildingBlock)) { + numerator.append(outboundArc.getSource().getId() + "+"); + } + for (InboundArc inboundArc : getInboundArcsInBuildingBlock(buildingBlock)) { + denominator.append(inboundArc.getTarget().getId() + "+"+"\n"); + } + } + output = "x_"+numerator+"="+"x_"+denominator; + return output; + } + + /** + * generates the product form rate equations for building blocks + * @param buildingBlock + * @return rate equations + */ + public Collection ProductForm(BuildingBlock buildingBlock){ + StringBuilder stringBuilder = new StringBuilder(),rate = new StringBuilder(); + Collection stringBuilders = new HashSet<>(),rates = new HashSet<>(); + Collection inputTransitions = new HashSet<>(), outputTransitions=new HashSet<>(); + for(Arc arc: getOutboundArcsInBuildingBlock(buildingBlock)){ + inputTransitions.add((Transition)arc.getSource()); + } + for(Arc arc: getInboundArcsInBuildingBlock(buildingBlock)){ + outputTransitions.add((Transition)arc.getTarget()); + + } + for(Transition i_transition:inputTransitions){ + for(Transition o_transition: outputTransitions){ + if(petriNet.outboundArcs(i_transition).size()==petriNet.inboundArcs(o_transition).size()){ + for(Arc arc: petriNet.outboundArcs(i_transition)){ + for (Arc arc1: petriNet.inboundArcs(o_transition)){ + if(arc.getTarget().equals(arc1.getSource())){ + stringBuilder.append(i_transition.getId()+"/"+o_transition.getId()+" "); + stringBuilders.add(stringBuilder); + rate.append("x_"+i_transition.getId()+" = "+"x_"+o_transition.getId()+"\n"); + rates.add(rate); + } + } + } + } + } + } + return rates; + } + + + + + + } + + + + + + + + + + + + + +