package pipe.gui.analysis;
import org.rendersnake.HtmlCanvas;
import pipe.gui.widget.GenerateResultsForm;
import pipe.gui.widget.HTMLPane;
import pipe.gui.widget.StateSpaceLoader;
import pipe.gui.widget.StateSpaceLoaderException;
import pipe.reachability.algorithm.*;
import pipe.steadystate.algorithm.ParallelGaussSeidel;
import pipe.steadystate.algorithm.SteadyStateSolver;
import pipe.steadystate.metrics.TokenMetrics;
import pipe.steadystate.metrics.TransitionMetrics;
import uk.ac.imperial.pipe.exceptions.InvalidRateException;
import uk.ac.imperial.pipe.models.petrinet.PetriNet;
import uk.ac.imperial.state.ClassifiedState;
import uk.ac.imperial.state.Record;
import javax.swing.*;
import java.awt.FileDialog;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.rendersnake.HtmlAttributesFactory.class_;
import static org.rendersnake.HtmlAttributesFactory.type;
/**
* Performs the exploration and steady state analysis of a Petri net.
* Displays useful performance analysis metrics
*/
public class GSPNAnalysis {
public static final String HTML_STYLE = "body{font-family:Arial,Helvetica,sans-serif;text-align:center;" +
"background:#ffffff}" +
"td.colhead{font-weight:bold;text-align:center;" +
"background:#ffffff}" +
"td.rowhead{font-weight:bold;background:#ffffff}" +
"td.cell{text-align:center;padding:5px,0}" +
"tr.even{background:#a0a0d0}" +
"tr.odd{background:#c0c0f0}" +
"td.empty{background:#ffffff}";
/**
* If the state space is larger than this then we cannot display the results as tables.
*/
private static final int MAX_DISPLAY_STATES = 200;
/**
* Results HTML pane for displaying info
*/
HTMLPane resultsPane = new HTMLPane();
private JLabel textResultsLabel;
private JButton goButton;
private JPanel mainPanel;
/**
* Results panel houses HTML results
*/
private JPanel resultsPanel;
private JPanel loadPanel;
private JPanel generatePanel;
private StateSpaceLoader stateSpaceLoader;
public GSPNAnalysis(FileDialog fileDialog) {
stateSpaceLoader = new StateSpaceLoader(fileDialog);
setUp();
}
/**
* Sets up the UI
*/
private void setUp() {
loadPanel.add(stateSpaceLoader.getMainPanel(), 0);
resultsPanel.add(resultsPane);
GenerateResultsForm generateResultsForm = new GenerateResultsForm(new GenerateResultsForm.GoAction() {
@Override
public void go(int threads) {
showSteadyState(threads);
}
});
generatePanel.add(generateResultsForm.getPanel());
}
/**
* Loads the steady state and if the number of states is < MAX_DISPLAY_STATES we display steady state information
*/
private void showSteadyState(int threads) {
try {
StateSpaceExplorer.StateSpaceExplorerResults results =
stateSpaceLoader.calculateResults(new StateSpaceLoader.ExplorerCreator() {
@Override
public ExplorerUtilities create(PetriNet petriNet) {
return new BoundedExplorerUtilities(petriNet, 1000000);
}
}, new StateSpaceLoader.VanishingExplorerCreator() {
@Override
public VanishingExplorer create(ExplorerUtilities utils) {
return new OnTheFlyVanishingExplorer(utils);
}
}, threads
);
displayResultsOnCanvas(results);
} catch (IOException | InterruptedException | ExecutionException | InvalidRateException | TimelessTrapException e) {
e.printStackTrace();
} catch (StateSpaceLoaderException e) {
JOptionPane.showMessageDialog(mainPanel, e.getMessage(), "GSPN Analysis Error", JOptionPane.ERROR_MESSAGE);
}
}
private void displayResultsOnCanvas(StateSpaceExplorer.StateSpaceExplorerResults results)
throws IOException, StateSpaceLoaderException {
HtmlCanvas html = new HtmlCanvas();
html.html().head();
html.style(type("text/css").media("screen")).content(HTML_STYLE);
html._head();
html.body();
if (results.numberOfStates < MAX_DISPLAY_STATES) {
StateSpaceLoader.Results stateSpace = stateSpaceLoader.loadStateSpace();
solveSteadyState(stateSpace.records, stateSpace.stateMappings, html);
} else {
html.write("State space is too large to show tabular results");
html.br();
html.write("Number of states: " + results.numberOfStates);
html.br();
html.write("Number of transitions: " + results.processedTransitions);
}
html._body()._html();
resultsPane.setText(html.toHtml());
}
/**
* Solves the steady state and adds the results to the html canvas
*
* @param records
* @param stateMappings
* @param html
*/
private void solveSteadyState(Collection<Record> records, Map<Integer, ClassifiedState> stateMappings,
HtmlCanvas html) {
ExecutorService executorService = Executors.newFixedThreadPool(8);
try {
displayStates(html, stateMappings);
SteadyStateSolver steadyStateSolver = new ParallelGaussSeidel(8, executorService, 10);
Map<Integer, Double> steadyState = steadyStateSolver.solve(new ArrayList<>(records));
displaySteadyState(html, steadyState);
displayMetrics(html, steadyState, stateMappings);
} catch (IOException e) {
e.printStackTrace();
} finally {
executorService.shutdownNow();
}
}
/**
* Displays the state mappings for each token
*
* @param html
* @param stateMappings
* @throws IOException
*/
private void displayStates(HtmlCanvas html, Map<Integer, ClassifiedState> stateMappings) throws IOException {
Collection<String> tokens = getTokens(stateMappings.values());
for (String token : tokens) {
buildTokenTable(html, stateMappings, token);
}
}
/**
* Displays the steady state information in a table
*
* @param html
* @param steadyState
* @throws IOException
*/
private void displaySteadyState(HtmlCanvas html, Map<Integer, Double> steadyState) throws IOException {
List<TableRow> rows = new LinkedList<>();
for (Map.Entry<Integer, Double> entry : steadyState.entrySet()) {
rows.add(new TableRow(entry.getKey().toString(), doubleToString(entry.getValue())));
}
addTable(html, rows, Arrays.asList("State", "Distribution"), "Steady state distribution");
}
/**
* Display Performance analysis metrics for the steady state
* <p/>
* Displays:
* - the average number of tokens on each place
* - the average transition throughput if loaded from a Petri net
*
* @param html
* @param steadyState
* @param stateMappings
* @throws IOException
*/
private void displayMetrics(HtmlCanvas html, Map<Integer, Double> steadyState,
Map<Integer, ClassifiedState> stateMappings) throws IOException {
Map<String, Map<String, Double>> averageTokens = TokenMetrics.averageTokensOnPlace(stateMappings, steadyState);
buildAverageMetrics(averageTokens, html);
if (!stateSpaceLoader.isBinaryLoadChecked()) {
PetriNet petriNet = stateSpaceLoader.getPetriNet();
Map<String, Double> throughputs =
TransitionMetrics.getTransitionThroughput(stateMappings, steadyState, petriNet);
displayThroughputs(throughputs, html);
}
}
/**
* @param states
* @return a sorted list of tokens contained within the states
*/
private Collection<String> getTokens(Collection<ClassifiedState> states) {
Collection<String> tokens = new ArrayList<>();
if (!states.isEmpty()) {
ClassifiedState state = states.iterator().next();
Collection<String> places = state.getPlaces();
if (!places.isEmpty()) {
String place = places.iterator().next();
tokens.addAll(state.getTokens(place).keySet());
}
}
return tokens;
}
/**
* @param html
* @param stateMappings
* @param token
* @throws IOException
*/
private void buildTokenTable(HtmlCanvas html, Map<Integer, ClassifiedState> stateMappings, String token)
throws IOException {
List<Integer> ids = new ArrayList<>(stateMappings.keySet());
Collections.sort(ids);
List<String> places = getPlaces(stateMappings);
List<TableRow> rows = new ArrayList<>();
for (Integer id : ids) {
ClassifiedState state = stateMappings.get(id);
TableRow row = new TableRow(id.toString());
for (String place : places) {
Integer count = state.getTokens(place).get(token);
row.addCell(count.toString());
}
rows.add(row);
}
places.add(0, "State");
addTable(html, rows, places, "State markings for " + token + " token");
}
/**
* @param value
* @return string representation rounded to 3 decimal places
*/
private String doubleToString(Double value) {
return String.format("%.3f", value);
}
/**
* Add the table to the HTML canvas ready for rendering
*
* @param html html canvas
* @param tableRows table rows, these should all be the same lenght
* @param headers table headers
* @param title itle of the table
* @throws IOException if IO error occurs
*/
public void addTable(HtmlCanvas html, List<TableRow> tableRows, List<String> headers, String title)
throws IOException {
html.h2().content(title);
html.table();
html.tr();
for (String header : headers) {
html.th().content(header);
}
html._tr();
int i = 0;
for (TableRow row : tableRows) {
String clazz = (i % 2 == 0) ? "even" : "odd";
html.tr(class_(clazz));
for (String column : row.getCells()) {
html.td().content(column);
}
html._tr();
i++;
}
html._table();
}
/**
* Creates and adds to the html canvas a table for each token colour
* containing the average number of tokens in the place
*
* @param averageTokens
* @param html
* @throws IOException
*/
private void buildAverageMetrics(Map<String, Map<String, Double>> averageTokens, HtmlCanvas html)
throws IOException {
List<String> places = new ArrayList<>(averageTokens.keySet());
Collections.sort(places);
List<String> tokens = new ArrayList<>(averageTokens.get(places.get(0)).keySet());
Collections.sort(tokens);
List<TableRow> rows = new ArrayList<>();
for (String place : places) {
Map<String, Double> average = averageTokens.get(place);
TableRow row = new TableRow(place);
for (String token : tokens) {
Double count = average.get(token);
row.addCell(doubleToString(count));
}
rows.add(row);
}
tokens.add(0, "Place");
addTable(html, rows, tokens, "Average token counts");
}
/**
* Creates and displays a table for the given throughputs
*
* @param throughputs
* @param html
* @throws IOException
*/
private void displayThroughputs(Map<String, Double> throughputs, HtmlCanvas html) throws IOException {
List<String> transitions = new ArrayList<>(throughputs.keySet());
Collections.sort(transitions);
List<TableRow> rows = new ArrayList<>();
for (String transition : transitions) {
Double average = throughputs.get(transition);
rows.add(new TableRow(transition, doubleToString(average)));
}
addTable(html, rows, Arrays.asList("Transition", "Throughput"), "Average timed transition throughputs");
}
/**
* @param stateMappings
* @return a list of places in the state mappings
*/
private List<String> getPlaces(Map<Integer, ClassifiedState> stateMappings) {
List<String> places = new ArrayList<>();
places.addAll(stateMappings.values().iterator().next().getPlaces());
Collections.sort(places);
return places;
}
public GSPNAnalysis(PetriNet petriNet, FileDialog fileDialog) {
stateSpaceLoader = new StateSpaceLoader(petriNet, fileDialog);
setUp();
}
/**
* Main method for running this externally without PIPE
*
* @param args command line arguments
*/
public static void main(String[] args) {
JFrame frame = new JFrame("Steady state results");
FileDialog selector = new FileDialog(frame, "Select petri net", FileDialog.LOAD);
frame.setContentPane(new GSPNAnalysis(selector).mainPanel);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
public JPanel getMainPanel() {
return mainPanel;
}
/**
* Useful class for creating fors for HTML tables displayed in the output
*/
public static class TableRow {
/**
* Table row cell values
*/
List<String> cells = new ArrayList<>();
/**
* @param cells to add
*/
public TableRow(String... cells) {
this.cells.addAll(Arrays.asList(cells));
}
/**
* Default constructor with no cells
*/
public TableRow() {
}
/**
* @return cells
*/
public List<String> getCells() {
return cells;
}
/**
* Append this value onto the cells, that is this value will become the last column
*
* @param cell to add
*/
public void addCell(String cell) {
cells.add(cell);
}
}
}