From 91520fd4125d64ba73c8a5750efaea2cf006e488 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Fri, 27 Dec 2024 10:09:21 +0000 Subject: [PATCH 01/44] Bugfix: Export settings were being ignored. Global export options (matlab/rows) were being ignored in command line after recent refactoring. --- prism/src/io/ModelExportOptions.java | 3 +++ prism/src/prism/PrismCL.java | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/prism/src/io/ModelExportOptions.java b/prism/src/io/ModelExportOptions.java index 30eb28e514..cc5923385a 100644 --- a/prism/src/io/ModelExportOptions.java +++ b/prism/src/io/ModelExportOptions.java @@ -177,6 +177,9 @@ public void apply(ModelExportOptions other) if (other.showActions.isPresent()) { setShowActions(other.getShowActions()); } + if (other.printHeaders.isPresent()) { + setPrintHeaders(other.getPrintHeaders()); + } if (other.explicitRows.isPresent()) { setExplicitRows(other.getExplicitRows()); } diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 273f666fef..9b8f165e9c 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -841,7 +841,7 @@ private void doExports() if (exporttrans) { try { File f = (exportTransFilename.equals("stdout")) ? null : new File(exportTransFilename); - exportTransOptions.applyTo(modelExportOptionsGlobal); + exportTransOptions.apply(modelExportOptionsGlobal); prism.exportBuiltModelTransitions(f, exportTransOptions); } // in case of error, report it and proceed @@ -856,7 +856,7 @@ private void doExports() if (exportstaterewards) { try { File f = (exportStateRewardsFilename.equals("stdout")) ? null : new File(exportStateRewardsFilename); - exportStateRewardsOptions.applyTo(modelExportOptionsGlobal); + exportStateRewardsOptions.apply(modelExportOptionsGlobal); prism.exportBuiltModelStateRewards(f, exportStateRewardsOptions); } // in case of error, report it and proceed @@ -871,7 +871,7 @@ private void doExports() if (exporttransrewards) { try { File f = (exportTransRewardsFilename.equals("stdout")) ? null : new File(exportTransRewardsFilename); - exportTransRewardsOptions.applyTo(modelExportOptionsGlobal); + exportTransRewardsOptions.apply(modelExportOptionsGlobal); prism.exportBuiltModelTransRewards(f, exportTransRewardsOptions); } // in case of error, report it and proceed @@ -886,7 +886,7 @@ private void doExports() if (exportstates) { try { File f = (exportStatesFilename.equals("stdout")) ? null : new File(exportStatesFilename); - exportStatesOptions.applyTo(modelExportOptionsGlobal); + exportStatesOptions.apply(modelExportOptionsGlobal); prism.exportBuiltModelStates(f, exportStatesOptions); } // in case of error, report it and proceed @@ -901,8 +901,8 @@ private void doExports() if (exportobservations) { try { File f = (exportObservationsFilename.equals("stdout")) ? null : new File(exportObservationsFilename); - exportObservationsOptions.applyTo(modelExportOptionsGlobal); - prism.exportBuiltModelObservations(f, exportStatesOptions); + exportObservationsOptions.apply(modelExportOptionsGlobal); + prism.exportBuiltModelObservations(f, exportObservationsOptions); } // in case of error, report it and proceed catch (FileNotFoundException e) { @@ -991,11 +991,11 @@ private void doExports() // export labels from model and properties to same file definedPFConstants = undefinedMFConstants.getPFConstantValues(); propertiesFile.setSomeUndefinedConstants(definedPFConstants, exactConstants); - exportLabelsOptions.applyTo(modelExportOptionsGlobal); + exportLabelsOptions.apply(modelExportOptionsGlobal); prism.exportBuiltModelLabels(propertiesFile, f, exportLabelsOptions); } else { // export labels from model only - exportLabelsOptions.applyTo(modelExportOptionsGlobal); + exportLabelsOptions.apply(modelExportOptionsGlobal); prism.exportBuiltModelLabels(null, f, exportLabelsOptions); } } @@ -1017,7 +1017,7 @@ private void doExports() // export labels from properties file definedPFConstants = undefinedMFConstants.getPFConstantValues(); propertiesFile.setSomeUndefinedConstants(definedPFConstants, exactConstants); - exportLabelsOptions.applyTo(modelExportOptionsGlobal); + exportLabelsOptions.apply(modelExportOptionsGlobal); prism.exportBuiltModelPropLabels(propertiesFile, f, exportLabelsOptions); } // in case of error, report it and proceed @@ -1032,7 +1032,7 @@ private void doExports() if (exportmodelcombined) { try { File f = (exportModelCombinedFilename.equals("stdout")) ? null : new File(exportModelCombinedFilename); - exportModelCombinedOptions.applyTo(modelExportOptionsGlobal); + exportModelCombinedOptions.apply(modelExportOptionsGlobal); prism.exportBuiltModelCombined(f, exportModelCombinedOptions); } // in case of error, report it and proceed From 38c123128a1a740c8affb572166600c54b04969a Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Thu, 2 Jan 2025 09:56:01 +0000 Subject: [PATCH 02/44] Move ModelExportFormat enum out to own class. --- prism/src/explicit/DTMCModelChecker.java | 5 ++-- prism/src/explicit/MDPModelChecker.java | 7 +++--- prism/src/explicit/StateModelChecker.java | 9 ++++--- prism/src/io/ModelExportFormat.java | 25 +++++++++++++++++++ prism/src/io/ModelExportOptions.java | 30 +++-------------------- prism/src/prism/Prism.java | 2 +- prism/src/prism/PrismCL.java | 2 +- prism/src/prism/TestModelGenerator.java | 3 ++- 8 files changed, 43 insertions(+), 40 deletions(-) create mode 100644 prism/src/io/ModelExportFormat.java diff --git a/prism/src/explicit/DTMCModelChecker.java b/prism/src/explicit/DTMCModelChecker.java index 6275b8df0e..896ccd8883 100644 --- a/prism/src/explicit/DTMCModelChecker.java +++ b/prism/src/explicit/DTMCModelChecker.java @@ -46,12 +46,11 @@ import explicit.rewards.MCRewards; import explicit.rewards.MDPRewards; import explicit.rewards.Rewards; -import io.ModelExportOptions; +import io.ModelExportFormat; import parser.ast.Expression; import prism.AccuracyFactory; import prism.ModelType; import prism.OptionsIntervalIteration; -import prism.Prism; import prism.PrismComponent; import prism.PrismException; import prism.PrismFileLog; @@ -597,7 +596,7 @@ public ModelCheckerResult computeReachProbs(DTMC dtmc, BitSet remain, Bi List labelNames = Arrays.asList("init", "target"); mainLog.println("\nExporting target states info to file \"" + getExportTargetFilename() + "\"..."); PrismLog out = new PrismFileLog(getExportTargetFilename()); - exportLabels(dtmc, labelNames, labels, out, ModelExportOptions.ModelExportFormat.EXPLICIT); + exportLabels(dtmc, labelNames, labels, out, ModelExportFormat.EXPLICIT); out.close(); } diff --git a/prism/src/explicit/MDPModelChecker.java b/prism/src/explicit/MDPModelChecker.java index 8226c19b06..be400fade6 100644 --- a/prism/src/explicit/MDPModelChecker.java +++ b/prism/src/explicit/MDPModelChecker.java @@ -47,12 +47,11 @@ import explicit.rewards.MCRewardsFromMDPRewards; import explicit.rewards.MDPRewards; import explicit.rewards.Rewards; -import io.ModelExportOptions; +import io.ModelExportFormat; import parser.ast.Expression; import parser.type.TypeDouble; import prism.AccuracyFactory; import prism.OptionsIntervalIteration; -import prism.Prism; import prism.PrismComponent; import prism.PrismDevNullLog; import prism.PrismException; @@ -390,7 +389,7 @@ public ModelCheckerResult computeReachProbs(MDP mdp, BitSet remain, BitS List labelNames = Arrays.asList("init", "target"); mainLog.println("\nExporting target states info to file \"" + getExportTargetFilename() + "\"..."); PrismLog out = new PrismFileLog(getExportTargetFilename()); - exportLabels(mdp, labelNames, labels, out, ModelExportOptions.ModelExportFormat.EXPLICIT); + exportLabels(mdp, labelNames, labels, out, ModelExportFormat.EXPLICIT); out.close(); } @@ -2122,7 +2121,7 @@ public ModelCheckerResult computeReachRewards(MDP mdp, MDPRewards labelNames = Arrays.asList("init", "target"); mainLog.println("\nExporting target states info to file \"" + getExportTargetFilename() + "\"..."); PrismLog out = new PrismFileLog(getExportTargetFilename()); - exportLabels(mdp, labelNames, labels, out, ModelExportOptions.ModelExportFormat.EXPLICIT); + exportLabels(mdp, labelNames, labels, out, ModelExportFormat.EXPLICIT); out.close(); } diff --git a/prism/src/explicit/StateModelChecker.java b/prism/src/explicit/StateModelChecker.java index 2f676a67da..fd2b87108b 100644 --- a/prism/src/explicit/StateModelChecker.java +++ b/prism/src/explicit/StateModelChecker.java @@ -42,6 +42,7 @@ import io.DotExporter; import io.DRNExporter; import io.MatlabExporter; +import io.ModelExportFormat; import io.ModelExportOptions; import io.PrismExplicitExporter; import io.PrismExplicitImporter; @@ -1528,7 +1529,7 @@ public static Map loadLabelsFile(String filename) throws PrismEx */ public void exportModelCombined(Model model, List labelNames, PrismLog out, ModelExportOptions exportOptions) throws PrismException { - if (exportOptions.getFormat() != ModelExportOptions.ModelExportFormat.DRN) { + if (exportOptions.getFormat() != ModelExportFormat.DRN) { return; } List> rewards = new ArrayList<>(); @@ -1569,7 +1570,7 @@ public void exportTransitions(Model model, PrismLog out, ModelExp */ public void exportStateRewards(Model model, int r, PrismLog out, ModelExportOptions exportOptions) throws PrismException { - if (exportOptions.getFormat() != ModelExportOptions.ModelExportFormat.EXPLICIT) { + if (exportOptions.getFormat() != ModelExportFormat.EXPLICIT) { throw new PrismNotSupportedException("Exporting state rewards in the requested format is currently not supported by the explicit engine"); } @@ -1587,7 +1588,7 @@ public void exportStateRewards(Model model, int r, PrismLog out, */ public void exportTransRewards(Model model, int r, PrismLog out, ModelExportOptions exportOptions) throws PrismException { - if (exportOptions.getFormat() != ModelExportOptions.ModelExportFormat.EXPLICIT) { + if (exportOptions.getFormat() != ModelExportFormat.EXPLICIT) { throw new PrismNotSupportedException("Exporting transition rewards in the requested format is currently not supported by the explicit engine"); } @@ -1669,7 +1670,7 @@ private List checkLabels(Model model, List labelNames) throws * @param out Where to export * @param format The format in which to export */ - public void exportLabels(Model model, List labelNames, List labelStates, PrismLog out, ModelExportOptions.ModelExportFormat format) throws PrismException + public void exportLabels(Model model, List labelNames, List labelStates, PrismLog out, ModelExportFormat format) throws PrismException { exportLabels(model, labelNames, labelStates, out, new ModelExportOptions(format)); } diff --git a/prism/src/io/ModelExportFormat.java b/prism/src/io/ModelExportFormat.java new file mode 100644 index 0000000000..24b9202cae --- /dev/null +++ b/prism/src/io/ModelExportFormat.java @@ -0,0 +1,25 @@ +package io; + +/** + * Model export formats + */ +public enum ModelExportFormat +{ + EXPLICIT, MATLAB, DOT, DRN; + + public String description() + { + switch (this) { + case EXPLICIT: + return "in plain text format"; + case MATLAB: + return "in Matlab format"; + case DOT: + return "in Dot format"; + case DRN: + return "in DRN format"; + default: + return this.toString(); + } + } +} diff --git a/prism/src/io/ModelExportOptions.java b/prism/src/io/ModelExportOptions.java index cc5923385a..6e499b0f30 100644 --- a/prism/src/io/ModelExportOptions.java +++ b/prism/src/io/ModelExportOptions.java @@ -35,32 +35,10 @@ */ public class ModelExportOptions implements Cloneable { - /** - * Model export formats - */ - public enum ModelExportFormat { - EXPLICIT, MATLAB, DOT, DRN; - public String description() - { - switch (this) { - case EXPLICIT: - return "in plain text format"; - case MATLAB: - return "in Matlab format"; - case DOT: - return "in Dot format"; - case DRN: - return "in DRN format"; - default: - return this.toString(); - } - } - } - /** * Model export format */ - private Optional format = Optional.empty(); + private Optional format = Optional.empty(); /** * Precision to export probabilities/etc. (number of significant decimal places) @@ -99,7 +77,7 @@ public ModelExportOptions() /** * Construct a StrategyExportOptions with specified format and default options. */ - public ModelExportOptions(ModelExportOptions.ModelExportFormat format) + public ModelExportOptions(ModelExportFormat format) { setFormat(format); } @@ -109,7 +87,7 @@ public ModelExportOptions(ModelExportOptions.ModelExportFormat format) /** * Set the model export format. */ - public ModelExportOptions setFormat(ModelExportOptions.ModelExportFormat format) + public ModelExportOptions setFormat(ModelExportFormat format) { this.format = Optional.of(format); return this; @@ -201,7 +179,7 @@ public ModelExportOptions applyTo(ModelExportOptions other) /** * Get the model export format. */ - public ModelExportOptions.ModelExportFormat getFormat() + public ModelExportFormat getFormat() { return format.orElse(ModelExportFormat.EXPLICIT); } diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index 03f8fc2708..6f2588415f 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -48,7 +48,7 @@ import hybrid.PrismHybrid; import io.ExplicitModelImporter; import io.ModelExportOptions; -import io.ModelExportOptions.ModelExportFormat; +import io.ModelExportFormat; import jdd.JDD; import jdd.JDDNode; import jdd.JDDVars; diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 9b8f165e9c..7aee1745bf 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -42,7 +42,7 @@ import common.StackTraceHelper; import csv.CsvFormatException; import io.ModelExportOptions; -import io.ModelExportOptions.ModelExportFormat; +import io.ModelExportFormat; import parser.Values; import parser.ast.Expression; import parser.ast.ExpressionReward; diff --git a/prism/src/prism/TestModelGenerator.java b/prism/src/prism/TestModelGenerator.java index 3b6a5661f0..9a4d1aa26c 100644 --- a/prism/src/prism/TestModelGenerator.java +++ b/prism/src/prism/TestModelGenerator.java @@ -33,6 +33,7 @@ import explicit.ConstructModel; import explicit.DTMCModelChecker; +import io.ModelExportFormat; import io.ModelExportOptions; import parser.State; import parser.ast.DeclarationInt; @@ -171,7 +172,7 @@ public static void main(String args[]) // Perform model construction/checking via Prism TestModelGenerator modelGen2 = new TestModelGenerator(10); prism.loadModelGenerator(modelGen2); - prism.exportBuiltModelTransitions(new File("test2.dot"), new ModelExportOptions(ModelExportOptions.ModelExportFormat.DOT)); + prism.exportBuiltModelTransitions(new File("test2.dot"), new ModelExportOptions(ModelExportFormat.DOT)); PropertiesFile pf = prism.parsePropertiesString(modelGen2, "P=? [F x=10]"); Expression expr = pf.getProperty(0); Result res = prism.modelCheck(pf, expr); From 166f8c6f431f7abf83ccf417edfedd97f68fc9be Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Fri, 27 Dec 2024 09:58:34 +0000 Subject: [PATCH 03/44] Model export methods in model checker take File not PrismLog. Push handling of export destination (null file being treated as the log) from Prism into explicit.StateModelChecker. This will help: - align explicit/symbolic model checkers (native code still takes File) - adding export to binary files (where PrismLog does not make sense) --- prism/src/explicit/DTMCModelChecker.java | 4 +- prism/src/explicit/MDPModelChecker.java | 9 +- prism/src/explicit/StateModelChecker.java | 128 ++++++++++++---------- prism/src/prism/Prism.java | 109 +++--------------- prism/src/prism/PrismComponent.java | 41 +++++++ 5 files changed, 132 insertions(+), 159 deletions(-) diff --git a/prism/src/explicit/DTMCModelChecker.java b/prism/src/explicit/DTMCModelChecker.java index 896ccd8883..216e07f566 100644 --- a/prism/src/explicit/DTMCModelChecker.java +++ b/prism/src/explicit/DTMCModelChecker.java @@ -595,9 +595,7 @@ public ModelCheckerResult computeReachProbs(DTMC dtmc, BitSet remain, Bi List labels = Arrays.asList(bsInit, target); List labelNames = Arrays.asList("init", "target"); mainLog.println("\nExporting target states info to file \"" + getExportTargetFilename() + "\"..."); - PrismLog out = new PrismFileLog(getExportTargetFilename()); - exportLabels(dtmc, labelNames, labels, out, ModelExportFormat.EXPLICIT); - out.close(); + exportLabels(dtmc, labelNames, labels, new File(getExportTargetFilename()), ModelExportFormat.EXPLICIT); } if (precomp && (prob0 || prob1) && preRel) { diff --git a/prism/src/explicit/MDPModelChecker.java b/prism/src/explicit/MDPModelChecker.java index be400fade6..475c3a4875 100644 --- a/prism/src/explicit/MDPModelChecker.java +++ b/prism/src/explicit/MDPModelChecker.java @@ -26,6 +26,7 @@ package explicit; +import java.io.File; import java.util.Arrays; import java.util.BitSet; import java.util.Iterator; @@ -388,9 +389,7 @@ public ModelCheckerResult computeReachProbs(MDP mdp, BitSet remain, BitS List labels = Arrays.asList(bsInit, target); List labelNames = Arrays.asList("init", "target"); mainLog.println("\nExporting target states info to file \"" + getExportTargetFilename() + "\"..."); - PrismLog out = new PrismFileLog(getExportTargetFilename()); - exportLabels(mdp, labelNames, labels, out, ModelExportFormat.EXPLICIT); - out.close(); + exportLabels(mdp, labelNames, labels, new File(getExportTargetFilename()), ModelExportFormat.EXPLICIT); } // If required, create/initialise strategy storage @@ -2120,9 +2119,7 @@ public ModelCheckerResult computeReachRewards(MDP mdp, MDPRewards labels = Arrays.asList(bsInit, target); List labelNames = Arrays.asList("init", "target"); mainLog.println("\nExporting target states info to file \"" + getExportTargetFilename() + "\"..."); - PrismLog out = new PrismFileLog(getExportTargetFilename()); - exportLabels(mdp, labelNames, labels, out, ModelExportFormat.EXPLICIT); - out.close(); + exportLabels(mdp, labelNames, labels, new File(getExportTargetFilename()), ModelExportFormat.EXPLICIT); } // If required, create/initialise strategy storage diff --git a/prism/src/explicit/StateModelChecker.java b/prism/src/explicit/StateModelChecker.java index fd2b87108b..5b5defa56d 100644 --- a/prism/src/explicit/StateModelChecker.java +++ b/prism/src/explicit/StateModelChecker.java @@ -1544,20 +1544,22 @@ public void exportModelCombined(Model model, List labelNa /** * Export the transition matrix of a model. * @param model The model - * @param out Where to export + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - public void exportTransitions(Model model, PrismLog out, ModelExportOptions exportOptions) throws PrismException - { - switch (exportOptions.getFormat()) { - case EXPLICIT: - new PrismExplicitExporter(exportOptions).exportTransitions(model, out); - break; - case MATLAB: - throw new PrismNotSupportedException("Export not yet supported"); - case DOT: - new DotExporter(exportOptions).exportModel(model, out, null); - break; + public void exportTransitions(Model model, File file, ModelExportOptions exportOptions) throws PrismException + { + try (PrismLog out = getPrismLogForFile(file)) { + switch (exportOptions.getFormat()) { + case EXPLICIT: + new PrismExplicitExporter(exportOptions).exportTransitions(model, out); + break; + case MATLAB: + throw new PrismNotSupportedException("Export not yet supported"); + case DOT: + new DotExporter(exportOptions).exportModel(model, out, null); + break; + } } } @@ -1565,71 +1567,79 @@ public void exportTransitions(Model model, PrismLog out, ModelExp * Export the state rewards for one reward structure of a model. * @param model The model * @param r Index of reward structure to export (0-indexed) - * @param out Where to export + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - public void exportStateRewards(Model model, int r, PrismLog out, ModelExportOptions exportOptions) throws PrismException + public void exportStateRewards(Model model, int r, File file, ModelExportOptions exportOptions) throws PrismException { if (exportOptions.getFormat() != ModelExportFormat.EXPLICIT) { throw new PrismNotSupportedException("Exporting state rewards in the requested format is currently not supported by the explicit engine"); } - Rewards modelRewards = constructRewards(model, r); - PrismExplicitExporter exporter = new PrismExplicitExporter<>(exportOptions); - exporter.exportStateRewards(model, modelRewards, rewardGen.getRewardStructName(r), out); + try (PrismLog out = getPrismLogForFile(file)) { + Rewards modelRewards = constructRewards(model, r); + PrismExplicitExporter exporter = new PrismExplicitExporter<>(exportOptions); + exporter.exportStateRewards(model, modelRewards, rewardGen.getRewardStructName(r), out); + } } /** * Export the transition rewards for one reward structure of a model. * @param model The model * @param r Index of reward structure to export (0-indexed) - * @param out Where to export + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - public void exportTransRewards(Model model, int r, PrismLog out, ModelExportOptions exportOptions) throws PrismException + public void exportTransRewards(Model model, int r, File file, ModelExportOptions exportOptions) throws PrismException { if (exportOptions.getFormat() != ModelExportFormat.EXPLICIT) { throw new PrismNotSupportedException("Exporting transition rewards in the requested format is currently not supported by the explicit engine"); } - Rewards modelRewards = constructRewards(model, r); - PrismExplicitExporter exporter = new PrismExplicitExporter<>(exportOptions); - exporter.exportTransRewards(model, modelRewards, rewardGen.getRewardStructName(r), out); + try (PrismLog out = getPrismLogForFile(file)) { + Rewards modelRewards = constructRewards(model, r); + PrismExplicitExporter exporter = new PrismExplicitExporter<>(exportOptions); + exporter.exportTransRewards(model, modelRewards, rewardGen.getRewardStructName(r), out); + } } /** * Export the set of states for a model. * @param model The model - * @param out Where to export + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - public void exportStates(Model model, PrismLog out, ModelExportOptions exportOptions) throws PrismException - { - switch (exportOptions.getFormat()) { - case EXPLICIT: - new PrismExplicitExporter(exportOptions).exportStates(model, modelInfo.createVarList(), out); - break; - case MATLAB: - new MatlabExporter(exportOptions).exportStates(model, modelInfo.createVarList(), out); - break; + public void exportStates(Model model, File file, ModelExportOptions exportOptions) throws PrismException + { + try (PrismLog out = getPrismLogForFile(file)) { + switch (exportOptions.getFormat()) { + case EXPLICIT: + new PrismExplicitExporter(exportOptions).exportStates(model, modelInfo.createVarList(), out); + break; + case MATLAB: + new MatlabExporter(exportOptions).exportStates(model, modelInfo.createVarList(), out); + break; + } } } /** * Export the observations for a (partially observable) model. * @param model The model - * @param out Where to export + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - public void exportObservations(Model model, PrismLog out, ModelExportOptions exportOptions) throws PrismException - { - switch (exportOptions.getFormat()) { - case EXPLICIT: - new PrismExplicitExporter(exportOptions).exportObservations((PartiallyObservableModel) model, modelInfo, out); - break; - case MATLAB: - new MatlabExporter(exportOptions).exportObservations((PartiallyObservableModel) model, modelInfo, out); - break; + public void exportObservations(Model model, File file, ModelExportOptions exportOptions) throws PrismException + { + try (PrismLog out = getPrismLogForFile(file)) { + switch (exportOptions.getFormat()) { + case EXPLICIT: + new PrismExplicitExporter(exportOptions).exportObservations((PartiallyObservableModel) model, modelInfo, out); + break; + case MATLAB: + new MatlabExporter(exportOptions).exportObservations((PartiallyObservableModel) model, modelInfo, out); + break; + } } } @@ -1637,13 +1647,13 @@ public void exportObservations(Model model, PrismLog out, ModelEx * Export a set of labels and the states that satisfy them. * @param model The model * @param labelNames The names of the labels to export - * @param out Where to export + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - public void exportLabels(Model model, List labelNames, PrismLog out, ModelExportOptions exportOptions) throws PrismException + public void exportLabels(Model model, List labelNames, File file, ModelExportOptions exportOptions) throws PrismException { List labelStates = checkLabels(model, labelNames); - exportLabels(model, labelNames, labelStates, out, exportOptions); + exportLabels(model, labelNames, labelStates, file, exportOptions); } /** @@ -1667,12 +1677,12 @@ private List checkLabels(Model model, List labelNames) throws * @param model The model * @param labelNames The names of the labels to export * @param labelStates The states that satisfy each label, specified as a BitSet - * @param out Where to export + * @param file File to export to (if null, print to the log instead) * @param format The format in which to export */ - public void exportLabels(Model model, List labelNames, List labelStates, PrismLog out, ModelExportFormat format) throws PrismException + public void exportLabels(Model model, List labelNames, List labelStates, File file, ModelExportFormat format) throws PrismException { - exportLabels(model, labelNames, labelStates, out, new ModelExportOptions(format)); + exportLabels(model, labelNames, labelStates, file, new ModelExportOptions(format)); } /** @@ -1680,18 +1690,20 @@ public void exportLabels(Model model, List labelNames, Li * @param model The model * @param labelNames The names of the labels to export * @param labelStates The states that satisfy each label, specified as a BitSet - * @param out Where to export + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - public void exportLabels(Model model, List labelNames, List labelStates, PrismLog out, ModelExportOptions exportOptions) throws PrismException - { - switch (exportOptions.getFormat()) { - case EXPLICIT: - new PrismExplicitExporter(exportOptions).exportLabels(model, labelNames, labelStates, out); - break; - case MATLAB: - new MatlabExporter(exportOptions).exportLabels(model, labelNames, labelStates, out); - break; + public void exportLabels(Model model, List labelNames, List labelStates, File file, ModelExportOptions exportOptions) throws PrismException + { + try (PrismLog out = getPrismLogForFile(file)) { + switch (exportOptions.getFormat()) { + case EXPLICIT: + new PrismExplicitExporter(exportOptions).exportLabels(model, labelNames, labelStates, out); + break; + case MATLAB: + new MatlabExporter(exportOptions).exportLabels(model, labelNames, labelStates, out); + break; + } } } diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index 6f2588415f..9f546eceab 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -2431,7 +2431,7 @@ public Model buildModelExplicit(ModulesFile modulesFile) throws PrismException /** * Export the currently loaded and parsed PRISM model to a file. - * @param file File to export to + * @param file File to export to (if null, print to the log instead) */ public void exportPRISMModel(File file) throws FileNotFoundException, PrismException { @@ -2447,7 +2447,7 @@ public void exportPRISMModel(File file) throws FileNotFoundException, PrismExcep /** * Export the currently loaded and parsed PRISM model to a file, * after expanding all constants to their actual, defined values. - * @param file File to export to + * @param file File to export to (if null, print to the log instead) */ public void exportPRISMModelWithExpandedConstants(File file) throws FileNotFoundException, PrismException { @@ -2466,7 +2466,7 @@ public void exportPRISMModelWithExpandedConstants(File file) throws FileNotFound /** * Export various aspects of the current built model together. - * @param file File to export to + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ public void exportBuiltModelCombined(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException @@ -2500,7 +2500,7 @@ public void exportBuiltModelCombined(File file, ModelExportOptions exportOptions /** * Export the transition matrix for the current built model. - * @param file File to export to + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ public void exportBuiltModelTransitions(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException @@ -2521,10 +2521,8 @@ public void exportBuiltModelTransitions(File file, ModelExportOptions exportOpti int precision = settings.getInteger(PrismSettings.PRISM_EXPORT_MODEL_PRECISION); getBuiltModelSymbolic().exportToFile(convertExportTypeTrans(exportOptions), true, file, precision); } else { - PrismLog tmpLog = getPrismLogForFile(file); explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); - mcExpl.exportTransitions(getBuiltModelExplicit(), tmpLog, exportOptions); - tmpLog.close(); + mcExpl.exportTransitions(getBuiltModelExplicit(), file, exportOptions); } // for export to dot with states, need to do a bit more @@ -2604,7 +2602,7 @@ public void exportToDotFile(File file) throws FileNotFoundException, PrismExcept * Export the state rewards for the current built model. * If there is more than 1 reward structure, then multiple files are generated * (e.g. "rew.sta" becomes "rew1.sta", "rew2.sta", ...) - * @param file File to export to + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException @@ -2641,18 +2639,7 @@ public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOpt getBuiltModelSymbolic().exportStateRewardsToFile(r, convertExportType(exportOptions), fileToUse, precision, noexportheaders); } else { explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); - try (PrismLog out = getPrismLogForFile(fileToUse)){ - mcExpl.exportStateRewards(getBuiltModelExplicit(), r, out, exportOptions); - } catch (PrismNotSupportedException e1) { - mainLog.println("\nReward export failed: " + e1.getMessage()); - try { - if (fileToUse != null) { - fileToUse.delete(); - } - } catch (SecurityException e2) { - // Cannot delete File; continue - } - } + mcExpl.exportStateRewards(getBuiltModelExplicit(), r, fileToUse, exportOptions); } } @@ -2665,7 +2652,7 @@ public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOpt * Export the transition rewards for the current built model. * If there is more than 1 reward structure, then multiple files are generated * (e.g. "rew.sta" becomes "rew1.sta", "rew2.sta", ...) - * @param file File to export to + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ public void exportBuiltModelTransRewards(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException @@ -2702,18 +2689,7 @@ public void exportBuiltModelTransRewards(File file, ModelExportOptions exportOpt getBuiltModelSymbolic().exportTransRewardsToFile(r, convertExportTypeTrans(exportOptions), true, fileToUse, precision, noexportheaders); } else { explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); - try (PrismLog out = getPrismLogForFile(fileToUse)){ - mcExpl.exportTransRewards(getBuiltModelExplicit(), r, out, exportOptions); - } catch (PrismNotSupportedException e1) { - mainLog.println("\nReward export failed: " + e1.getMessage()); - try { - if (fileToUse != null) { - fileToUse.delete(); - } - } catch (SecurityException e2) { - // Cannot delete File; continue - } - } + mcExpl.exportTransRewards(getBuiltModelExplicit(), r, fileToUse, exportOptions); } } @@ -2740,20 +2716,15 @@ public void exportBuiltModelStates(File file, ModelExportOptions exportOptions) // Merge export options with settings exportOptions = newMergedModelExportOptions(exportOptions); - // Create new file log or use main log - PrismLog out = getPrismLogForFile(file); - // Export if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - getBuiltModelSymbolic().exportStates(convertExportType(exportOptions), out); + try (PrismLog out = getPrismLogForFile(file)) { + getBuiltModelSymbolic().exportStates(convertExportType(exportOptions), out); + } } else { explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); - mcExpl.exportStates(getBuiltModelExplicit(), out, exportOptions); + mcExpl.exportStates(getBuiltModelExplicit(), file, exportOptions); } - - // Tidy up - if (file != null) - out.close(); } /** @@ -2779,16 +2750,9 @@ public void exportBuiltModelObservations(File file, ModelExportOptions exportOpt // Merge export options with settings exportOptions = newMergedModelExportOptions(exportOptions); - // Create new file log or use main log - PrismLog out = getPrismLogForFile(file); - // Export (explicit engine only) explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); - mcExpl.exportObservations(getBuiltModelExplicit(), out, exportOptions); - - // Tidy up - if (file != null) - out.close(); + mcExpl.exportObservations(getBuiltModelExplicit(), file, exportOptions); } /** @@ -2836,7 +2800,7 @@ public void exportBuiltModelPropLabels(PropertiesFile propertiesFile, File file, * The PropertiesFile should correspond to the currently loaded model. * @param propertiesFile The properties file, for further labels (ignored if null) * @param labelNames The list of label names to export - * @param file File to export to + * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ private void doExportBuiltModelLabels(PropertiesFile propertiesFile, List labelNames, File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException @@ -2854,10 +2818,8 @@ private void doExportBuiltModelLabels(PropertiesFile propertiesFile, List Date: Sat, 28 Dec 2024 11:17:08 +0000 Subject: [PATCH 04/44] Move model export code from Prism to symbolic.StateModelChecker. This now matches the code for the explicit engine more closely. --- prism/src/prism/Prism.java | 37 +++----- .../src/symbolic/comp/StateModelChecker.java | 88 +++++++++++++++++-- 2 files changed, 92 insertions(+), 33 deletions(-) diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index 9f546eceab..2b06d887b2 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -2518,26 +2518,12 @@ public void exportBuiltModelTransitions(File file, ModelExportOptions exportOpti // do export if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - int precision = settings.getInteger(PrismSettings.PRISM_EXPORT_MODEL_PRECISION); - getBuiltModelSymbolic().exportToFile(convertExportTypeTrans(exportOptions), true, file, precision); + symbolic.comp.StateModelChecker mcSymb = createModelChecker(null); + mcSymb.exportTransitions(file, exportOptions); } else { explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); mcExpl.exportTransitions(getBuiltModelExplicit(), file, exportOptions); } - - // for export to dot with states, need to do a bit more - if (getBuiltModelType() == ModelBuildType.SYMBOLIC && convertExportTypeTrans(exportOptions) == EXPORT_DOT_STATES) { - // open (appending to) existing new file log or use main log - PrismLog tmpLog = getPrismLogForFile(file, true); - // insert states info into dot file - getBuiltModelSymbolic().getReachableStates().printDot(tmpLog); - // print footer - tmpLog.println("}"); - // tidy up - if (file != null) { - tmpLog.close(); - } - } } /** @@ -2634,9 +2620,8 @@ public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOpt } File fileToUse = (filename == null) ? null : new File(filename); if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - int precision = settings.getInteger(PrismSettings.PRISM_EXPORT_MODEL_PRECISION); - boolean noexportheaders = !settings.getBoolean(PrismSettings.PRISM_EXPORT_MODEL_HEADERS); - getBuiltModelSymbolic().exportStateRewardsToFile(r, convertExportType(exportOptions), fileToUse, precision, noexportheaders); + symbolic.comp.StateModelChecker mcSymb = createModelChecker(null); + mcSymb.exportStateRewards(r, fileToUse, exportOptions); } else { explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); mcExpl.exportStateRewards(getBuiltModelExplicit(), r, fileToUse, exportOptions); @@ -2684,9 +2669,8 @@ public void exportBuiltModelTransRewards(File file, ModelExportOptions exportOpt } File fileToUse = (filename == null) ? null : new File(filename); if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - int precision = settings.getInteger(PrismSettings.PRISM_EXPORT_MODEL_PRECISION); - boolean noexportheaders = !settings.getBoolean(PrismSettings.PRISM_EXPORT_MODEL_HEADERS); - getBuiltModelSymbolic().exportTransRewardsToFile(r, convertExportTypeTrans(exportOptions), true, fileToUse, precision, noexportheaders); + symbolic.comp.StateModelChecker mcSymb = createModelChecker(null); + mcSymb.exportTransRewards(r, fileToUse, exportOptions); } else { explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); mcExpl.exportTransRewards(getBuiltModelExplicit(), r, fileToUse, exportOptions); @@ -2718,9 +2702,8 @@ public void exportBuiltModelStates(File file, ModelExportOptions exportOptions) // Export if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - try (PrismLog out = getPrismLogForFile(file)) { - getBuiltModelSymbolic().exportStates(convertExportType(exportOptions), out); - } + symbolic.comp.StateModelChecker mcSymb = createModelChecker(null); + mcSymb.exportStates(file, exportOptions); } else { explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); mcExpl.exportStates(getBuiltModelExplicit(), file, exportOptions); @@ -2821,8 +2804,8 @@ private void doExportBuiltModelLabels(PropertiesFile propertiesFile, List getDefinedLabelNames() return definedLabelNames; } + /** + * Export the transition matrix. + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportTransitions(File file, ModelExportOptions exportOptions) throws PrismException + { + int precision = exportOptions.getModelPrecision(); + try { + model.exportToFile(Prism.convertExportTypeTrans(exportOptions), true, file, precision); + } catch (FileNotFoundException e) { + throw new PrismException("Could not open file \"" + file.getName() + "\" for output"); + } + + // For export to Dot with states, need to insert states info into file + if (exportOptions.getFormat() == ModelExportFormat.DOT) { + try (PrismLog out = getPrismLogForFile(file, true)) { + model.getReachableStates().printDot(out); + // Print footer + out.println("}"); + } + } + } + + /** + * Export the state rewards for one reward structure. + * @param r Index of reward structure to export (0-indexed) + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportStateRewards(int r, File file, ModelExportOptions exportOptions) throws PrismException + { + int precision = exportOptions.getModelPrecision(); + boolean noexportheaders = !exportOptions.getPrintHeaders(); + try { + model.exportStateRewardsToFile(r, Prism.convertExportType(exportOptions), file, precision, noexportheaders); + } catch (FileNotFoundException e) { + throw new PrismException("Could not open file \"" + file.getName() + "\" for output"); + } + } + + /** + * Export the transition rewards for one reward structure. + * @param r Index of reward structure to export (0-indexed) + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportTransRewards(int r, File file, ModelExportOptions exportOptions) throws PrismException + { + int precision = exportOptions.getModelPrecision(); + boolean noexportheaders = !exportOptions.getPrintHeaders(); + try { + model.exportTransRewardsToFile(r, Prism.convertExportTypeTrans(exportOptions), true, file, precision, noexportheaders); + } catch (FileNotFoundException e) { + throw new PrismException("Could not open file \"" + file.getName() + "\" for output"); + } + } + + /** + * Export the set of states. + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportStates(File file, ModelExportOptions exportOptions) throws PrismException + { + try (PrismLog out = getPrismLogForFile(file)) { + model.exportStates(Prism.convertExportType(exportOptions), out); + } + } + /** * Export a set of labels and the states that satisfy them. * @param labelNames The name of each label - * @param exportType The format in which to export - * @param file Where to export + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export */ - public void exportLabels(List labelNames, int exportType, File file) throws PrismException, FileNotFoundException + public void exportLabels(List labelNames, File file, ModelExportOptions exportOptions) throws PrismException { // Convert labels to BDDs int numLabels = labelNames.size(); @@ -1709,8 +1781,12 @@ public void exportLabels(List labelNames, int exportType, File file) thr // Export them using the MTBDD engine String matlabVarName = "l"; String labelNamesArr[] = labelNames.toArray(new String[labelNames.size()]); - PrismMTBDD.ExportLabels(labels, labelNamesArr, matlabVarName, allDDRowVars, odd, exportType, (file != null) ? file.getPath() : null); - + try { + PrismMTBDD.ExportLabels(labels, labelNamesArr, matlabVarName, allDDRowVars, odd, Prism.convertExportType(exportOptions), (file != null) ? file.getPath() : null); + } catch (FileNotFoundException e) { + throw new PrismException("Could not open file \"" + file.getName() + "\" for output"); + } + // Derefs for (int i = 0; i < numLabels; i++) { JDD.Deref(labels[i]); From 886511ecc03b93bc8206c43c71530d3ea84b2fc1 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sat, 28 Dec 2024 22:43:55 +0000 Subject: [PATCH 05/44] Change/remove rarely used export options. Export of transition matrix (MTB)DD to a Dot file (-exportdot) moved to an instance of exportBuiltModelTransitions (DD_DOT format). Export of transition matrix to a spy file (-exportspy) removed from command line, but code kept in StateModelChecker. --- prism/src/explicit/StateModelChecker.java | 2 + prism/src/io/ModelExportFormat.java | 4 +- prism/src/prism/Prism.java | 71 ++++--------------- prism/src/prism/PrismCL.java | 27 +------ .../src/symbolic/comp/StateModelChecker.java | 28 ++++++++ 5 files changed, 47 insertions(+), 85 deletions(-) diff --git a/prism/src/explicit/StateModelChecker.java b/prism/src/explicit/StateModelChecker.java index 5b5defa56d..e080a24579 100644 --- a/prism/src/explicit/StateModelChecker.java +++ b/prism/src/explicit/StateModelChecker.java @@ -1559,6 +1559,8 @@ public void exportTransitions(Model model, File file, ModelExport case DOT: new DotExporter(exportOptions).exportModel(model, out, null); break; + default: + throw new PrismNotSupportedException("Export " + exportOptions.getFormat().description() + " not supported by explicit engine"); } } } diff --git a/prism/src/io/ModelExportFormat.java b/prism/src/io/ModelExportFormat.java index 24b9202cae..729867ce38 100644 --- a/prism/src/io/ModelExportFormat.java +++ b/prism/src/io/ModelExportFormat.java @@ -5,7 +5,7 @@ */ public enum ModelExportFormat { - EXPLICIT, MATLAB, DOT, DRN; + EXPLICIT, MATLAB, DOT, DD_DOT, DRN; public String description() { @@ -16,6 +16,8 @@ public String description() return "in Matlab format"; case DOT: return "in Dot format"; + case DD_DOT: + return "in DD Dot format"; case DRN: return "in DRN format"; default: diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index 2b06d887b2..78f98d00ef 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -2526,64 +2526,6 @@ public void exportBuiltModelTransitions(File file, ModelExportOptions exportOpti } } - /** - * Export the currently loaded model's transition matrix to a Spy file. - * @param file File to export to - */ - public void exportToSpyFile(File file) throws FileNotFoundException, PrismException - { - int depth; - JDDNode tmp; - - if (getCurrentEngine() == PrismEngine.EXPLICIT) { - throw new PrismNotSupportedException("Export to Spy file not yet supported by explicit engine"); - } - - // Build model, if necessary - buildModelIfRequired(); - - mainLog.println("\nExporting to spy file \"" + file + "\"..."); - - // choose depth - depth = getBuiltModelSymbolic().getAllDDRowVars().n(); - if (depth > 9) - depth = 9; - - // get rid of non det vars if necessary - tmp = getBuiltModelSymbolic().getTrans(); - JDD.Ref(tmp); - if (getModelType() == ModelType.MDP) { - tmp = JDD.MaxAbstract(tmp, ((NondetModel) getBuiltModelSymbolic()).getAllDDNondetVars()); - } - - // export to spy file - JDD.ExportMatrixToSpyFile(tmp, getBuiltModelSymbolic().getAllDDRowVars(), getBuiltModelSymbolic().getAllDDColVars(), depth, file.getPath()); - JDD.Deref(tmp); - } - - /** - * Export the MTBDD for the currently loaded model's transition matrix to a Dot file. - * @param file File to export to - */ - public void exportToDotFile(File file) throws FileNotFoundException, PrismException - { - if (getCurrentEngine() == PrismEngine.EXPLICIT) { - throw new PrismNotSupportedException("Export to Dot file not yet supported by explicit engine"); - } - - // Build model, if necessary - buildModelIfRequired(); - - // Check again (in case engine was switched) - if (getCurrentEngine() == PrismEngine.EXPLICIT) { - throw new PrismNotSupportedException("Export to Dot file not yet supported by explicit engine"); - } - - // Export to dot file - mainLog.println("\nExporting to dot file \"" + file + "\"..."); - JDD.ExportDDToDotFileLabelled(getBuiltModelSymbolic().getTrans(), file.getPath(), getBuiltModelSymbolic().getDDVarNames()); - } - /** * Export the state rewards for the current built model. * If there is more than 1 reward structure, then multiple files are generated @@ -4379,6 +4321,7 @@ public void exportLabelsToFile(PropertiesFile propertiesFile, int exportType, Fi } /** + * @deprecated * Export the states satisfying labels from the properties file to a file. * The PropertiesFile should correspond to the currently loaded model. * @param propertiesFile The properties file (for further labels) @@ -4388,11 +4331,23 @@ public void exportLabelsToFile(PropertiesFile propertiesFile, int exportType, Fi * * @param file File to export to (if null, print to the log instead) */ + @Deprecated public void exportPropLabelsToFile(PropertiesFile propertiesFile, int exportType, File file) throws FileNotFoundException, PrismException { exportBuiltModelPropLabels(propertiesFile, file, convertExportType(exportType)); } + /** + * @deprecated + * Export the MTBDD for the currently loaded model's transition matrix to a Dot file. + * @param file File to export to + */ + @Deprecated + public void exportToDotFile(File file) throws FileNotFoundException, PrismException + { + exportBuiltModelTransitions(file, new ModelExportOptions(ModelExportFormat.DD_DOT)); + } + /** * Convert a {@code ModelExportOptions} object to an {@code exportType} value. */ diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 7aee1745bf..56c645676f 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -92,7 +92,6 @@ public class PrismCL implements PrismModelListener private boolean exportmodelproplabels = false; private boolean exportproplabels = false; private boolean exportmodelcombined = false; - private boolean exportspy = false; private boolean exportdot = false; private boolean exporttransdot = false; private boolean exporttransdotstates = false; @@ -164,7 +163,6 @@ public class PrismCL implements PrismModelListener private String exportObservationsFilename = null; private String exportModelLabelsFilename = null; private String exportPropLabelsFilename = null; - private String exportSpyFilename = null; private String exportDotFilename = null; private String exportTransDotFilename = null; private String exportTransDotStatesFilename = null; @@ -816,7 +814,6 @@ private void doExports() exporttransrewards || exportstates || exportobservations || - exportspy || exportdot || exporttransdot || exporttransdotstates || @@ -912,23 +909,10 @@ private void doExports() } } - // export to spy file - if (exportspy) { - try { - prism.exportToSpyFile(new File(exportSpyFilename)); - } - // in case of error, report it and proceed - catch (FileNotFoundException e) { - error("Couldn't open file \"" + exportSpyFilename + "\" for output"); - } catch (PrismException e) { - error(e); - } - } - // export mtbdd to dot file if (exportdot) { try { - prism.exportToDotFile(new File(exportDotFilename)); + prism.exportBuiltModelTransitions(new File(exportDotFilename), new ModelExportOptions(ModelExportFormat.DD_DOT)); } // in case of error, report it and proceed catch (FileNotFoundException e) { @@ -1864,15 +1848,6 @@ else if (sw.equals("exportprodvector")) { errorAndExit("No file specified for -" + sw + " switch"); } } - // export to spy file (hidden option) - else if (sw.equals("exportspy")) { - if (i < args.length - 1) { - exportspy = true; - exportSpyFilename = args[++i]; - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } // NB: Following the ordering of the -help text, more options go here, // but these are processed in the PrismSettings class; see below diff --git a/prism/src/symbolic/comp/StateModelChecker.java b/prism/src/symbolic/comp/StateModelChecker.java index 6c2ea8f2fb..f3ed7d4aa9 100644 --- a/prism/src/symbolic/comp/StateModelChecker.java +++ b/prism/src/symbolic/comp/StateModelChecker.java @@ -1700,6 +1700,11 @@ public Set getDefinedLabelNames() */ public void exportTransitions(File file, ModelExportOptions exportOptions) throws PrismException { + if (exportOptions.getFormat() == ModelExportFormat.DD_DOT) { + JDD.ExportDDToDotFileLabelled(model.getTrans(), file.getPath(), model.getDDVarNames()); + return; + } + int precision = exportOptions.getModelPrecision(); try { model.exportToFile(Prism.convertExportTypeTrans(exportOptions), true, file, precision); @@ -1792,6 +1797,29 @@ public void exportLabels(List labelNames, File file, ModelExportOptions JDD.Deref(labels[i]); } } + + /** + * Export the transition matrix to a Spy file. + * @param file File to export to + */ + public void exportTransitionsToSpyFile(File file) throws PrismException + { + // choose depth + int depth = model.getAllDDRowVars().n(); + if (depth > 9) + depth = 9; + + // get rid of non det vars if necessary + JDDNode tmp = model.getTrans(); + JDD.Ref(tmp); + if (model.getModelType() == ModelType.MDP) { + tmp = JDD.MaxAbstract(tmp, ((NondetModel) model).getAllDDNondetVars()); + } + + // export to spy file + JDD.ExportMatrixToSpyFile(tmp, model.getAllDDRowVars(), model.getAllDDColVars(), depth, file.getPath()); + JDD.Deref(tmp); + } } // ------------------------------------------------------------------------------ From 5eeddc90a8a982e644c5dc244873ce73f30b0f75 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Mon, 30 Dec 2024 23:25:20 +0000 Subject: [PATCH 06/44] Model export in rows format not yet supported by explicit engine. --- prism/src/explicit/StateModelChecker.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/prism/src/explicit/StateModelChecker.java b/prism/src/explicit/StateModelChecker.java index e080a24579..e4b511f589 100644 --- a/prism/src/explicit/StateModelChecker.java +++ b/prism/src/explicit/StateModelChecker.java @@ -1552,10 +1552,11 @@ public void exportTransitions(Model model, File file, ModelExport try (PrismLog out = getPrismLogForFile(file)) { switch (exportOptions.getFormat()) { case EXPLICIT: + if (exportOptions.getExplicitRows()) { + throw new PrismNotSupportedException("Export in rows format not yet supported by explicit engine"); + } new PrismExplicitExporter(exportOptions).exportTransitions(model, out); break; - case MATLAB: - throw new PrismNotSupportedException("Export not yet supported"); case DOT: new DotExporter(exportOptions).exportModel(model, out, null); break; From dcdc8b43d64083c66bbe83102a71da76ac87da87 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jul 2024 17:11:58 +0100 Subject: [PATCH 07/44] Model export refactoring: ModelExportTask objects for export task definitions. ModelExportTask stores the entity to be exported (model, states, rewards, etc.), the destination and the export options. Tasks that export the whole model (e.g. Dot, DRN) use entity "model", representing the core part of the model, and can also have other entities added as annotations. --- prism/src/explicit/StateModelChecker.java | 54 +-- prism/src/io/ModelExportOptions.java | 8 + prism/src/io/ModelExportTask.java | 392 ++++++++++++++++++ prism/src/prism/Prism.java | 315 ++++++++------ prism/src/prism/PrismCL.java | 2 +- .../src/symbolic/comp/StateModelChecker.java | 23 +- 6 files changed, 629 insertions(+), 165 deletions(-) create mode 100644 prism/src/io/ModelExportTask.java diff --git a/prism/src/explicit/StateModelChecker.java b/prism/src/explicit/StateModelChecker.java index e4b511f589..90f6bf0e8b 100644 --- a/prism/src/explicit/StateModelChecker.java +++ b/prism/src/explicit/StateModelChecker.java @@ -44,6 +44,7 @@ import io.MatlabExporter; import io.ModelExportFormat; import io.ModelExportOptions; +import io.ModelExportTask; import io.PrismExplicitExporter; import io.PrismExplicitImporter; import parser.EvaluateContext.EvalMode; @@ -1521,34 +1522,14 @@ public static Map loadLabelsFile(String filename) throws PrismEx } /** - * Export various aspects of a model, combined. + * Export a model. * @param model The model - * @param labelNames Names of labels to include in export - * @param out Where to export - * @param exportOptions The options for export + * @param exportTask Export task (destination, which parts of the model to export, options) */ - public void exportModelCombined(Model model, List labelNames, PrismLog out, ModelExportOptions exportOptions) throws PrismException - { - if (exportOptions.getFormat() != ModelExportFormat.DRN) { - return; - } - List> rewards = new ArrayList<>(); - for (int r = 0; r < rewardGen.getNumRewardStructs(); r++) { - rewards.add(constructRewards(model, r)); - } - List labelStates = checkLabels(model, labelNames); - DRNExporter exporter = new DRNExporter<>(exportOptions); - exporter.exportModel(model, (RewardGenerator) rewardGen, rewards, labelNames, labelStates, out); - } - - /** - * Export the transition matrix of a model. - * @param model The model - * @param file File to export to (if null, print to the log instead) - * @param exportOptions The options for export - */ - public void exportTransitions(Model model, File file, ModelExportOptions exportOptions) throws PrismException + public void exportModel(Model model, ModelExportTask exportTask) throws PrismException { + File file = exportTask.getFile(); + ModelExportOptions exportOptions = exportTask.getExportOptions(); try (PrismLog out = getPrismLogForFile(file)) { switch (exportOptions.getFormat()) { case EXPLICIT: @@ -1560,12 +1541,35 @@ public void exportTransitions(Model model, File file, ModelExport case DOT: new DotExporter(exportOptions).exportModel(model, out, null); break; + case DRN: + List labelNames = new ArrayList(); + labelNames.add("init"); + labelNames.add("deadlock"); + labelNames.addAll(modelInfo.getLabelNames()); + List> rewards = new ArrayList<>(); + for (int r = 0; r < rewardGen.getNumRewardStructs(); r++) { + rewards.add(constructRewards(model, r)); + } + List labelStates = checkLabels(model, labelNames); + DRNExporter exporter = new DRNExporter<>(exportOptions); + exporter.exportModel(model, (RewardGenerator) rewardGen, rewards, labelNames, labelStates, out); + break; default: throw new PrismNotSupportedException("Export " + exportOptions.getFormat().description() + " not supported by explicit engine"); } } } + /** + * Export the transition function/matrix of a model. + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportTransitions(Model model, File file, ModelExportOptions exportOptions) throws PrismException + { + exportModel(model, ModelExportTask.fromOptions(file, exportOptions)); + } + /** * Export the state rewards for one reward structure of a model. * @param model The model diff --git a/prism/src/io/ModelExportOptions.java b/prism/src/io/ModelExportOptions.java index 6e499b0f30..c8e82eedc5 100644 --- a/prism/src/io/ModelExportOptions.java +++ b/prism/src/io/ModelExportOptions.java @@ -82,6 +82,14 @@ public ModelExportOptions(ModelExportFormat format) setFormat(format); } + /** + * Copy constructor. + */ + public ModelExportOptions(ModelExportOptions exportOptions) + { + apply(exportOptions); + } + // Set methods /** diff --git a/prism/src/io/ModelExportTask.java b/prism/src/io/ModelExportTask.java new file mode 100644 index 0000000000..be761441d4 --- /dev/null +++ b/prism/src/io/ModelExportTask.java @@ -0,0 +1,392 @@ +//============================================================================== +// +// Copyright (c) 2024- +// Authors: +// * Dave Parker (University of Oxford) +// +//------------------------------------------------------------------------------ +// +// This file is part of PRISM. +// +// PRISM 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 2 of the License, or +// (at your option) any later version. +// +// PRISM 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 PRISM; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//============================================================================== + +package io; + +import parser.ast.PropertiesFile; +import prism.ModelInfo; +import prism.PrismException; + +import java.io.File; + +/** + * Class to represent a task related to exporting models. + */ +public class ModelExportTask +{ + /** + * Model export entities + */ + public enum ModelExportEntity { + MODEL, // Core part of the model (transition function/matrix) + STATE_REWARDS, // State rewards + TRANSITION_REWARDS, // Transition rewards + STATES, // State definitions (variable values) + OBSERVATIONS, // Observation definitions (observable values) + LABELS; // Labels (atomic propositions) + public String description() + { + switch (this) { + case MODEL: + return "model"; + case STATE_REWARDS: + return "state rewards"; + case TRANSITION_REWARDS: + return "transition rewards"; + case STATES: + return "reachable states"; + case OBSERVATIONS: + return "observations"; + case LABELS: + return "labels and satisfying states"; + default: + return this.toString(); + } + } + } + + /** + * Model export entity (what to export) + */ + private ModelExportEntity entity; + + /** + * File to export to (null means stdout) + */ + private File file; + + /** + * Model export options + */ + private ModelExportOptions exportOptions; + + /** + * Which labels to export + */ + public enum LabelExportSet { + MODEL, // Just those in the model + EXTRA, // Just those in the "extra" source of labels + ALL // All (model and extra) + } + + /** + * When exporting labels, which ones to include + */ + private LabelExportSet labelExportSet = LabelExportSet.MODEL; + + /** + * Optionally, a source of extra labels for label export + */ + private PropertiesFile extraLabelsSource; + + // Constructors + + /** + * Construct a ModelExportTask with default options. + * @param entity What to export + * @param file File to export to (null means stdout) + */ + public ModelExportTask(ModelExportEntity entity, File file) + { + this(entity, file, new ModelExportOptions()); + } + + /** + * Construct a ModelExportTask with specified options. + * @param entity What to export + * @param file File to export to (null means stdout) + * @param exportOptions The options for export + */ + public ModelExportTask(ModelExportEntity entity, File file, ModelExportOptions exportOptions) + { + this.entity = entity; + this.file = file; + this.exportOptions = exportOptions; + } + + /** + * Construct a ModelExportTask with specified options. + * @param entity What to export + * @param filename Name of file to export to (can be "stdout") + */ + public ModelExportTask(ModelExportEntity entity, String filename) + { + this(entity, filename, new ModelExportOptions()); + } + + /** + * Construct a ModelExportTask with specified options. + * @param entity What to export + * @param filename Name of file to export to (can be "stdout") + * @param exportOptions The options for export + */ + public ModelExportTask(ModelExportEntity entity, String filename, ModelExportOptions exportOptions) + { + this.entity = entity; + this.file = "stdout".equals(filename) ? null : new File(filename); + this.exportOptions = exportOptions; + } + + /** + * Copy constructor. + */ + public ModelExportTask(ModelExportTask exportTask) + { + this(exportTask, new ModelExportOptions(exportTask.exportOptions)); + } + + /** + * Copy constructor, but with a specified ModelExportOptions. + */ + public ModelExportTask(ModelExportTask exportTask, ModelExportOptions exportOptions) + { + this.entity = exportTask.entity; + this.file = exportTask.file; + this.exportOptions = exportOptions; + this.labelExportSet = exportTask.labelExportSet; + this.extraLabelsSource = exportTask.extraLabelsSource; + } + + /** + * Create a ModelExportTask based on a filename, supplied as separate basename and extension. + * The basename can also be "stdout". It can also be left empty ("") and later replaced + * (e.g. with the model basename) using {@link #replaceEmptyFileBasename(String)}. + */ + public static ModelExportTask fromFilename(String basename, String ext) throws PrismException + { + String filename = "stdout".equals(basename) ? "stdout" : basename + "." + ext; + switch (ext) { + case "tra": + return new ModelExportTask(ModelExportEntity.MODEL, filename); + case "srew": + return new ModelExportTask(ModelExportEntity.STATE_REWARDS, filename); + case "trew": + return new ModelExportTask(ModelExportEntity.TRANSITION_REWARDS, filename); + case "sta": + return new ModelExportTask(ModelExportEntity.STATES, filename); + case "obs": + return new ModelExportTask(ModelExportEntity.OBSERVATIONS, filename); + case "lab": + return new ModelExportTask(ModelExportEntity.LABELS, filename); + case "dot": + return fromFormat(filename, ModelExportFormat.DOT); + case "drn": + return fromFormat(filename, ModelExportFormat.DRN); + default: + throw new PrismException("Unknown extension \"" + ext + "\" for model export"); + } + } + + /** + * Create a ModelExportTask to export a model to a file in the specified format. + * @param filename Name of file to export to (can be "stdout") + * @param exportFormat The format to use for export + */ + public static ModelExportTask fromFormat(String filename, ModelExportFormat exportFormat) throws PrismException + { + File file = "stdout".equals(filename) ? null : new File(filename); + return fromOptions(file, new ModelExportOptions(exportFormat)); + } + + /** + * Create a ModelExportTask to export a model to a file in the specified format. + * @param file File to export to (null means stdout) + * @param exportFormat The format to use for export + */ + public static ModelExportTask fromFormat(File file, ModelExportFormat exportFormat) throws PrismException + { + return fromOptions(file, new ModelExportOptions(exportFormat)); + } + + /** + * Create a ModelExportTask to export a model to a file, + * using the supplied export options (which includes the format). + * @param file File to export to (null means stdout) + * @param exportOptions The options for export + */ + public static ModelExportTask fromOptions(File file, ModelExportOptions exportOptions) throws PrismException + { + ModelExportTask exportTask; + switch (exportOptions.getFormat()) { + case EXPLICIT: + case MATLAB: + exportTask = new ModelExportTask(ModelExportEntity.MODEL, file); + break; + case DOT: + exportTask = new ModelExportTask(ModelExportEntity.MODEL, file); + exportTask.getExportOptions().setShowStates(true); + break; + case DRN: + exportTask = new ModelExportTask(ModelExportEntity.MODEL, file); + break; + default: + return null; + } + exportTask.getExportOptions().apply(exportOptions); + return exportTask; + } + + // Set methods + + /** + * Set what is to be exported. + * @param entity What to export + */ + public void setEntity(ModelExportEntity entity) + { + this.entity = entity; + } + + /** + * Set where to export to. + * @param file File to export to (null means stdout) + */ + public void setFile(File file) + { + this.file = file; + } + + /** + * Set the export options. + * @param exportOptions Export options + */ + public void setExportOptions(ModelExportOptions exportOptions) + { + this.exportOptions = exportOptions; + } + + /** + * Set, when exporting labels, which ones to export. + * @param labelExportSet Which set of labels to export + */ + public void setLabelExportSet(LabelExportSet labelExportSet) + { + this.labelExportSet = labelExportSet; + } + + /** + * Set a source of extra labels + * @param extraLabelsSource Extra labels source + */ + public void setExtraLabelsSource(PropertiesFile extraLabelsSource) + { + this. extraLabelsSource = extraLabelsSource; + } + + /** + * If the file to be exported to is of the form ".ext", plug in the supplied basename, + * i.e., make the new filename "basename.ext". + */ + public void replaceEmptyFileBasename(String basename) + { + if (file != null && file.getName().matches("\\.[a-zA-Z]+")) { + file = new File(basename + file.getName()); + } + } + + // Get methods + + /** + * Get what is to be exported. + */ + public ModelExportEntity getEntity() + { + return entity; + } + + /** + * Is this export task applicable for a given model? + */ + public boolean isApplicable(ModelInfo modelInfo) + { + if (entity == ModelExportEntity.OBSERVATIONS && !modelInfo.getModelType().partiallyObservable()) { + return false; + } + return true; + } + + /** + * Get where to export to (null means stdout). + */ + public File getFile() + { + return file; + } + + /** + * Get the export options. + */ + public ModelExportOptions getExportOptions() + { + return exportOptions; + } + + /** + * Get which ones to export when exporting labels. + */ + public LabelExportSet getLabelExportSet() + { + return labelExportSet; + } + + /** + * Get an (optional) source of extra labels + */ + public PropertiesFile getExtraLabelsSource() + { + return extraLabelsSource; + } + + /** + * Get whether extra labels are used for this export task. + */ + public boolean extraLabelsUsed() + { + return labelExportSet == LabelExportSet.EXTRA || labelExportSet == LabelExportSet.ALL; + } + + /** + * Get a message describing the export task to be done. + */ + public String getMessage() + { + String s = "Exporting " + entity.description(); + s += " " + exportOptions.getFormat().description(); + s += " " + getDestinationStringForFile(file); + return s; + } + + // Utility methods + + /** + * Get a string describing the output destination specified by a File: + * "to file \"filename\"..." if non-null; "below:" if null + */ + private static String getDestinationStringForFile(File file) + { + return (file == null) ? "below:" : "to file \"" + file + "\"..."; + } +} diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index 78f98d00ef..5548e845d8 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -48,6 +48,7 @@ import hybrid.PrismHybrid; import io.ExplicitModelImporter; import io.ModelExportOptions; +import io.ModelExportTask; import io.ModelExportFormat; import jdd.JDD; import jdd.JDDNode; @@ -2465,95 +2466,124 @@ public void exportPRISMModelWithExpandedConstants(File file) throws FileNotFound } /** - * Export various aspects of the current built model together. - * @param file File to export to (if null, print to the log instead) - * @param exportOptions The options for export + * Perform an export task for the current model, building it first if needed. + * @param exportTask Export task */ - public void exportBuiltModelCombined(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException + public void exportBuiltModelTask(ModelExportTask exportTask) throws PrismException, FileNotFoundException { + // Skip non-applicable tasks + if (!exportTask.isApplicable(getModelInfo())) { + return; + } // Build model, if necessary buildModelIfRequired(); - - if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - mainLog.println("\nDRN export currently only supported by explicit engine"); - return; + // Merge export options with PRISM settings and do export + mainLog.println("\n" + exportTask.getMessage()); + ModelExportOptions exportOptions = newMergedModelExportOptions(exportTask.getExportOptions()); + switch (exportTask.getEntity()) { + case MODEL: + doExportBuiltModel(new ModelExportTask(exportTask, exportOptions)); + break; + case STATE_REWARDS: + doExportBuiltModelStateRewards(exportTask.getFile(), exportOptions); + break; + case TRANSITION_REWARDS: + doExportBuiltModelTransRewards(exportTask.getFile(), exportOptions); + break; + case STATES: + doExportBuiltModelStates(exportTask.getFile(), exportOptions); + break; + case OBSERVATIONS: + doExportBuiltModelObservations(exportTask.getFile(), exportOptions); + break; + case LABELS: + doExportBuiltModelLabels(new ModelExportTask(exportTask, exportOptions)); + break; } + } - // Print message - mainLog.print("\nExporting model "); - mainLog.print(exportOptions.getFormat().description() + " "); - mainLog.println(getDestinationStringForFile(file)); - - // Merge export options with settings - exportOptions = newMergedModelExportOptions(exportOptions); + /** + * Export the current model, building it first if needed. + * To configure which model parts are exported, use {@link #exportBuiltModelTask(ModelExportTask)}. + * @param file File to export to (if null, print to the log instead) + * @param exportFormat The format to use for export + */ + public void exportBuiltModel(File file, ModelExportFormat exportFormat) throws PrismException, FileNotFoundException + { + exportBuiltModelTask(ModelExportTask.fromFormat(file, exportFormat)); + } - // Export (explicit engine only) - try (PrismLog out = getPrismLogForFile(file)) { - List labelNames = new ArrayList(); - labelNames.add("init"); - labelNames.add("deadlock"); - labelNames.addAll(getModelInfo().getLabelNames()); - explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); - mcExpl.exportModelCombined(getBuiltModelExplicit(), labelNames, out, exportOptions); - } + /** + * Export the current model, building it first if needed. + * The format to use is specified within {@code exportOptions}. + * To configure which model parts are exported, use {@link #exportBuiltModelTask(ModelExportTask)} + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportBuiltModel(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException + { + exportBuiltModelTask(ModelExportTask.fromOptions(file, exportOptions)); } /** - * Export the transition matrix for the current built model. + * Export the transition matrix/function for the current model, building it first if needed. * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ public void exportBuiltModelTransitions(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException { - // Build model, if necessary - buildModelIfRequired(); - - // Print message - mainLog.print("\nExporting transition matrix "); - mainLog.print(exportOptions.getFormat().description() + " "); - mainLog.println(getDestinationStringForFile(file)); + // This is equivalent to exportBuiltModel + exportBuiltModel(file, exportOptions); + } - // Merge export options with settings - exportOptions = newMergedModelExportOptions(exportOptions); + /** + * Export the state rewards for the current model, building it first if needed. + * If there is more than 1 reward structure, then multiple files are generated + * (e.g. "rew.sta" becomes "rew1.sta", "rew2.sta", ...) + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException + { + if (getRewardInfo().getNumRewardStructs() == 0) { + mainLog.println("\nOmitting state reward export as there are no reward structures"); + return; + } + exportBuiltModelTask(new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, file, exportOptions)); + } - // do export + /** + * Export the transition matrix/function for the current built model. + * This assumes that the model has already been built. + * Various other parts/annotations of the model may also be exported, + * as specified by the {@code exportTask} object. + * @param exportTask Export task (destination, which parts of the model to export, options) + */ + private void doExportBuiltModel(ModelExportTask exportTask) throws PrismException, FileNotFoundException + { + // Export via either symbolic/explicit model checker if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { symbolic.comp.StateModelChecker mcSymb = createModelChecker(null); - mcSymb.exportTransitions(file, exportOptions); + mcSymb.exportModel(exportTask); } else { explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); - mcExpl.exportTransitions(getBuiltModelExplicit(), file, exportOptions); + mcExpl.exportModel(getBuiltModelExplicit(), exportTask); } } /** * Export the state rewards for the current built model. + * This assumes that the model has already been built. * If there is more than 1 reward structure, then multiple files are generated * (e.g. "rew.sta" becomes "rew1.sta", "rew2.sta", ...) * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException + private void doExportBuiltModelStateRewards(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException { - int numRewardStructs = getRewardInfo().getNumRewardStructs(); - if (numRewardStructs == 0) { - mainLog.println("\nOmitting state reward export as there are no reward structures"); - return; - } - - // Build model, if necessary - buildModelIfRequired(); - - // Print message - mainLog.print("\nExporting state rewards "); - mainLog.print(exportOptions.getFormat().description() + " "); - mainLog.println(getDestinationStringForFile(file)); - - // Merge export options with settings - exportOptions = newMergedModelExportOptions(exportOptions); - - // Do export, writing to multiple files if necessary + // Export to multiple files if necessary List files = new ArrayList<>(); + int numRewardStructs = getRewardInfo().getNumRewardStructs(); for (int r = 0; r < numRewardStructs; r++) { String filename = (file != null) ? file.getPath() : null; if (filename != null && numRewardStructs > 1) { @@ -2561,6 +2591,7 @@ public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOpt files.add(filename); } File fileToUse = (filename == null) ? null : new File(filename); + // Export via either symbolic/explicit model checker if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { symbolic.comp.StateModelChecker mcSymb = createModelChecker(null); mcSymb.exportStateRewards(r, fileToUse, exportOptions); @@ -2569,14 +2600,13 @@ public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOpt mcExpl.exportStateRewards(getBuiltModelExplicit(), r, fileToUse, exportOptions); } } - if (files.size() > 1) { mainLog.println("Rewards were exported to multiple files: " + PrismUtils.joinString(files, ",")); } } /** - * Export the transition rewards for the current built model. + * Export the transition rewards for the current model, building it first if needed. * If there is more than 1 reward structure, then multiple files are generated * (e.g. "rew.sta" becomes "rew1.sta", "rew2.sta", ...) * @param file File to export to (if null, print to the log instead) @@ -2584,25 +2614,26 @@ public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOpt */ public void exportBuiltModelTransRewards(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException { - int numRewardStructs = getRewardInfo().getNumRewardStructs(); - if (numRewardStructs == 0) { + if (getRewardInfo().getNumRewardStructs() == 0) { mainLog.println("\nOmitting transition reward export as there are no reward structures"); return; } + exportBuiltModelTask(new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, file, exportOptions)); + } - // Build model, if necessary - buildModelIfRequired(); - - // Print message - mainLog.print("\nExporting transition rewards "); - mainLog.print(exportOptions.getFormat().description() + " "); - mainLog.println(getDestinationStringForFile(file)); - - // Merge export options with settings - exportOptions = newMergedModelExportOptions(exportOptions); - - // Do export, writing to multiple files if necessary + /** + * Export the transition rewards for the current built model. + * This assumes that the model has already been built. + * If there is more than 1 reward structure, then multiple files are generated + * (e.g. "rew.sta" becomes "rew1.sta", "rew2.sta", ...) + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + private void doExportBuiltModelTransRewards(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + { + // Export to multiple files if necessary List files = new ArrayList<>(); + int numRewardStructs = getRewardInfo().getNumRewardStructs(); for (int r = 0; r < numRewardStructs; r++) { String filename = (file != null) ? file.getPath() : null; if (filename != null && numRewardStructs > 1) { @@ -2610,6 +2641,7 @@ public void exportBuiltModelTransRewards(File file, ModelExportOptions exportOpt files.add(filename); } File fileToUse = (filename == null) ? null : new File(filename); + // Export via either symbolic/explicit model checker if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { symbolic.comp.StateModelChecker mcSymb = createModelChecker(null); mcSymb.exportTransRewards(r, fileToUse, exportOptions); @@ -2618,31 +2650,30 @@ public void exportBuiltModelTransRewards(File file, ModelExportOptions exportOpt mcExpl.exportTransRewards(getBuiltModelExplicit(), r, fileToUse, exportOptions); } } - if (files.size() > 1) { mainLog.println("Rewards were exported to multiple files: " + PrismUtils.joinString(files, ",")); } } /** - * Export the states of the currently loaded model. + * Export the states of the currently loaded model, building it first if needed. * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ public void exportBuiltModelStates(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException { - // Build model, if necessary - buildModelIfRequired(); - - // Print message - mainLog.print("\nExporting list of reachable states "); - mainLog.print(exportOptions.getFormat().description() + " "); - mainLog.println(getDestinationStringForFile(file)); - - // Merge export options with settings - exportOptions = newMergedModelExportOptions(exportOptions); + exportBuiltModelTask(new ModelExportTask(ModelExportTask.ModelExportEntity.STATES, file, exportOptions)); + } - // Export + /** + * Export the states of the currently built model. + * This assumes that the model has already been built. + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + private void doExportBuiltModelStates(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + { + // Export via either symbolic/explicit model checker if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { symbolic.comp.StateModelChecker mcSymb = createModelChecker(null); mcSymb.exportStates(file, exportOptions); @@ -2653,7 +2684,7 @@ public void exportBuiltModelStates(File file, ModelExportOptions exportOptions) } /** - * Export the observations of the currently loaded (partially observable) model. + * Export the observations of the currently loaded (partially observable) loaded model, building it first if needed. * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ @@ -2663,47 +2694,53 @@ public void exportBuiltModelObservations(File file, ModelExportOptions exportOpt mainLog.println("\nOmitting observations export as the model is not partially observable"); return; } + exportBuiltModelTask(new ModelExportTask(ModelExportTask.ModelExportEntity.OBSERVATIONS, file, exportOptions)); + } - // Build model, if necessary - buildModelIfRequired(); - - // Print message - mainLog.print("\nExporting list of observations "); - mainLog.print(exportOptions.getFormat().description() + " "); - mainLog.println(getDestinationStringForFile(file)); - - // Merge export options with settings - exportOptions = newMergedModelExportOptions(exportOptions); - + /** + * Export the observations of the current built (partially observable) model. + * This assumes that the model has already been built. + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + private void doExportBuiltModelObservations(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + { // Export (explicit engine only) explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); mcExpl.exportObservations(getBuiltModelExplicit(), file, exportOptions); } /** - * Export the states satisfying labels from the currently loaded model and (optionally) a properties file to a file. - * The PropertiesFile should correspond to the currently loaded model. + * Export the labels and satisfying states of the currently loaded model, building it first if needed. + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportBuiltModelLabels(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + { + exportBuiltModelLabels(null, file, exportOptions); + } + + /** + * Export model, and optionally property file, labels and the satisfying states + * of the currently loaded model, building it first if needed. + * The PropertiesFile (if non-null) should correspond to the currently loaded model. * @param propertiesFile The properties file, for further labels (ignored if null) * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ public void exportBuiltModelLabels(PropertiesFile propertiesFile, File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException { - // Collect names of labels to export from model - List labelNames = new ArrayList(); - labelNames.add("init"); - labelNames.add("deadlock"); - labelNames.addAll(getModelInfo().getLabelNames()); - // Collect names of labels to export from properties file + ModelExportTask exportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file, exportOptions); if (propertiesFile != null) { - LabelList ll = propertiesFile.getLabelList(); - new Range(ll.size()).map((int i) -> ll.getLabelName(i)).collect(labelNames); + exportTask.setLabelExportSet(ModelExportTask.LabelExportSet.ALL); + exportTask.setExtraLabelsSource(propertiesFile); } - doExportBuiltModelLabels(propertiesFile, labelNames, file, exportOptions); + exportBuiltModelTask(exportTask); } /** - * Export the states satisfying labels from the properties file to a file. + * Export labels from a properties file and the satisfying states + * of the currently loaded model, building it first if needed. * The PropertiesFile should correspond to the currently loaded model. * @param propertiesFile The properties file (for further labels) * @param file File to export to (if null, print to the log instead) @@ -2711,37 +2748,33 @@ public void exportBuiltModelLabels(PropertiesFile propertiesFile, File file, Mod */ public void exportBuiltModelPropLabels(PropertiesFile propertiesFile, File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException { - // Collect names of labels to export from properties file - List labelNames = new ArrayList(); - if (propertiesFile != null) { - LabelList ll = propertiesFile.getLabelList(); - new Range(ll.size()).map((int i) -> ll.getLabelName(i)).collect(labelNames); - } - doExportBuiltModelLabels(propertiesFile, labelNames, file, exportOptions); + ModelExportTask exportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file, exportOptions); + exportTask.setLabelExportSet(ModelExportTask.LabelExportSet.EXTRA); + exportTask.setExtraLabelsSource(propertiesFile); + exportBuiltModelTask(exportTask); } /** - * Export the states satisfying labels from the currently loaded model and/or a properties file to a file. - * The PropertiesFile should correspond to the currently loaded model. - * @param propertiesFile The properties file, for further labels (ignored if null) - * @param labelNames The list of label names to export - * @param file File to export to (if null, print to the log instead) - * @param exportOptions The options for export + * Export the states satisfying a set of labels, as specified in a ModelExportTask. + * @param exportTask Export task (destination, which labels to export, options) */ - private void doExportBuiltModelLabels(PropertiesFile propertiesFile, List labelNames, File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + private void doExportBuiltModelLabels(ModelExportTask exportTask) throws FileNotFoundException, PrismException { - // Build model, if necessary - buildModelIfRequired(); - - // Print message - mainLog.print("\nExporting labels and satisfying states "); - mainLog.print(exportOptions.getFormat().description() + " "); - mainLog.println(getDestinationStringForFile(file)); - - // Merge export options with settings - exportOptions = newMergedModelExportOptions(exportOptions); - - // Export + // Collect names of labels to export from model and/or properties file + List labelNames = new ArrayList<>(); + if (exportTask.getLabelExportSet() == ModelExportTask.LabelExportSet.MODEL || exportTask.getLabelExportSet() == ModelExportTask.LabelExportSet.ALL) { + labelNames.add("init"); + labelNames.add("deadlock"); + labelNames.addAll(getModelInfo().getLabelNames()); + } + if (exportTask.getLabelExportSet() == ModelExportTask.LabelExportSet.EXTRA || exportTask.getLabelExportSet() == ModelExportTask.LabelExportSet.ALL) { + LabelList ll = exportTask.getExtraLabelsSource().getLabelList(); + new Range(ll.size()).map((int i) -> ll.getLabelName(i)).collect(labelNames); + } + // Export via either symbolic/explicit model checker + PropertiesFile propertiesFile = exportTask.getExtraLabelsSource(); + File file = exportTask.getFile(); + ModelExportOptions exportOptions = exportTask.getExportOptions(); if (getBuiltModelType() != ModelBuildType.SYMBOLIC) { explicit.StateModelChecker mcExpl = createModelCheckerExplicit(propertiesFile); mcExpl.exportLabels(getBuiltModelExplicit(), labelNames, file, exportOptions); @@ -4348,6 +4381,18 @@ public void exportToDotFile(File file) throws FileNotFoundException, PrismExcept exportBuiltModelTransitions(file, new ModelExportOptions(ModelExportFormat.DD_DOT)); } + /** + * @deprecated + * Export various aspects of the current built model together. + * @param file File to export to + * @param exportOptions The options for export + */ + @Deprecated + public void exportBuiltModelCombined(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException + { + exportBuiltModel(file, exportOptions); + } + /** * Convert a {@code ModelExportOptions} object to an {@code exportType} value. */ diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 56c645676f..630709c7ce 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -1017,7 +1017,7 @@ private void doExports() try { File f = (exportModelCombinedFilename.equals("stdout")) ? null : new File(exportModelCombinedFilename); exportModelCombinedOptions.apply(modelExportOptionsGlobal); - prism.exportBuiltModelCombined(f, exportModelCombinedOptions); + prism.exportBuiltModel(f, exportModelCombinedOptions); } // in case of error, report it and proceed catch (FileNotFoundException e) { diff --git a/prism/src/symbolic/comp/StateModelChecker.java b/prism/src/symbolic/comp/StateModelChecker.java index f3ed7d4aa9..dd38c7b1f6 100644 --- a/prism/src/symbolic/comp/StateModelChecker.java +++ b/prism/src/symbolic/comp/StateModelChecker.java @@ -36,6 +36,7 @@ import io.ModelExportFormat; import io.ModelExportOptions; +import io.ModelExportTask; import mtbdd.PrismMTBDD; import dv.DoubleVector; import jdd.*; @@ -1694,16 +1695,20 @@ public Set getDefinedLabelNames() } /** - * Export the transition matrix. - * @param file File to export to (if null, print to the log instead) - * @param exportOptions The options for export + * Export the model. + * @param exportTask Export task (destination, which parts of the model to export, options) */ - public void exportTransitions(File file, ModelExportOptions exportOptions) throws PrismException + public void exportModel(ModelExportTask exportTask) throws PrismException { + ModelExportOptions exportOptions = exportTask.getExportOptions(); + File file = exportTask.getFile(); if (exportOptions.getFormat() == ModelExportFormat.DD_DOT) { JDD.ExportDDToDotFileLabelled(model.getTrans(), file.getPath(), model.getDDVarNames()); return; } + if (exportOptions.getFormat() == ModelExportFormat.DRN) { + throw new PrismException("DRN export not yet supported by the symbolic engine"); + } int precision = exportOptions.getModelPrecision(); try { @@ -1722,6 +1727,16 @@ public void exportTransitions(File file, ModelExportOptions exportOptions) throw } } + /** + * Export the transition function/matrix. + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportTransitions(File file, ModelExportOptions exportOptions) throws PrismException + { + exportModel(ModelExportTask.fromOptions(file, exportOptions)); + } + /** * Export the state rewards for one reward structure. * @param r Index of reward structure to export (0-indexed) From 3f52ccf0b3a7c06478cdb2551b303ecb7c293ce2 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Mon, 30 Dec 2024 09:34:15 +0000 Subject: [PATCH 08/44] Use ModelExportTask for PrismCL export switches. This allows multiple -exportmodel with independent options to be used. --- prism/src/prism/PrismCL.java | 433 +++++++++-------------------------- 1 file changed, 108 insertions(+), 325 deletions(-) diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 630709c7ce..e9da3d1a00 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -43,6 +43,7 @@ import csv.CsvFormatException; import io.ModelExportOptions; import io.ModelExportFormat; +import io.ModelExportTask; import parser.Values; import parser.ast.Expression; import parser.ast.ExpressionReward; @@ -83,18 +84,6 @@ public class PrismCL implements PrismModelListener private boolean dotransient = false; private boolean exportprism = false; private boolean exportprismconst = false; - private boolean exporttrans = false; - private boolean exportstaterewards = false; - private boolean exporttransrewards = false; - private boolean exportstates = false; - private boolean exportobservations = false; - private boolean exportmodellabels = false; - private boolean exportmodelproplabels = false; - private boolean exportproplabels = false; - private boolean exportmodelcombined = false; - private boolean exportdot = false; - private boolean exporttransdot = false; - private boolean exporttransdotstates = false; private boolean exportmodeldotview = false; private boolean exportsccs = false; private boolean exportbsccs = false; @@ -102,19 +91,8 @@ public class PrismCL implements PrismModelListener private boolean exportresults = false; private ResultsExportShape exportShape = ResultsExportShape.LIST_PLAIN; private boolean exportvector = false; - private boolean exportModelNoBasename = false; private int exportType = Prism.EXPORT_PLAIN; private boolean exportstrat = false; - private ModelExportOptions exportTransOptions = new ModelExportOptions(); - private ModelExportOptions exportStateRewardsOptions = new ModelExportOptions(); - private ModelExportOptions exportTransRewardsOptions = new ModelExportOptions(); - private ModelExportOptions exportStatesOptions = new ModelExportOptions(); - private ModelExportOptions exportObservationsOptions = new ModelExportOptions(); - private ModelExportOptions exportLabelsOptions = new ModelExportOptions(); - private ModelExportOptions exportTransDotOptions = new ModelExportOptions(); - private ModelExportOptions exportTransDotStatesOptions = new ModelExportOptions(); - private ModelExportOptions exportModelCombinedOptions = new ModelExportOptions(); - private ModelExportOptions modelExportOptionsGlobal = new ModelExportOptions(); private boolean simulate = false; private boolean simpath = false; private boolean param = false; @@ -126,6 +104,10 @@ public class PrismCL implements PrismModelListener private boolean test = false; private boolean testExitsOnFail = true; + // export info + private List modelExportTasks = new ArrayList<>(); + private ModelExportOptions modelExportOptionsGlobal = new ModelExportOptions(); + // property info private List propertyIndices = null; private String propertyString = ""; @@ -156,17 +138,6 @@ public class PrismCL implements PrismModelListener private String propertiesFilename = null; private String exportPrismFilename = null; private String exportPrismConstFilename = null; - private String exportTransFilename = null; - private String exportStateRewardsFilename = null; - private String exportTransRewardsFilename = null; - private String exportStatesFilename = null; - private String exportObservationsFilename = null; - private String exportModelLabelsFilename = null; - private String exportPropLabelsFilename = null; - private String exportDotFilename = null; - private String exportTransDotFilename = null; - private String exportTransDotStatesFilename = null; - private String exportModelCombinedFilename = null; private String exportSCCsFilename = null; private String exportBSCCsFilename = null; private String exportMECsFilename = null; @@ -305,7 +276,7 @@ public void run(String[] args) try { // first, see which constants are undefined // (one set of info for model, and one set of info for each property) - if (exportmodellabels && exportmodelproplabels || exportproplabels) { + if (modelExportTasks.stream().anyMatch(ModelExportTask::extraLabelsUsed)) { undefinedMFConstants = new UndefinedConstants(modulesFile, propertiesFile, true); } else { undefinedMFConstants = new UndefinedConstants(modulesFile, null); @@ -809,18 +780,8 @@ private void doExports() } } - if (exporttrans || - exportstaterewards || - exporttransrewards || - exportstates || - exportobservations || - exportdot || - exporttransdot || - exporttransdotstates || - exportmodelcombined || + if (!modelExportTasks.isEmpty() || exportmodeldotview || - exportmodellabels || - exportproplabels || exportsccs || exportbsccs || exportmecs) { @@ -834,117 +795,20 @@ private void doExports() } } - // export transition matrix to a file - if (exporttrans) { + // Do export tasks + for (ModelExportTask exportTask : modelExportTasks) { try { - File f = (exportTransFilename.equals("stdout")) ? null : new File(exportTransFilename); - exportTransOptions.apply(modelExportOptionsGlobal); - prism.exportBuiltModelTransitions(f, exportTransOptions); - } - // in case of error, report it and proceed - catch (FileNotFoundException e) { - error("Couldn't open file \"" + exportTransFilename + "\" for output"); - } catch (PrismException e) { - error(e); - } - } - - // export state rewards to a file - if (exportstaterewards) { - try { - File f = (exportStateRewardsFilename.equals("stdout")) ? null : new File(exportStateRewardsFilename); - exportStateRewardsOptions.apply(modelExportOptionsGlobal); - prism.exportBuiltModelStateRewards(f, exportStateRewardsOptions); - } - // in case of error, report it and proceed - catch (FileNotFoundException e) { - error("Couldn't open file \"" + exportStateRewardsFilename + "\" for output"); - } catch (PrismException e) { - error(e); - } - } - - // export transition rewards to a file - if (exporttransrewards) { - try { - File f = (exportTransRewardsFilename.equals("stdout")) ? null : new File(exportTransRewardsFilename); - exportTransRewardsOptions.apply(modelExportOptionsGlobal); - prism.exportBuiltModelTransRewards(f, exportTransRewardsOptions); - } - // in case of error, report it and proceed - catch (FileNotFoundException e) { - error("Couldn't open file \"" + exportTransRewardsFilename + "\" for output"); - } catch (PrismException e) { - error(e); - } - } - - // export states list - if (exportstates) { - try { - File f = (exportStatesFilename.equals("stdout")) ? null : new File(exportStatesFilename); - exportStatesOptions.apply(modelExportOptionsGlobal); - prism.exportBuiltModelStates(f, exportStatesOptions); - } - // in case of error, report it and proceed - catch (FileNotFoundException e) { - error("Couldn't open file \"" + exportStatesFilename + "\" for output"); - } catch (PrismException e) { - error(e); - } - } - - // export observations list - if (exportobservations) { - try { - File f = (exportObservationsFilename.equals("stdout")) ? null : new File(exportObservationsFilename); - exportObservationsOptions.apply(modelExportOptionsGlobal); - prism.exportBuiltModelObservations(f, exportObservationsOptions); - } - // in case of error, report it and proceed - catch (FileNotFoundException e) { - error("Couldn't open file \"" + exportObservationsFilename + "\" for output"); - } catch (PrismException e) { - error(e); - } - } - - // export mtbdd to dot file - if (exportdot) { - try { - prism.exportBuiltModelTransitions(new File(exportDotFilename), new ModelExportOptions(ModelExportFormat.DD_DOT)); - } - // in case of error, report it and proceed - catch (FileNotFoundException e) { - error("Couldn't open file \"" + exportDotFilename + "\" for output"); - } catch (PrismException e) { - error(e); - } - } - - // export transition matrix graph to dot file - if (exporttransdot) { - try { - File f = (exportTransDotFilename.equals("stdout")) ? null : new File(exportTransDotFilename); - prism.exportBuiltModelTransitions(f, exportTransDotOptions); - } - // in case of error, report it and proceed - catch (FileNotFoundException e) { - error("Couldn't open file \"" + exportTransDotFilename + "\" for output"); - } catch (PrismException e) { - error(e); - } - } - - // export transition matrix graph to dot file (with states) - if (exporttransdotstates) { - try { - File f = (exportTransDotStatesFilename.equals("stdout")) ? null : new File(exportTransDotStatesFilename); - prism.exportBuiltModelTransitions(f, exportTransDotStatesOptions); + exportTask.getExportOptions().apply(modelExportOptionsGlobal); + if (exportTask.extraLabelsUsed()) { + definedPFConstants = undefinedMFConstants.getPFConstantValues(); + propertiesFile.setSomeUndefinedConstants(definedPFConstants, exactConstants); + exportTask.setExtraLabelsSource(propertiesFile); + } + prism.exportBuiltModelTask(exportTask); } - // in case of error, report it and proceed + // In case of error, report it and proceed catch (FileNotFoundException e) { - error("Couldn't open file \"" + exportTransDotStatesFilename + "\" for output"); + error("Couldn't open file \"" + exportTask.getFile().getName() + "\" for output"); } catch (PrismException e) { error(e); } @@ -955,7 +819,7 @@ private void doExports() try { File dotFile = File.createTempFile("prism-dot-", ".dot", null); File dotPdfFile = File.createTempFile("prism-dot-", ".dot.pdf", null); - prism.exportBuiltModelTransitions(dotFile, exportTransDotStatesOptions); + prism.exportBuiltModelTransitions(dotFile, new ModelExportOptions().setFormat(ModelExportFormat.DOT).setShowStates(true)); (new ProcessBuilder(new String[]{ "dot", "-Tpdf", "-o", dotPdfFile.getPath(), dotFile.getPath()})).start().waitFor(); (new ProcessBuilder(new String[]{ "open",dotPdfFile.getPath()})).start(); } @@ -967,66 +831,6 @@ private void doExports() } } - // export labels/states - if (exportmodellabels) { - try { - File f = (exportModelLabelsFilename.equals("stdout")) ? null : new File(exportModelLabelsFilename); - if (propertiesFile != null && exportmodelproplabels) { - // export labels from model and properties to same file - definedPFConstants = undefinedMFConstants.getPFConstantValues(); - propertiesFile.setSomeUndefinedConstants(definedPFConstants, exactConstants); - exportLabelsOptions.apply(modelExportOptionsGlobal); - prism.exportBuiltModelLabels(propertiesFile, f, exportLabelsOptions); - } else { - // export labels from model only - exportLabelsOptions.apply(modelExportOptionsGlobal); - prism.exportBuiltModelLabels(null, f, exportLabelsOptions); - } - } - // in case of error, report it and proceed - catch (FileNotFoundException e) { - mainLog.println("Couldn't open file \"" + exportModelLabelsFilename + "\" for output"); - } catch (PrismException e) { - mainLog.println("\nError: " + e.getMessage() + "."); - } - } - - // export labels/states from properties file - if (exportproplabels) { - try { - File f = (exportPropLabelsFilename.equals("stdout")) ? null : new File(exportPropLabelsFilename); - if (propertiesFile == null) { - throw new PrismException("No properties file provided to export labels from"); - } - // export labels from properties file - definedPFConstants = undefinedMFConstants.getPFConstantValues(); - propertiesFile.setSomeUndefinedConstants(definedPFConstants, exactConstants); - exportLabelsOptions.apply(modelExportOptionsGlobal); - prism.exportBuiltModelPropLabels(propertiesFile, f, exportLabelsOptions); - } - // in case of error, report it and proceed - catch (FileNotFoundException e) { - mainLog.println("Couldn't open file \"" + exportModelLabelsFilename + "\" for output"); - } catch (PrismException e) { - mainLog.println("\nError: " + e.getMessage() + "."); - } - } - - // export combined aspects of model - if (exportmodelcombined) { - try { - File f = (exportModelCombinedFilename.equals("stdout")) ? null : new File(exportModelCombinedFilename); - exportModelCombinedOptions.apply(modelExportOptionsGlobal); - prism.exportBuiltModel(f, exportModelCombinedOptions); - } - // in case of error, report it and proceed - catch (FileNotFoundException e) { - error("Couldn't open file \"" + exportModelCombinedFilename + "\" for output"); - } catch (PrismException e) { - error(e); - } - } - // export SCCs to a file if (exportsccs) { try { @@ -1624,8 +1428,7 @@ else if (sw.equals("exportmodel")) { // export transition matrix to file else if (sw.equals("exporttrans")) { if (i < args.length - 1) { - exporttrans = true; - exportTransFilename = args[++i]; + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, args[++i])); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1633,8 +1436,7 @@ else if (sw.equals("exporttrans")) { // export state rewards to file else if (sw.equals("exportstaterewards")) { if (i < args.length - 1) { - exportstaterewards = true; - exportStateRewardsFilename = args[++i]; + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, args[++i])); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1642,8 +1444,7 @@ else if (sw.equals("exportstaterewards")) { // export transition rewards to file else if (sw.equals("exporttransrewards")) { if (i < args.length - 1) { - exporttransrewards = true; - exportTransRewardsFilename = args[++i]; + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, args[++i])); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1651,10 +1452,8 @@ else if (sw.equals("exporttransrewards")) { // export both state/transition rewards to file else if (sw.equals("exportrewards")) { if (i < args.length - 2) { - exportstaterewards = true; - exporttransrewards = true; - exportStateRewardsFilename = args[++i]; - exportTransRewardsFilename = args[++i]; + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, args[++i])); + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, args[++i])); } else { errorAndExit("Two files must be specified for -" + sw + " switch"); } @@ -1662,8 +1461,7 @@ else if (sw.equals("exportrewards")) { // export states else if (sw.equals("exportstates")) { if (i < args.length - 1) { - exportstates = true; - exportStatesFilename = args[++i]; + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.STATES, args[++i])); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1671,8 +1469,7 @@ else if (sw.equals("exportstates")) { // export observations else if (sw.equals("exportobs")) { if (i < args.length - 1) { - exportobservations = true; - exportObservationsFilename = args[++i]; + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.OBSERVATIONS, args[++i])); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1680,8 +1477,7 @@ else if (sw.equals("exportobs")) { // export labels/states else if (sw.equals("exportlabels")) { if (i < args.length - 1) { - exportmodellabels = true; - exportModelLabelsFilename = processExportLabelsSwitch(args[++i]); + processExportLabelsSwitch(args[++i]); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1689,8 +1485,7 @@ else if (sw.equals("exportlabels")) { // export labels/states from properties file else if (sw.equals("exportproplabels")) { if (i < args.length - 1) { - exportproplabels = true; - exportPropLabelsFilename = processExportLabelsSwitch(args[++i]); + processExportPropLabelsSwitch(args[++i]); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1720,10 +1515,8 @@ else if (sw.equals("exportunordered") || sw.equals("unordered")) { // export transition matrix graph to dot file else if (sw.equals("exporttransdot")) { if (i < args.length - 1) { - exporttransdot = true; - exportTransDotFilename = args[++i]; - exportTransDotOptions = new ModelExportOptions(ModelExportFormat.DOT); - exportTransDotOptions.setShowStates(false); + ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DOT).setShowStates(false); + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, args[++i], exportOptions)); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1731,10 +1524,8 @@ else if (sw.equals("exporttransdot")) { // export transition matrix graph to dot file (with states) else if (sw.equals("exporttransdotstates")) { if (i < args.length - 1) { - exporttransdotstates = true; - exportTransDotStatesFilename = args[++i]; - exportTransDotStatesOptions = new ModelExportOptions(ModelExportFormat.DOT); - exportTransDotStatesOptions.setShowStates(true); + ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DOT).setShowStates(true); + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, args[++i], exportOptions)); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1742,8 +1533,8 @@ else if (sw.equals("exporttransdotstates")) { // export transition matrix MTBDD to dot file else if (sw.equals("exportdot")) { if (i < args.length - 1) { - exportdot = true; - exportDotFilename = args[++i]; + ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DD_DOT); + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, args[++i], exportOptions)); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -2015,8 +1806,6 @@ else if (sw.equals("mainlog")) { // export transition matrix graph to dot file and view it (hidden option, for now) else if (sw.equals("exportmodeldotview")) { exportmodeldotview = true; - exportTransDotStatesOptions = new ModelExportOptions(ModelExportFormat.DOT); - exportTransDotStatesOptions.setShowStates(true); } // mtbdd construction method (hidden option) else if (sw.equals("c1")) { @@ -2240,15 +2029,13 @@ private void getTransRewardsFilenames(String basename, boolean assumeExists) } /** - * Process the arguments (file, options) to the -export(prop)labels switch. - * Currently, only one option is supported: proplabels, cf. - * {@link #processExportModelSwitch} - * @return The name of the export file + * Process the arguments (file, options) to the -exportlabels switch. */ - private String processExportLabelsSwitch(String filesOptionsString) throws PrismException + private void processExportLabelsSwitch(String filesOptionsString) throws PrismException { // Split into files/options (on :) String pair[] = splitFilesAndOptions(filesOptionsString); + ModelExportTask newExportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, pair[0]); String options[] = pair[1].split(","); for (String opt : options) { // Ignore "" @@ -2256,17 +2043,42 @@ private String processExportLabelsSwitch(String filesOptionsString) throws Prism } // Export type else if (opt.equals("matlab")) { - exportType = Prism.EXPORT_MATLAB; - exportLabelsOptions.setFormat(ModelExportFormat.MATLAB); + newExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB); } else if (opt.equals("proplabels")) { - exportmodelproplabels = true; + newExportTask.setLabelExportSet(ModelExportTask.LabelExportSet.ALL); } // Unknown option else { throw new PrismException("Unknown option \"" + opt + "\" for -exportlabels switch"); } } - return pair[0]; + modelExportTasks.add(newExportTask); + } + + /** + * Process the arguments (file, options) to the -exportproplabels switch. + */ + private void processExportPropLabelsSwitch(String filesOptionsString) throws PrismException + { + // Split into files/options (on :) + String pair[] = splitFilesAndOptions(filesOptionsString); + ModelExportTask newExportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, pair[0]); + newExportTask.setLabelExportSet(ModelExportTask.LabelExportSet.EXTRA); + String options[] = pair[1].split(","); + for (String opt : options) { + // Ignore "" + if (opt.equals("")) { + } + // Export type + else if (opt.equals("matlab")) { + newExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB); + } + // Unknown option + else { + throw new PrismException("Unknown option \"" + opt + "\" for -exportproplabels switch"); + } + } + modelExportTasks.add(newExportTask); } /** @@ -2283,64 +2095,42 @@ private void processExportModelSwitch(String filesOptionsString) throws PrismExc String optionsString = halves[1]; // Split files into basename/extensions int i = filesString.lastIndexOf('.'); - if (i == -1) + if (i == -1) { throw new PrismException("No file name extension(s) in file(s) \"" + filesString + "\" for -exportmodel"); + } String basename = filesString.substring(0, i); String extList = filesString.substring(i + 1); String exts[] = extList.split(","); - // Check for empty base name (e.g. ".all") - will be replaced with modelname - if (basename.length() == 0) { - basename = "modelFileBasename"; - exportModelNoBasename = true; - } // Process file extensions + List newModelExportTasks = new ArrayList<>(); for (String ext : exts) { // Items to export if (ext.equals("all")) { - exporttrans = true; - exportTransFilename = basename.equals("stdout") ? "stdout" : basename + ".tra"; - exportstaterewards = true; - exportStateRewardsFilename = basename.equals("stdout") ? "stdout" : basename + ".srew"; - exporttransrewards = true; - exportTransRewardsFilename = basename.equals("stdout") ? "stdout" : basename + ".trew"; - exportstates = true; - exportStatesFilename = basename.equals("stdout") ? "stdout" : basename + ".sta"; - exportobservations = true; - exportObservationsFilename = basename.equals("stdout") ? "stdout" : basename + ".obs"; - exportmodellabels = true; - exportModelLabelsFilename = basename.equals("stdout") ? "stdout" : basename + ".lab"; + newModelExportTasks.add(ModelExportTask.fromFilename(basename, "tra")); + newModelExportTasks.add(ModelExportTask.fromFilename(basename, "srew")); + newModelExportTasks.add(ModelExportTask.fromFilename(basename, "trew")); + newModelExportTasks.add(ModelExportTask.fromFilename(basename, "sta")); + newModelExportTasks.add(ModelExportTask.fromFilename(basename, "obs")); + newModelExportTasks.add(ModelExportTask.fromFilename(basename, "lab")); } else if (ext.equals("tra")) { - exporttrans = true; - exportTransFilename = basename.equals("stdout") ? "stdout" : basename + ".tra"; + newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); } else if (ext.equals("srew")) { - exportstaterewards = true; - exportStateRewardsFilename = basename.equals("stdout") ? "stdout" : basename + ".srew"; + newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); } else if (ext.equals("trew")) { - exporttransrewards = true; - exportTransRewardsFilename = basename.equals("stdout") ? "stdout" : basename + ".trew"; + newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); } else if (ext.equals("rew")) { - exportstaterewards = true; - exportStateRewardsFilename = basename.equals("stdout") ? "stdout" : basename + ".srew"; - exporttransrewards = true; - exportTransRewardsFilename = basename.equals("stdout") ? "stdout" : basename + ".trew"; + newModelExportTasks.add(ModelExportTask.fromFilename(basename, "srew")); + newModelExportTasks.add(ModelExportTask.fromFilename(basename, "trew")); } else if (ext.equals("sta")) { - exportstates = true; - exportStatesFilename = basename.equals("stdout") ? "stdout" : basename + ".sta"; + newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); } else if (ext.equals("obs")) { - exportobservations = true; - exportObservationsFilename = basename.equals("stdout") ? "stdout" : basename + ".obs"; + newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); } else if (ext.equals("lab")) { - exportmodellabels = true; - exportModelLabelsFilename = basename.equals("stdout") ? "stdout" : basename + ".lab"; + newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); } else if (ext.equals("dot")) { - exporttransdotstates = true; - exportTransDotStatesFilename = basename.equals("stdout") ? "stdout" : basename + ".dot"; - exportTransDotStatesOptions = new ModelExportOptions(ModelExportFormat.DOT); - exportTransDotStatesOptions.setShowStates(true); + newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); } else if (ext.equals("drn")) { - exportmodelcombined = true; - exportModelCombinedFilename = basename.equals("stdout") ? "stdout" : basename + ".drn"; - exportModelCombinedOptions = new ModelExportOptions(ModelExportFormat.DRN); + newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); } // Unknown extension else { @@ -2348,6 +2138,7 @@ private void processExportModelSwitch(String filesOptionsString) throws PrismExc } } // Process options + ModelExportOptions exportOptions = new ModelExportOptions(); String options[] = optionsString.split(","); for (String opt : options) { // Ignore "" @@ -2355,17 +2146,11 @@ private void processExportModelSwitch(String filesOptionsString) throws PrismExc } // Export type else if (opt.equals("matlab")) { + exportOptions.setFormat(ModelExportFormat.MATLAB); exportType = Prism.EXPORT_MATLAB; - exportTransOptions.setFormat(ModelExportFormat.MATLAB); - exportStateRewardsOptions.setFormat(ModelExportFormat.MATLAB); - exportTransRewardsOptions.setFormat(ModelExportFormat.MATLAB); - exportStatesOptions.setFormat(ModelExportFormat.MATLAB); - exportObservationsOptions.setFormat(ModelExportFormat.MATLAB); - exportLabelsOptions.setFormat(ModelExportFormat.MATLAB); } else if (opt.equals("rows")) { + exportOptions.setExplicitRows(true); exportType = Prism.EXPORT_ROWS; - exportTransOptions.setExplicitRows(true); - exportTransRewardsOptions.setExplicitRows(true); } /*else if (opt.startsWith("type=")) { String exportTypeString = opt.substring(5); if (exportTypeString.equals("matlab")) { @@ -2377,18 +2162,20 @@ else if (opt.equals("matlab")) { } }*/ else if (opt.equals("proplabels")) { - exportmodelproplabels = true; + for (ModelExportTask exportTask : newModelExportTasks) { + if (exportTask.getEntity() == ModelExportTask.ModelExportEntity.LABELS) { + exportTask.setLabelExportSet(ModelExportTask.LabelExportSet.ALL); + } + } } else if (opt.startsWith("actions")) { if (!opt.startsWith("actions=")) throw new PrismException("No value provided for \"actions\" option of -exportmodel"); String optVal = opt.substring(8); if (optVal.equals("true")) { - exportTransOptions.setShowActions(true); - exportTransRewardsOptions.setShowActions(true); + exportOptions.setShowActions(true); } else if (optVal.equals("false")) { - exportTransOptions.setShowActions(false); - exportTransRewardsOptions.setShowActions(false); + exportOptions.setShowActions(false); } else throw new PrismException("Unknown value \"" + optVal + "\" provided for \"reach\" option of -exportstrat"); @@ -2398,6 +2185,12 @@ else if (opt.startsWith("actions")) { throw new PrismException("Unknown option \"" + opt + "\" for -exportmodel switch"); } } + // Apply options from this switch to each export task + for (ModelExportTask exportTask : newModelExportTasks) { + exportTask.getExportOptions().apply(exportOptions); + } + // Add export tasks to the main list + modelExportTasks.addAll(newModelExportTasks); } /** @@ -2632,25 +2425,15 @@ else if (prism.getEngine() == Prism.SPARSE && !test) { } } - // plug in basename for -exportmodel switch if needed - if (exportModelNoBasename) { + // Plug in model basename for model exports where needed + if (!modelExportTasks.isEmpty()) { String modelFileBasename = modelFilename; - if (modelFileBasename.lastIndexOf('.') > -1) + if (modelFileBasename.lastIndexOf('.') > -1) { modelFileBasename = modelFilename.substring(0, modelFileBasename.lastIndexOf('.')); - if (exporttrans) - exportTransFilename = exportTransFilename.replaceFirst("modelFileBasename", modelFileBasename); - if (exportstaterewards) - exportStateRewardsFilename = exportStateRewardsFilename.replaceFirst("modelFileBasename", modelFileBasename); - if (exporttransrewards) - exportTransRewardsFilename = exportTransRewardsFilename.replaceFirst("modelFileBasename", modelFileBasename); - if (exportstates) - exportStatesFilename = exportStatesFilename.replaceFirst("modelFileBasename", modelFileBasename); - if (exportobservations) - exportObservationsFilename = exportObservationsFilename.replaceFirst("modelFileBasename", modelFileBasename); - if (exportmodellabels) - exportModelLabelsFilename = exportModelLabelsFilename.replaceFirst("modelFileBasename", modelFileBasename); - if (exporttransdotstates) - exportTransDotStatesFilename = exportTransDotStatesFilename.replaceFirst("modelFileBasename", modelFileBasename); + } + for (ModelExportTask exportTask : modelExportTasks) { + exportTask.replaceEmptyFileBasename(modelFileBasename); + } } } From cffa4bdec86abb697f06e50c2f47aa93ebd78679 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Thu, 2 Jan 2025 09:53:20 +0000 Subject: [PATCH 09/44] Use ModelExportTask for GUI exports. This means the deprecated Prism.exportXXX methods are no longer used. --- .../userinterface/model/GUIMultiModel.java | 175 ++++++++---------- .../model/GUIMultiModelHandler.java | 50 ++--- .../computation/ComputeSteadyStateThread.java | 20 +- .../computation/ComputeTransientThread.java | 20 +- .../computation/ExportBuiltModelThread.java | 59 +----- .../properties/GUIMultiProperties.java | 58 +++--- 6 files changed, 151 insertions(+), 231 deletions(-) diff --git a/prism/src/userinterface/model/GUIMultiModel.java b/prism/src/userinterface/model/GUIMultiModel.java index abcb87ee1f..1fe6abdc73 100644 --- a/prism/src/userinterface/model/GUIMultiModel.java +++ b/prism/src/userinterface/model/GUIMultiModel.java @@ -53,8 +53,11 @@ import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; +import io.ModelExportFormat; +import io.ModelExportOptions; +import io.ModelExportTask; +import io.ModelExportTask.ModelExportEntity; import prism.ModelType; -import prism.Prism; import prism.PrismSettings; import prism.PrismSettingsListener; import userinterface.GUIClipboardEvent; @@ -78,7 +81,7 @@ public class GUIMultiModel extends GUIPlugin implements PrismSettingsListener private JMenu exportStatesMenu, exportTransMenu, exportObsMenu, exportStateRewardsMenu, exportTransRewardsMenu, exportLabelsMenu, exportSSMenu, exportTrMenu; private AbstractAction viewStates, viewTrans, viewObs, viewStateRewards, viewTransRewards, viewLabels, viewPrismCode, computeSS, computeTr, newPRISMModel, newPEPAModel, loadModel, reloadModel, saveModel, saveAsModel, parseModel, buildModel, exportStatesPlain, exportStatesMatlab, - exportTransPlain, exportTransMatlab, exportTransDot, exportTransDotStates, exportObsPlain, exportObsMatlab, exportStateRewardsPlain, exportStateRewardsMatlab, + exportTransPlain, exportTransMatlab, exportTransDot, exportObsPlain, exportObsMatlab, exportStateRewardsPlain, exportStateRewardsMatlab, exportTransRewardsPlain, exportTransRewardsMatlab, exportLabelsPlain, exportLabelsMatlab, exportSSPlain, exportSSMatlab, exportTrPlain, exportTrMatlab; private JPopupMenu popup; @@ -185,7 +188,6 @@ public void doEnables() exportTransPlain.setEnabled(!computing); exportTransMatlab.setEnabled(!computing); exportTransDot.setEnabled(!computing); - exportTransDotStates.setEnabled(!computing); exportObsPlain.setEnabled(!computing); exportObsMatlab.setEnabled(!computing); exportStateRewardsPlain.setEnabled(!computing); @@ -351,54 +353,52 @@ protected void a_build() handler.forceBuild(); } - protected void a_exportBuildAs(int exportEntity, int exportType) + protected void a_exportBuildAs(ModelExportEntity exportEntity, ModelExportFormat exportFormat) { int res = JFileChooser.CANCEL_OPTION; // pop up dialog to select file - switch (exportType) { - case Prism.EXPORT_DOT: - res = showSaveFileDialog(dotFilter); - break; - case Prism.EXPORT_DOT_STATES: - res = showSaveFileDialog(dotFilter); - break; - case Prism.EXPORT_MATLAB: - res = showSaveFileDialog(matlabFilter); - break; - default: - switch (exportEntity) { - case GUIMultiModelHandler.STATES_EXPORT: - res = showSaveFileDialog(staFilters.values(), staFilters.get("sta")); - break; - case GUIMultiModelHandler.TRANS_EXPORT: - res = showSaveFileDialog(traFilters.values(), traFilters.get("tra")); + switch (exportFormat) { + case DOT: + res = showSaveFileDialog(dotFilter); break; - case GUIMultiModelHandler.OBSERVATIONS_EXPORT: - res = showSaveFileDialog(obsFilters.values(), obsFilters.get("obs")); - break; - case GUIMultiModelHandler.LABELS_EXPORT: - res = showSaveFileDialog(labFilters.values(), labFilters.get("lab")); + case MATLAB: + res = showSaveFileDialog(matlabFilter); break; default: - res = showSaveFileDialog(textFilter); - } - break; + switch (exportEntity) { + case ModelExportEntity.STATES: + res = showSaveFileDialog(staFilters.values(), staFilters.get("sta")); + break; + case ModelExportEntity.MODEL: + res = showSaveFileDialog(traFilters.values(), traFilters.get("tra")); + break; + case ModelExportEntity.OBSERVATIONS: + res = showSaveFileDialog(obsFilters.values(), obsFilters.get("obs")); + break; + case ModelExportEntity.LABELS: + res = showSaveFileDialog(labFilters.values(), labFilters.get("lab")); + break; + default: + res = showSaveFileDialog(textFilter); + } + break; } - if (res != JFileChooser.APPROVE_OPTION) + if (res != JFileChooser.APPROVE_OPTION) { return; + } // Reset warnings counter getPrism().getMainLog().resetNumberOfWarnings(); // Do export... - handler.export(exportEntity, exportType, getChooserFile()); + handler.export(new ModelExportTask(exportEntity, getChooserFile(), new ModelExportOptions(exportFormat))); } - protected void a_viewBuild(int exportEntity, int exportType) + protected void a_viewBuild(ModelExportEntity exportEntity, ModelExportFormat exportFormat) { // Reset warnings counter getPrism().getMainLog().resetNumberOfWarnings(); // Do view... - handler.export(exportEntity, exportType, null); + handler.export(new ModelExportTask(exportEntity, (File) null, new ModelExportOptions(exportFormat))); } // protected void a_viewStates() @@ -411,25 +411,26 @@ protected void a_viewCurrentModelBuild() handler.requestViewModel(); } - protected void a_exportSteadyState(int exportType) + protected void a_exportSteadyState(ModelExportFormat exportFormat) { // Pop up dialog to select file int res = JFileChooser.CANCEL_OPTION; - switch (exportType) { - case Prism.EXPORT_MATLAB: - res = showSaveFileDialog(matlabFilter); - break; - case Prism.EXPORT_PLAIN: - default: - res = showSaveFileDialog(textFilter); - break; + switch (exportFormat) { + case MATLAB: + res = showSaveFileDialog(matlabFilter); + break; + case ModelExportFormat.EXPLICIT: + default: + res = showSaveFileDialog(textFilter); + break; } - if (res != JFileChooser.APPROVE_OPTION) + if (res != JFileChooser.APPROVE_OPTION) { return; + } // Reset warnings counter getPrism().getMainLog().resetNumberOfWarnings(); // Do steady-state - handler.computeSteadyState(exportType, getChooserFile()); + handler.computeSteadyState(new ModelExportTask(ModelExportEntity.MODEL, getChooserFile(), new ModelExportOptions(exportFormat))); } protected void a_computeSteadyState() @@ -437,10 +438,10 @@ protected void a_computeSteadyState() // Reset warnings counter getPrism().getMainLog().resetNumberOfWarnings(); // Do steady-state - handler.computeSteadyState(Prism.EXPORT_PLAIN, null); + handler.computeSteadyState(new ModelExportTask(ModelExportEntity.MODEL, (File) null, new ModelExportOptions(ModelExportFormat.EXPLICIT))); } - protected void a_exportTransient(int exportType) + protected void a_exportTransient(ModelExportFormat exportFormat) { // Get time int result = GUITransientTime.requestTime(this.getGUI()); @@ -448,21 +449,22 @@ protected void a_exportTransient(int exportType) return; // Pop up dialog to select file int res = JFileChooser.CANCEL_OPTION; - switch (exportType) { - case Prism.EXPORT_MATLAB: - res = showSaveFileDialog(matlabFilter); - break; - case Prism.EXPORT_PLAIN: - default: - res = showSaveFileDialog(textFilter); - break; + switch (exportFormat) { + case ModelExportFormat.MATLAB: + res = showSaveFileDialog(matlabFilter); + break; + case ModelExportFormat.EXPLICIT: + default: + res = showSaveFileDialog(textFilter); + break; } - if (res != JFileChooser.APPROVE_OPTION) + if (res != JFileChooser.APPROVE_OPTION) { return; + } // Reset warnings counter getPrism().getMainLog().resetNumberOfWarnings(); // Do transient - handler.computeTransient(GUITransientTime.getTime(), exportType, getChooserFile()); + handler.computeTransient(GUITransientTime.getTime(), new ModelExportTask(ModelExportEntity.MODEL, getChooserFile(), new ModelExportOptions(exportFormat))); } protected void a_computeTransient() @@ -474,7 +476,7 @@ protected void a_computeTransient() if (result != GUITransientTime.OK) return; // Do transient - handler.computeTransient(GUITransientTime.getTime(), Prism.EXPORT_PLAIN, null); + handler.computeTransient(GUITransientTime.getTime(), new ModelExportTask(ModelExportEntity.MODEL, (File) null, new ModelExportOptions(ModelExportFormat.EXPLICIT))); } protected void a_convertToPrismTextModel() @@ -621,7 +623,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.STATES_EXPORT, Prism.EXPORT_PLAIN); + a_exportBuildAs(ModelExportEntity.STATES, ModelExportFormat.EXPLICIT); } }; exportStatesPlain.putValue(Action.LONG_DESCRIPTION, "Exports the reachable states to a plain text file"); @@ -633,7 +635,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.STATES_EXPORT, Prism.EXPORT_MATLAB); + a_exportBuildAs(ModelExportEntity.STATES, ModelExportFormat.MATLAB); } }; exportStatesMatlab.putValue(Action.LONG_DESCRIPTION, "Exports the reachable states to a Matlab file"); @@ -645,7 +647,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.TRANS_EXPORT, Prism.EXPORT_PLAIN); + a_exportBuildAs(ModelExportEntity.MODEL, ModelExportFormat.EXPLICIT); } }; exportTransPlain.putValue(Action.LONG_DESCRIPTION, "Exports the transition matrix to a plain text file"); @@ -657,7 +659,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.TRANS_EXPORT, Prism.EXPORT_MATLAB); + a_exportBuildAs(ModelExportEntity.MODEL, ModelExportFormat.MATLAB); } }; exportTransMatlab.putValue(Action.LONG_DESCRIPTION, "Exports the transition matrix to a Matlab file"); @@ -669,7 +671,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.TRANS_EXPORT, Prism.EXPORT_DOT); + a_exportBuildAs(ModelExportEntity.MODEL, ModelExportFormat.DOT); } }; exportTransDot.putValue(Action.LONG_DESCRIPTION, "Exports the transition matrix graph to a Dot file"); @@ -677,23 +679,11 @@ public void actionPerformed(ActionEvent e) exportTransDot.putValue(Action.NAME, "Dot file"); exportTransDot.putValue(Action.SMALL_ICON, GUIPrism.getIconFromImage("smallFileDot.png")); - exportTransDotStates = new AbstractAction() - { - public void actionPerformed(ActionEvent e) - { - a_exportBuildAs(GUIMultiModelHandler.TRANS_EXPORT, Prism.EXPORT_DOT_STATES); - } - }; - exportTransDotStates.putValue(Action.LONG_DESCRIPTION, "Exports the transition matrix graph to a Dot file (with states)"); - exportTransDotStates.putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_S)); - exportTransDotStates.putValue(Action.NAME, "Dot file (with states)"); - exportTransDotStates.putValue(Action.SMALL_ICON, GUIPrism.getIconFromImage("smallFileDot.png")); - exportObsPlain = new AbstractAction() { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.OBSERVATIONS_EXPORT, Prism.EXPORT_PLAIN); + a_exportBuildAs(ModelExportEntity.OBSERVATIONS, ModelExportFormat.EXPLICIT); } }; exportObsPlain.putValue(Action.LONG_DESCRIPTION, "Exports the observations to a plain text file"); @@ -705,7 +695,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.OBSERVATIONS_EXPORT, Prism.EXPORT_MATLAB); + a_exportBuildAs(ModelExportEntity.OBSERVATIONS, ModelExportFormat.MATLAB); } }; exportObsMatlab.putValue(Action.LONG_DESCRIPTION, "Exports the observations to a Matlab file"); @@ -717,7 +707,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.STATE_REWARDS_EXPORT, Prism.EXPORT_PLAIN); + a_exportBuildAs(ModelExportEntity.STATE_REWARDS, ModelExportFormat.EXPLICIT); } }; exportStateRewardsPlain.putValue(Action.LONG_DESCRIPTION, "Exports the state rewards vector to a plain text file"); @@ -729,7 +719,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.STATE_REWARDS_EXPORT, Prism.EXPORT_MATLAB); + a_exportBuildAs(ModelExportEntity.STATE_REWARDS, ModelExportFormat.MATLAB); } }; exportStateRewardsMatlab.putValue(Action.LONG_DESCRIPTION, "Exports the state rewards vector to a Matlab file"); @@ -741,7 +731,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.TRANS_REWARDS_EXPORT, Prism.EXPORT_PLAIN); + a_exportBuildAs(ModelExportEntity.STATE_REWARDS, ModelExportFormat.EXPLICIT); } }; exportTransRewardsPlain.putValue(Action.LONG_DESCRIPTION, "Exports the transition rewards matrix to a plain text file"); @@ -753,7 +743,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.TRANS_REWARDS_EXPORT, Prism.EXPORT_MATLAB); + a_exportBuildAs(ModelExportEntity.TRANSITION_REWARDS, ModelExportFormat.MATLAB); } }; exportTransRewardsMatlab.putValue(Action.LONG_DESCRIPTION, "Exports the transition rewards matrix to a Matlab file"); @@ -765,7 +755,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.LABELS_EXPORT, Prism.EXPORT_PLAIN); + a_exportBuildAs(ModelExportEntity.LABELS, ModelExportFormat.EXPLICIT); } }; exportLabelsPlain.putValue(Action.LONG_DESCRIPTION, "Exports the model's labels and their satisfying states to a plain text file"); @@ -777,7 +767,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportBuildAs(GUIMultiModelHandler.LABELS_EXPORT, Prism.EXPORT_MATLAB); + a_exportBuildAs(ModelExportEntity.LABELS, ModelExportFormat.MATLAB); } }; exportLabelsMatlab.putValue(Action.LONG_DESCRIPTION, "Exports the model's labels and their satisfying states to a Matlab file"); @@ -817,7 +807,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportSteadyState(Prism.EXPORT_PLAIN); + a_exportSteadyState(ModelExportFormat.EXPLICIT); } }; exportSSPlain.putValue(Action.LONG_DESCRIPTION, "Exports the steady-state probabilities to a plain text file"); @@ -829,7 +819,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportSteadyState(Prism.EXPORT_MATLAB); + a_exportSteadyState(ModelExportFormat.MATLAB); } }; exportSSMatlab.putValue(Action.LONG_DESCRIPTION, "Exports the steady-state probabilities to a Matlab file"); @@ -841,7 +831,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportTransient(Prism.EXPORT_PLAIN); + a_exportTransient(ModelExportFormat.EXPLICIT); } }; exportTrPlain.putValue(Action.LONG_DESCRIPTION, "Exports the transient probabilities to a plain text file"); @@ -853,7 +843,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportTransient(Prism.EXPORT_MATLAB); + a_exportTransient(ModelExportFormat.MATLAB); } }; exportTrMatlab.putValue(Action.LONG_DESCRIPTION, "Exports the transient probabilities to a Matlab file"); @@ -865,7 +855,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_viewBuild(GUIMultiModelHandler.STATES_EXPORT, Prism.EXPORT_PLAIN); + a_viewBuild(ModelExportEntity.STATES, ModelExportFormat.EXPLICIT); } }; viewStates.putValue(Action.LONG_DESCRIPTION, "Print the reachable states to the log"); @@ -877,7 +867,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_viewBuild(GUIMultiModelHandler.TRANS_EXPORT, Prism.EXPORT_PLAIN); + a_viewBuild(ModelExportEntity.MODEL, ModelExportFormat.EXPLICIT); } }; viewTrans.putValue(Action.LONG_DESCRIPTION, "Print the transition matrix to the log"); @@ -889,7 +879,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_viewBuild(GUIMultiModelHandler.OBSERVATIONS_EXPORT, Prism.EXPORT_PLAIN); + a_viewBuild(ModelExportEntity.OBSERVATIONS, ModelExportFormat.EXPLICIT); } }; viewObs.putValue(Action.LONG_DESCRIPTION, "Print the observations to the log"); @@ -901,7 +891,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_viewBuild(GUIMultiModelHandler.STATE_REWARDS_EXPORT, Prism.EXPORT_PLAIN); + a_viewBuild(ModelExportEntity.STATE_REWARDS, ModelExportFormat.EXPLICIT); } }; viewStateRewards.putValue(Action.LONG_DESCRIPTION, "Print the state rewards to the log"); @@ -913,7 +903,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_viewBuild(GUIMultiModelHandler.TRANS_REWARDS_EXPORT, Prism.EXPORT_PLAIN); + a_viewBuild(ModelExportEntity.TRANSITION_REWARDS, ModelExportFormat.EXPLICIT); } }; viewTransRewards.putValue(Action.LONG_DESCRIPTION, "Print the transition rewards to the log"); @@ -925,7 +915,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_viewBuild(GUIMultiModelHandler.LABELS_EXPORT, Prism.EXPORT_PLAIN); + a_viewBuild(ModelExportEntity.LABELS, ModelExportFormat.EXPLICIT); } }; viewLabels.putValue(Action.LONG_DESCRIPTION, "Print the labels and satisfying states to the log"); @@ -1053,7 +1043,6 @@ private JMenu initExportMenu() exportTransMenu.add(exportTransPlain); exportTransMenu.add(exportTransMatlab); exportTransMenu.add(exportTransDot); - exportTransMenu.add(exportTransDotStates); exportMenu.add(exportTransMenu); exportObsMenu = new JMenu("Observations"); exportObsMenu.setMnemonic('O'); diff --git a/prism/src/userinterface/model/GUIMultiModelHandler.java b/prism/src/userinterface/model/GUIMultiModelHandler.java index 961a8c56f5..12b54c0a40 100644 --- a/prism/src/userinterface/model/GUIMultiModelHandler.java +++ b/prism/src/userinterface/model/GUIMultiModelHandler.java @@ -44,9 +44,9 @@ import javax.swing.border.EmptyBorder; import javax.swing.border.TitledBorder; +import io.ModelExportTask; import parser.Values; import parser.ast.ModulesFile; -import symbolic.model.Model; import prism.ModelType; import prism.Prism; import prism.PrismException; @@ -73,14 +73,6 @@ public class GUIMultiModelHandler extends JPanel implements PrismModelListener public static final int PRISM_MODE = 1; public static final int PEPA_MODE = 2; - // export entity types - public static final int TRANS_EXPORT = 1; - public static final int STATE_REWARDS_EXPORT = 2; - public static final int TRANS_REWARDS_EXPORT = 3; - public static final int STATES_EXPORT = 4; - public static final int LABELS_EXPORT = 5; - public static final int OBSERVATIONS_EXPORT = 6; - private GUIMultiModel theModel; private GUIMultiModelTree tree; private GUIModelEditor editor; @@ -128,9 +120,7 @@ public class GUIMultiModelHandler extends JPanel implements PrismModelListener private boolean exportAfterReceiveParseNotification = false; private boolean computeSSAfterReceiveParseNotification = false; private boolean computeTransientAfterReceiveParseNotification = false; - private int exportEntity = 0; - private int exportType = Prism.EXPORT_PLAIN; - private File exportFile = null; + private ModelExportTask exportTask; private double transientTime; // GUI @@ -712,13 +702,11 @@ public void run() // Export model... - public void export(int entity, int type, File f) + public void export(ModelExportTask exportTask) { // set flags/store info exportAfterReceiveParseNotification = true; - exportEntity = entity; - exportType = type; - exportFile = f; + this.exportTask = exportTask; // do a parse if necessary requestParse(false); } @@ -745,21 +733,19 @@ private void exportAfterParse() return; } // if export is being done to log, switch view to log - if (exportFile == null) + if (exportTask.getFile() == null) { theModel.logToFront(); - new ExportBuiltModelThread(this, exportEntity, exportType, exportFile) - .setExportModelLabels(true) - .start(); + } + new ExportBuiltModelThread(this, exportTask).start(); } // Compute steady-state... - public void computeSteadyState(int type, File f) + public void computeSteadyState(ModelExportTask exportTask) { // set flags/store info computeSSAfterReceiveParseNotification = true; - exportType = type; - exportFile = f; + this.exportTask = exportTask; // do a parse if necessary requestParse(false); } @@ -774,8 +760,9 @@ private void computeSteadyStateAfterParse() unC = new UndefinedConstants(parsedModel, null); if (unC.getMFNumUndefined() > 0) { int result = GUIConstantsPicker.defineConstantsWithDialog(theModel.getGUI(), unC, lastMFConstants, null); - if (result != GUIConstantsPicker.VALUES_DONE) + if (result != GUIConstantsPicker.VALUES_DONE) { return; + } lastMFConstants = unC.getMFConstantValues(); } try { @@ -786,19 +773,19 @@ private void computeSteadyStateAfterParse() return; } // if the results are going to the log, switch view to there - if (exportFile == null) + if (exportTask.getFile() == null) { theModel.logToFront(); - new ComputeSteadyStateThread(this, exportType, exportFile).start(); + } + new ComputeSteadyStateThread(this, exportTask).start(); } // Compute transient probabilities... - public void computeTransient(double time, int type, File f) + public void computeTransient(double time, ModelExportTask exportTask) { computeTransientAfterReceiveParseNotification = true; transientTime = time; - exportType = type; - exportFile = f; + this.exportTask = exportTask; // do a parse if necessary requestParse(false); } @@ -825,9 +812,10 @@ private void computeTransientAfterParse() return; } // if the results are going to the log, switch view to there - if (exportFile == null) + if (exportTask.getFile() == null) { theModel.logToFront(); - new ComputeTransientThread(this, transientTime, exportType, exportFile).start(); + } + new ComputeTransientThread(this, transientTime, exportTask).start(); } public void requestViewModel() diff --git a/prism/src/userinterface/model/computation/ComputeSteadyStateThread.java b/prism/src/userinterface/model/computation/ComputeSteadyStateThread.java index 793f30b7be..7cad80c9e2 100644 --- a/prism/src/userinterface/model/computation/ComputeSteadyStateThread.java +++ b/prism/src/userinterface/model/computation/ComputeSteadyStateThread.java @@ -27,9 +27,9 @@ package userinterface.model.computation; -import java.io.File; - import javax.swing.*; + +import io.ModelExportTask; import userinterface.*; import userinterface.model.*; import prism.*; @@ -42,22 +42,14 @@ public class ComputeSteadyStateThread extends GUIComputationThread { @SuppressWarnings("unused") private GUIMultiModelHandler handler; - private int exportType; - private File exportFile; - - /** Creates a new instance of ComputeSteadyStateThread */ - public ComputeSteadyStateThread(GUIMultiModelHandler handler) - { - this(handler, Prism.EXPORT_PLAIN, null); - } + private ModelExportTask exportTask; /** Creates a new instance of ComputeSteadyStateThread */ - public ComputeSteadyStateThread(GUIMultiModelHandler handler, int type, File f) + public ComputeSteadyStateThread(GUIMultiModelHandler handler, ModelExportTask exportTask) { super(handler.getGUIPlugin()); this.handler = handler; - this.exportType = type; - this.exportFile = f; + this.exportTask = exportTask; } public void run() @@ -75,7 +67,7 @@ public void run() // Do Computation try { - prism.doSteadyState(exportType, exportFile, null); + prism.doSteadyState(Prism.convertExportType(exportTask.getExportOptions()), exportTask.getFile(), null); } catch (Throwable e) { error(e); SwingUtilities.invokeLater(new Runnable() diff --git a/prism/src/userinterface/model/computation/ComputeTransientThread.java b/prism/src/userinterface/model/computation/ComputeTransientThread.java index df67fa7afd..26e0ba02c5 100644 --- a/prism/src/userinterface/model/computation/ComputeTransientThread.java +++ b/prism/src/userinterface/model/computation/ComputeTransientThread.java @@ -27,9 +27,9 @@ package userinterface.model.computation; -import java.io.File; - import javax.swing.*; + +import io.ModelExportTask; import userinterface.*; import userinterface.model.*; import prism.*; @@ -43,23 +43,15 @@ public class ComputeTransientThread extends GUIComputationThread @SuppressWarnings("unused") private GUIMultiModelHandler handler; private double transientTime; - private int exportType; - private File exportFile; - - /** Creates a new instance of ComputeTransientThread */ - public ComputeTransientThread(GUIMultiModelHandler handler, double transientTime) - { - this(handler, transientTime, Prism.EXPORT_PLAIN, null); - } + private ModelExportTask exportTask; /** Creates a new instance of ComputeTransientThread */ - public ComputeTransientThread(GUIMultiModelHandler handler, double transientTime, int type, File f) + public ComputeTransientThread(GUIMultiModelHandler handler, double transientTime, ModelExportTask exportTask) { super(handler.getGUIPlugin()); this.handler = handler; this.transientTime = transientTime; - this.exportType = type; - this.exportFile = f; + this.exportTask = exportTask; } public void run() @@ -77,7 +69,7 @@ public void run() // Do Computation try { - prism.doTransient(transientTime, exportType, exportFile, null); + prism.doTransient(transientTime, Prism.convertExportType(exportTask.getExportOptions()), exportTask.getFile(), null); } catch (Throwable e) { error(e); SwingUtilities.invokeLater(new Runnable() diff --git a/prism/src/userinterface/model/computation/ExportBuiltModelThread.java b/prism/src/userinterface/model/computation/ExportBuiltModelThread.java index d4d7bd7bf6..824d06aa13 100644 --- a/prism/src/userinterface/model/computation/ExportBuiltModelThread.java +++ b/prism/src/userinterface/model/computation/ExportBuiltModelThread.java @@ -30,8 +30,7 @@ import java.io.*; import javax.swing.*; -import parser.ast.PropertiesFile; -import prism.*; +import io.ModelExportTask; import userinterface.*; import userinterface.model.*; import userinterface.util.*; @@ -41,38 +40,19 @@ */ public class ExportBuiltModelThread extends GUIComputationThread { - private int exportEntity; - private boolean exportModelLabels; - private int exportType; - private File exportFile; - private PropertiesFile propertiesFile; + private ModelExportTask exportTask; /** Creates a new instance of ExportBuiltModelThread */ - public ExportBuiltModelThread(GUIMultiModelHandler handler, int entity, int type, File f) + public ExportBuiltModelThread(GUIMultiModelHandler handler, ModelExportTask exportTask) { - this(handler.getGUIPlugin(), entity, type, f); + this(handler.getGUIPlugin(), exportTask); } /** Creates a new instance of ExportBuiltModelThread */ - public ExportBuiltModelThread(GUIPlugin plug, int entity, int type, File f) + public ExportBuiltModelThread(GUIPlugin plug, ModelExportTask exportTask) { super(plug); - this.exportEntity = entity; - this.exportType = type; - this.exportFile = f; - } - - /** Set (optional) associated PropertiesFile (for label export) */ - public ExportBuiltModelThread setPropertiesFile(PropertiesFile propertiesFile) - { - this.propertiesFile = propertiesFile; - return this; - } - - public ExportBuiltModelThread setExportModelLabels(boolean exportModelLabels) - { - this.exportModelLabels = exportModelLabels; - return this; + this.exportTask = exportTask; } public void run() @@ -91,30 +71,7 @@ public void run() // Do export try { - switch (exportEntity) { - case GUIMultiModelHandler.STATES_EXPORT: - prism.exportStatesToFile(exportType, exportFile); - break; - case GUIMultiModelHandler.TRANS_EXPORT: - prism.exportTransToFile(true, exportType, exportFile); - break; - case GUIMultiModelHandler.OBSERVATIONS_EXPORT: - prism.exportObservationsToFile(exportType, exportFile); - break; - case GUIMultiModelHandler.STATE_REWARDS_EXPORT: - prism.exportStateRewardsToFile(exportType, exportFile); - break; - case GUIMultiModelHandler.TRANS_REWARDS_EXPORT: - prism.exportTransRewardsToFile(true, exportType, exportFile); - break; - case GUIMultiModelHandler.LABELS_EXPORT: - if (exportModelLabels) { - prism.exportLabelsToFile(propertiesFile, exportType, exportFile); - } else { - prism.exportPropLabelsToFile(propertiesFile, exportType, exportFile); - } - break; - } + prism.exportBuiltModelTask(exportTask); } catch (FileNotFoundException e) { SwingUtilities.invokeAndWait(new Runnable() { @@ -123,7 +80,7 @@ public void run() plug.stopProgress(); plug.setTaskBarText("Exporting... error."); plug.notifyEventListeners(new GUIComputationEvent(GUIComputationEvent.COMPUTATION_ERROR, plug)); - error("Could not export to file \"" + exportFile + "\""); + error("Couldn't open file \"" + exportTask.getFile().getName() + "\" for output"); } }); return; diff --git a/prism/src/userinterface/properties/GUIMultiProperties.java b/prism/src/userinterface/properties/GUIMultiProperties.java index 8f0da0a817..7ca16017ee 100644 --- a/prism/src/userinterface/properties/GUIMultiProperties.java +++ b/prism/src/userinterface/properties/GUIMultiProperties.java @@ -83,6 +83,9 @@ import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; +import io.ModelExportFormat; +import io.ModelExportOptions; +import io.ModelExportTask; import org.jfree.data.xy.XYDataItem; import parser.Values; @@ -94,7 +97,6 @@ import parser.type.TypeDouble; import parser.type.TypeInt; import parser.type.TypeInterval; -import prism.Prism; import prism.PrismException; import prism.PrismSettings; import prism.PrismSettingsListener; @@ -112,7 +114,6 @@ import userinterface.graph.Graph; import userinterface.graph.Graph.SeriesKey; import userinterface.model.GUIModelEvent; -import userinterface.model.GUIMultiModelHandler; import userinterface.model.computation.ExportBuiltModelThread; import userinterface.properties.computation.ExportResultsThread; import userinterface.properties.computation.ExportStrategyThread; @@ -152,9 +153,7 @@ public class GUIMultiProperties extends GUIPlugin implements MouseListener, List private File activeFile; private Values pfConstants; private String argsPropertiesFile; - private int exportType = Prism.EXPORT_PLAIN; - private File exportFile = null; - private boolean exportModelLabels = false; + private ModelExportTask exportTask; // GUI private FileFilter propsFilter; @@ -1120,30 +1119,31 @@ public void a_removeSelectedLabels() } } - public void a_exportLabels(int exportType, boolean exportModelLabels) + public void a_exportLabels(ModelExportTask.LabelExportSet exportLabelSet, ModelExportFormat exportFormat) { int res = JFileChooser.CANCEL_OPTION; // pop up dialog to select file - switch (exportType) { - case Prism.EXPORT_MATLAB: - res = showSaveFileDialog(matlabFilter); - break; - default: - res = showSaveFileDialog(labFilters.values(), labFilters.get("lab")); - break; + switch (exportFormat) { + case MATLAB: + res = showSaveFileDialog(matlabFilter); + break; + case EXPLICIT: + default: + res = showSaveFileDialog(labFilters.values(), labFilters.get("lab")); + break; } - if (res != JFileChooser.APPROVE_OPTION) + if (res != JFileChooser.APPROVE_OPTION) { return; + } consTable.correctEditors(); labTable.correctEditors(); // Reset warnings counter getPrism().getMainLog().resetNumberOfWarnings(); // Set flag, store export info exportLabelsAfterReceiveParseNotification = true; - this.exportType = exportType; - this.exportFile = getChooserFile(); - this.exportModelLabels = exportModelLabels; + exportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, getChooserFile(), new ModelExportOptions(exportFormat)); + exportTask.setLabelExportSet(exportLabelSet); // Request a parse exportLabelsAfterReceiveParseNotification = true; notifyEventListeners(new GUIPropertiesEvent(GUIPropertiesEvent.REQUEST_MODEL_PARSE)); @@ -1171,16 +1171,18 @@ public void exportLabelsAfterParse() } // Store model/property constants pfConstants = uCon.getPFConstantValues(); + // currently, evaluate constants non-exact for model building getPrism().setPRISMModelConstants(uCon.getMFConstantValues(), exact); - parsedProperties.setSomeUndefinedConstants(pfConstants, exact); + if (exportTask.extraLabelsUsed()) { + parsedProperties.setSomeUndefinedConstants(pfConstants, exact); + exportTask.setExtraLabelsSource(parsedProperties); + } // If export is being done to log, switch view to log - if (exportFile == null) + if (exportTask.getFile() == null) { logToFront(); - // Start export - new ExportBuiltModelThread(this, GUIMultiModelHandler.LABELS_EXPORT, exportType, exportFile) - .setPropertiesFile(parsedProperties) - .setExportModelLabels(exportModelLabels) - .start(); + } + // Start export + new ExportBuiltModelThread(this, exportTask).start(); } catch (PrismException e) { error(e.getMessage()); return; @@ -2499,7 +2501,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportLabels(Prism.EXPORT_PLAIN, false); + a_exportLabels(ModelExportTask.LabelExportSet.EXTRA, ModelExportFormat.EXPLICIT); } }; exportLabelsPlain.putValue(Action.LONG_DESCRIPTION, "Exports the property file's labels and their satisfying states to a plain text file"); @@ -2511,7 +2513,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportLabels(Prism.EXPORT_PLAIN, true); + a_exportLabels(ModelExportTask.LabelExportSet.ALL, ModelExportFormat.EXPLICIT); } }; exportModelLabelsPlain.putValue(Action.LONG_DESCRIPTION, "Exports the model and property file's labels and their satisfying states to a plain text file"); @@ -2523,7 +2525,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportLabels(Prism.EXPORT_MATLAB, false); + a_exportLabels(ModelExportTask.LabelExportSet.EXTRA, ModelExportFormat.MATLAB); } }; exportLabelsMatlab.putValue(Action.LONG_DESCRIPTION, "Exports the property file's labels and their satisfying states to a Matlab file"); @@ -2535,7 +2537,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { - a_exportLabels(Prism.EXPORT_MATLAB, true); + a_exportLabels(ModelExportTask.LabelExportSet.ALL, ModelExportFormat.MATLAB); } }; exportModelLabelsMatlab.putValue(Action.LONG_DESCRIPTION, "Exports the model and property file's labels and their satisfying states to a Matlab file"); From 3b575760405912a03f226895833c6d5dc8877c21 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sun, 5 Jan 2025 12:08:24 +0000 Subject: [PATCH 10/44] Update Prism.exportBuiltModelXXX methods. * Add some some alternatives that just take the format * Don't throw FileNotFoundException * Tidy up order of methods in Prism --- prism/src/prism/Prism.java | 327 +++++++++++------- prism/src/prism/PrismCL.java | 4 +- prism/src/prism/TestModelGenerator.java | 3 - .../computation/ExportBuiltModelThread.java | 12 - 4 files changed, 197 insertions(+), 149 deletions(-) diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index 5548e845d8..be0468c4d3 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -2465,50 +2465,13 @@ public void exportPRISMModelWithExpandedConstants(File file) throws FileNotFound tmpLog.close(); } - /** - * Perform an export task for the current model, building it first if needed. - * @param exportTask Export task - */ - public void exportBuiltModelTask(ModelExportTask exportTask) throws PrismException, FileNotFoundException - { - // Skip non-applicable tasks - if (!exportTask.isApplicable(getModelInfo())) { - return; - } - // Build model, if necessary - buildModelIfRequired(); - // Merge export options with PRISM settings and do export - mainLog.println("\n" + exportTask.getMessage()); - ModelExportOptions exportOptions = newMergedModelExportOptions(exportTask.getExportOptions()); - switch (exportTask.getEntity()) { - case MODEL: - doExportBuiltModel(new ModelExportTask(exportTask, exportOptions)); - break; - case STATE_REWARDS: - doExportBuiltModelStateRewards(exportTask.getFile(), exportOptions); - break; - case TRANSITION_REWARDS: - doExportBuiltModelTransRewards(exportTask.getFile(), exportOptions); - break; - case STATES: - doExportBuiltModelStates(exportTask.getFile(), exportOptions); - break; - case OBSERVATIONS: - doExportBuiltModelObservations(exportTask.getFile(), exportOptions); - break; - case LABELS: - doExportBuiltModelLabels(new ModelExportTask(exportTask, exportOptions)); - break; - } - } - /** * Export the current model, building it first if needed. * To configure which model parts are exported, use {@link #exportBuiltModelTask(ModelExportTask)}. * @param file File to export to (if null, print to the log instead) * @param exportFormat The format to use for export */ - public void exportBuiltModel(File file, ModelExportFormat exportFormat) throws PrismException, FileNotFoundException + public void exportBuiltModel(File file, ModelExportFormat exportFormat) throws PrismException { exportBuiltModelTask(ModelExportTask.fromFormat(file, exportFormat)); } @@ -2520,22 +2483,45 @@ public void exportBuiltModel(File file, ModelExportFormat exportFormat) throws P * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - public void exportBuiltModel(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException + public void exportBuiltModel(File file, ModelExportOptions exportOptions) throws PrismException { exportBuiltModelTask(ModelExportTask.fromOptions(file, exportOptions)); } + /** + * Export the transition matrix/function for the current model, building it first if needed. + * @param file File to export to (if null, print to the log instead) + * @param exportFormat The format to use for export + */ + public void exportBuiltModelTransitions(File file, ModelExportFormat exportFormat) throws PrismException + { + // This is equivalent to exportBuiltModel + exportBuiltModel(file, exportFormat); + } + /** * Export the transition matrix/function for the current model, building it first if needed. * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - public void exportBuiltModelTransitions(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException + public void exportBuiltModelTransitions(File file, ModelExportOptions exportOptions) throws PrismException { // This is equivalent to exportBuiltModel exportBuiltModel(file, exportOptions); } + /** + * Export the state rewards for the current model, building it first if needed. + * If there is more than 1 reward structure, then multiple files are generated + * (e.g. "rew.sta" becomes "rew1.sta", "rew2.sta", ...) + * @param file File to export to (if null, print to the log instead) + * @param exportFormat The format to use for export + */ + public void exportBuiltModelStateRewards(File file, ModelExportFormat exportFormat) throws PrismException + { + exportBuiltModelStateRewards(file, new ModelExportOptions(exportFormat)); + } + /** * Export the state rewards for the current model, building it first if needed. * If there is more than 1 reward structure, then multiple files are generated @@ -2543,7 +2529,7 @@ public void exportBuiltModelTransitions(File file, ModelExportOptions exportOpti * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException + public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOptions) throws PrismException { if (getRewardInfo().getNumRewardStructs() == 0) { mainLog.println("\nOmitting state reward export as there are no reward structures"); @@ -2552,6 +2538,169 @@ public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOpt exportBuiltModelTask(new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, file, exportOptions)); } + /** + * Export the transition rewards for the current model, building it first if needed. + * If there is more than 1 reward structure, then multiple files are generated + * (e.g. "rew.sta" becomes "rew1.sta", "rew2.sta", ...) + * @param file File to export to (if null, print to the log instead) + * @param exportFormat The format to use for export + */ + public void exportBuiltModelTransRewards(File file, ModelExportFormat exportFormat) throws FileNotFoundException, PrismException + { + exportBuiltModelTransRewards(file, new ModelExportOptions(exportFormat)); + } + + /** + * Export the transition rewards for the current model, building it first if needed. + * If there is more than 1 reward structure, then multiple files are generated + * (e.g. "rew.sta" becomes "rew1.sta", "rew2.sta", ...) + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportBuiltModelTransRewards(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + { + if (getRewardInfo().getNumRewardStructs() == 0) { + mainLog.println("\nOmitting transition reward export as there are no reward structures"); + return; + } + exportBuiltModelTask(new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, file, exportOptions)); + } + + /** + * Export the states of the currently loaded model, building it first if needed. + * @param file File to export to (if null, print to the log instead) + * @param exportFormat The format to use for export + */ + public void exportBuiltModelStates(File file, ModelExportFormat exportFormat) throws FileNotFoundException, PrismException + { + exportBuiltModelStates(file, new ModelExportOptions(exportFormat)); + } + + /** + * Export the states of the currently loaded model, building it first if needed. + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportBuiltModelStates(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + { + exportBuiltModelTask(new ModelExportTask(ModelExportTask.ModelExportEntity.STATES, file, exportOptions)); + } + + /** + * Export the observations of the currently loaded (partially observable) loaded model, building it first if needed. + * @param file File to export to (if null, print to the log instead) + * @param exportFormat The format to use for export + */ + public void exportBuiltModelObservations(File file, ModelExportFormat exportFormat) throws FileNotFoundException, PrismException + { + exportBuiltModelObservations(file, new ModelExportOptions(exportFormat)); + } + + /** + * Export the observations of the currently loaded (partially observable) loaded model, building it first if needed. + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportBuiltModelObservations(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + { + if (!getModelType().partiallyObservable()) { + mainLog.println("\nOmitting observations export as the model is not partially observable"); + return; + } + exportBuiltModelTask(new ModelExportTask(ModelExportTask.ModelExportEntity.OBSERVATIONS, file, exportOptions)); + } + + /** + * Export the labels and satisfying states of the currently loaded model, building it first if needed. + * @param file File to export to (if null, print to the log instead) + * @param exportFormat The format to use for export + */ + public void exportBuiltModelLabels(File file, ModelExportFormat exportFormat) throws FileNotFoundException, PrismException + { + exportBuiltModelLabels(file, new ModelExportOptions(exportFormat)); + } + + /** + * Export the labels and satisfying states of the currently loaded model, building it first if needed. + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportBuiltModelLabels(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + { + exportBuiltModelLabels(null, file, exportOptions); + } + + /** + * Export model, and optionally property file, labels and the satisfying states + * of the currently loaded model, building it first if needed. + * The PropertiesFile (if non-null) should correspond to the currently loaded model. + * @param propertiesFile The properties file, for further labels (ignored if null) + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportBuiltModelLabels(PropertiesFile propertiesFile, File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + { + ModelExportTask exportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file, exportOptions); + if (propertiesFile != null) { + exportTask.setLabelExportSet(ModelExportTask.LabelExportSet.ALL); + exportTask.setExtraLabelsSource(propertiesFile); + } + exportBuiltModelTask(exportTask); + } + + /** + * Export labels from a properties file and the satisfying states + * of the currently loaded model, building it first if needed. + * The PropertiesFile should correspond to the currently loaded model. + * @param propertiesFile The properties file (for further labels) + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + */ + public void exportBuiltModelPropLabels(PropertiesFile propertiesFile, File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + { + ModelExportTask exportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file, exportOptions); + exportTask.setLabelExportSet(ModelExportTask.LabelExportSet.EXTRA); + exportTask.setExtraLabelsSource(propertiesFile); + exportBuiltModelTask(exportTask); + } + + /** + * Perform an export task for the current model, building it first if needed. + * @param exportTask Export task + */ + public void exportBuiltModelTask(ModelExportTask exportTask) throws PrismException + { + // Skip non-applicable tasks + if (!exportTask.isApplicable(getModelInfo())) { + return; + } + // Build model, if necessary + buildModelIfRequired(); + // Merge export options with PRISM settings and do export + mainLog.println("\n" + exportTask.getMessage()); + ModelExportOptions exportOptions = newMergedModelExportOptions(exportTask.getExportOptions()); + switch (exportTask.getEntity()) { + case MODEL: + doExportBuiltModel(new ModelExportTask(exportTask, exportOptions)); + break; + case STATE_REWARDS: + doExportBuiltModelStateRewards(exportTask.getFile(), exportOptions); + break; + case TRANSITION_REWARDS: + doExportBuiltModelTransRewards(exportTask.getFile(), exportOptions); + break; + case STATES: + doExportBuiltModelStates(exportTask.getFile(), exportOptions); + break; + case OBSERVATIONS: + doExportBuiltModelObservations(exportTask.getFile(), exportOptions); + break; + case LABELS: + doExportBuiltModelLabels(new ModelExportTask(exportTask, exportOptions)); + break; + } + } + /** * Export the transition matrix/function for the current built model. * This assumes that the model has already been built. @@ -2559,7 +2708,7 @@ public void exportBuiltModelStateRewards(File file, ModelExportOptions exportOpt * as specified by the {@code exportTask} object. * @param exportTask Export task (destination, which parts of the model to export, options) */ - private void doExportBuiltModel(ModelExportTask exportTask) throws PrismException, FileNotFoundException + private void doExportBuiltModel(ModelExportTask exportTask) throws PrismException { // Export via either symbolic/explicit model checker if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { @@ -2579,7 +2728,7 @@ private void doExportBuiltModel(ModelExportTask exportTask) throws PrismExceptio * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - private void doExportBuiltModelStateRewards(File file, ModelExportOptions exportOptions) throws PrismException, FileNotFoundException + private void doExportBuiltModelStateRewards(File file, ModelExportOptions exportOptions) throws PrismException { // Export to multiple files if necessary List files = new ArrayList<>(); @@ -2605,22 +2754,6 @@ private void doExportBuiltModelStateRewards(File file, ModelExportOptions export } } - /** - * Export the transition rewards for the current model, building it first if needed. - * If there is more than 1 reward structure, then multiple files are generated - * (e.g. "rew.sta" becomes "rew1.sta", "rew2.sta", ...) - * @param file File to export to (if null, print to the log instead) - * @param exportOptions The options for export - */ - public void exportBuiltModelTransRewards(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException - { - if (getRewardInfo().getNumRewardStructs() == 0) { - mainLog.println("\nOmitting transition reward export as there are no reward structures"); - return; - } - exportBuiltModelTask(new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, file, exportOptions)); - } - /** * Export the transition rewards for the current built model. * This assumes that the model has already been built. @@ -2629,7 +2762,7 @@ public void exportBuiltModelTransRewards(File file, ModelExportOptions exportOpt * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - private void doExportBuiltModelTransRewards(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + private void doExportBuiltModelTransRewards(File file, ModelExportOptions exportOptions) throws PrismException { // Export to multiple files if necessary List files = new ArrayList<>(); @@ -2655,23 +2788,13 @@ private void doExportBuiltModelTransRewards(File file, ModelExportOptions export } } - /** - * Export the states of the currently loaded model, building it first if needed. - * @param file File to export to (if null, print to the log instead) - * @param exportOptions The options for export - */ - public void exportBuiltModelStates(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException - { - exportBuiltModelTask(new ModelExportTask(ModelExportTask.ModelExportEntity.STATES, file, exportOptions)); - } - /** * Export the states of the currently built model. * This assumes that the model has already been built. * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - private void doExportBuiltModelStates(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + private void doExportBuiltModelStates(File file, ModelExportOptions exportOptions) throws PrismException { // Export via either symbolic/explicit model checker if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { @@ -2683,82 +2806,24 @@ private void doExportBuiltModelStates(File file, ModelExportOptions exportOption } } - /** - * Export the observations of the currently loaded (partially observable) loaded model, building it first if needed. - * @param file File to export to (if null, print to the log instead) - * @param exportOptions The options for export - */ - public void exportBuiltModelObservations(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException - { - if (!getModelType().partiallyObservable()) { - mainLog.println("\nOmitting observations export as the model is not partially observable"); - return; - } - exportBuiltModelTask(new ModelExportTask(ModelExportTask.ModelExportEntity.OBSERVATIONS, file, exportOptions)); - } - /** * Export the observations of the current built (partially observable) model. * This assumes that the model has already been built. * @param file File to export to (if null, print to the log instead) * @param exportOptions The options for export */ - private void doExportBuiltModelObservations(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException + private void doExportBuiltModelObservations(File file, ModelExportOptions exportOptions) throws PrismException { // Export (explicit engine only) explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); mcExpl.exportObservations(getBuiltModelExplicit(), file, exportOptions); } - /** - * Export the labels and satisfying states of the currently loaded model, building it first if needed. - * @param file File to export to (if null, print to the log instead) - * @param exportOptions The options for export - */ - public void exportBuiltModelLabels(File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException - { - exportBuiltModelLabels(null, file, exportOptions); - } - - /** - * Export model, and optionally property file, labels and the satisfying states - * of the currently loaded model, building it first if needed. - * The PropertiesFile (if non-null) should correspond to the currently loaded model. - * @param propertiesFile The properties file, for further labels (ignored if null) - * @param file File to export to (if null, print to the log instead) - * @param exportOptions The options for export - */ - public void exportBuiltModelLabels(PropertiesFile propertiesFile, File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException - { - ModelExportTask exportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file, exportOptions); - if (propertiesFile != null) { - exportTask.setLabelExportSet(ModelExportTask.LabelExportSet.ALL); - exportTask.setExtraLabelsSource(propertiesFile); - } - exportBuiltModelTask(exportTask); - } - - /** - * Export labels from a properties file and the satisfying states - * of the currently loaded model, building it first if needed. - * The PropertiesFile should correspond to the currently loaded model. - * @param propertiesFile The properties file (for further labels) - * @param file File to export to (if null, print to the log instead) - * @param exportOptions The options for export - */ - public void exportBuiltModelPropLabels(PropertiesFile propertiesFile, File file, ModelExportOptions exportOptions) throws FileNotFoundException, PrismException - { - ModelExportTask exportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file, exportOptions); - exportTask.setLabelExportSet(ModelExportTask.LabelExportSet.EXTRA); - exportTask.setExtraLabelsSource(propertiesFile); - exportBuiltModelTask(exportTask); - } - /** * Export the states satisfying a set of labels, as specified in a ModelExportTask. * @param exportTask Export task (destination, which labels to export, options) */ - private void doExportBuiltModelLabels(ModelExportTask exportTask) throws FileNotFoundException, PrismException + private void doExportBuiltModelLabels(ModelExportTask exportTask) throws PrismException { // Collect names of labels to export from model and/or properties file List labelNames = new ArrayList<>(); diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index e9da3d1a00..90539ff80c 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -807,9 +807,7 @@ private void doExports() prism.exportBuiltModelTask(exportTask); } // In case of error, report it and proceed - catch (FileNotFoundException e) { - error("Couldn't open file \"" + exportTask.getFile().getName() + "\" for output"); - } catch (PrismException e) { + catch (PrismException e) { error(e); } } diff --git a/prism/src/prism/TestModelGenerator.java b/prism/src/prism/TestModelGenerator.java index 9a4d1aa26c..defc55a7da 100644 --- a/prism/src/prism/TestModelGenerator.java +++ b/prism/src/prism/TestModelGenerator.java @@ -182,9 +182,6 @@ public static void main(String args[]) prism.closeDown(true); } catch (PrismException e) { System.err.println("Error: " + e.getMessage()); - } catch (FileNotFoundException e) { - System.err.println("Error: " + e.getMessage()); } - } } diff --git a/prism/src/userinterface/model/computation/ExportBuiltModelThread.java b/prism/src/userinterface/model/computation/ExportBuiltModelThread.java index 824d06aa13..1715af8525 100644 --- a/prism/src/userinterface/model/computation/ExportBuiltModelThread.java +++ b/prism/src/userinterface/model/computation/ExportBuiltModelThread.java @@ -72,18 +72,6 @@ public void run() // Do export try { prism.exportBuiltModelTask(exportTask); - } catch (FileNotFoundException e) { - SwingUtilities.invokeAndWait(new Runnable() - { - public void run() - { - plug.stopProgress(); - plug.setTaskBarText("Exporting... error."); - plug.notifyEventListeners(new GUIComputationEvent(GUIComputationEvent.COMPUTATION_ERROR, plug)); - error("Couldn't open file \"" + exportTask.getFile().getName() + "\" for output"); - } - }); - return; } catch (Throwable e2) { error(e2); SwingUtilities.invokeAndWait(new Runnable() From b751e46b88d1472c07d7734b6bb6136a98ef031f Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sun, 5 Jan 2025 15:15:11 +0000 Subject: [PATCH 11/44] Refactor steady-state probability export methods in Prism. Use new ModelExportFormat/ModelExportOptions to specify format. The probabilities can also be accessed via computeSteadyStateProbabilities. --- prism/src/prism/Prism.java | 122 ++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 49 deletions(-) diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index be0468c4d3..5f2a0317d2 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -3668,74 +3668,74 @@ public void generateSimulationPath(String details, long maxPathLength, File file } /** - * Compute steady-state probabilities for the current model (DTMCs/CTMCs only). - * Output probability distribution to log. + * Compute/export steady-state probabilities for the current model, building it first if needed. + * Applicable for DTMCs/CTMCs only. + * @param file File to export to (if null, print to the log instead) + * @param exportFormat The format to use for export */ - public void doSteadyState() throws PrismException + public void exportSteadyStateProbabilities(File file, ModelExportFormat exportFormat) throws PrismException { - doSteadyState(EXPORT_PLAIN, null, null); + exportSteadyStateProbabilities(file, new ModelExportOptions(exportFormat), null); } /** - * Compute steady-state probabilities for the current model (DTMCs/CTMCs only). - * Output probability distribution to a file (or, if {@code fileOut} is null, to log). - * The exportType should be EXPORT_PLAIN or EXPORT_MATLAB. + * Compute/export steady-state probabilities for the current model, building it first if needed. + * Applicable for DTMCs/CTMCs only. * Optionally (if non-null), read in the initial probability distribution from a file. + * @param file File to export to (if null, print to the log instead) + * @param exportFormat The format to use for export + * @param initDistFile Initial distribution (ignored if null) */ - public void doSteadyState(int exportType, File fileOut, File fileIn) throws PrismException + public void exportSteadyStateProbabilities(File file, ModelExportFormat exportFormat, File initDistFile) throws PrismException { - long l = 0; // timer - StateValues probs = null; - explicit.StateValues probsExpl = null; - PrismLog tmpLog; + exportSteadyStateProbabilities(file, new ModelExportOptions(exportFormat), initDistFile); + } - // Do some checks - if (!(getModelType() == ModelType.CTMC || getModelType() == ModelType.DTMC)) - throw new PrismException("Steady-state probabilities only computed for DTMCs/CTMCs"); - if (exportType == EXPORT_ROWS) - exportType = EXPORT_PLAIN; // rows format does not apply to states output + /** + * Compute/export steady-state probabilities for the current model, building it first if needed. + * Applicable for DTMCs/CTMCs only. + * Optionally (if non-null), read in the initial probability distribution from a file. + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + * @param initDistFile Initial distribution (ignored if null) + */ + public void exportSteadyStateProbabilities(File file, ModelExportOptions exportOptions, File initDistFile) throws PrismException + { + prism.StateVector probs = computeSteadyStateProbabilities(initDistFile); + mainLog.print("\nExporting steady-state probabilities "); + mainLog.println(exportOptions.getFormat().description() + " " + getDestinationStringForFile(file)); + try (PrismLog out = getPrismLogForFile(file)) { + probs.print(out, file == null, exportOptions.getFormat() == ModelExportFormat.MATLAB, file == null, file == null); + } + probs.clear(); + } - // Print message + /** + * Compute steady-state probabilities for the current model, building it first if needed. + * Applicable for DTMCs/CTMCs only. + * Optionally (if non-null), read in the initial probability distribution from a file. + * @param initDistFile Initial distribution (ignored if null) + */ + public prism.StateVector computeSteadyStateProbabilities(File initDistFile) throws PrismException + { + if (!(getModelType() == ModelType.CTMC || getModelType() == ModelType.DTMC)) { + throw new PrismException("Steady-state probabilities only computed for DTMCs/CTMCs"); + } mainLog.printSeparator(); mainLog.println("\nComputing steady-state probabilities..."); - // Build model, if necessary buildModelIfRequired(); - - l = System.currentTimeMillis(); + // Do computation + long l = System.currentTimeMillis(); + prism.StateVector probs; if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - probs = computeSteadyStateProbabilities(getBuiltModelSymbolic(), fileIn); + probs = computeSteadyStateProbabilities(getBuiltModelSymbolic(), initDistFile); } else { - probsExpl = computeSteadyStateProbabilitiesExplicit(getBuiltModelExplicit(), fileIn); + probs = computeSteadyStateProbabilitiesExplicit(getBuiltModelExplicit(), initDistFile); } l = System.currentTimeMillis() - l; - - // print message - mainLog.print("\nPrinting steady-state probabilities "); - mainLog.print(getStringForExportType(exportType) + " "); - mainLog.println(getDestinationStringForFile(fileOut)); - - // create new file log or use main log - tmpLog = getPrismLogForFile(fileOut); - - // print out or export probabilities - if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - probs.print(tmpLog, fileOut == null, exportType == EXPORT_MATLAB, fileOut == null, fileOut == null); - } else { - probsExpl.print(tmpLog, fileOut == null, exportType == EXPORT_MATLAB, fileOut == null, fileOut == null); - } - - // print out computation time mainLog.println("\nTime for steady-state probability computation: " + l / 1000.0 + " seconds."); - - // tidy up - if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - probs.clear(); - } else { - probsExpl.clear(); - } - if (fileOut != null) - tmpLog.close(); + return probs; } /** @@ -4458,6 +4458,30 @@ public void exportBuiltModelCombined(File file, ModelExportOptions exportOptions exportBuiltModel(file, exportOptions); } + /** + * Compute steady-state probabilities for the current model (DTMCs/CTMCs only). + * Output probability distribution to log. + * @deprecated Use {@link #exportSteadyStateProbabilities(File, ModelExportFormat)} + */ + @Deprecated + public void doSteadyState() throws PrismException + { + exportSteadyStateProbabilities(null, ModelExportFormat.EXPLICIT, null); + } + + /** + * Compute steady-state probabilities for the current model (DTMCs/CTMCs only). + * Output probability distribution to a file (or, if {@code fileOut} is null, to log). + * The exportType should be EXPORT_PLAIN or EXPORT_MATLAB. + * Optionally (if non-null), read in the initial probability distribution from a file. + * @deprecated Use {@link #exportSteadyStateProbabilities(File, ModelExportFormat, File)} + */ + @Deprecated + public void doSteadyState(int exportType, File fileOut, File fileIn) throws PrismException + { + exportSteadyStateProbabilities(fileOut, convertExportType(exportType), fileIn); + } + /** * Convert a {@code ModelExportOptions} object to an {@code exportType} value. */ From b477ccd1075061456565b7a17c684449de5b5435 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sun, 5 Jan 2025 18:08:19 +0000 Subject: [PATCH 12/44] Refactor transient probability export methods in Prism. Use new ModelExportFormat/ModelExportOptions to specify format. The probabilities can also be accessed via computeTransientProbabilities. --- prism/src/prism/Prism.java | 290 ++++++++++++++++++++----------------- 1 file changed, 155 insertions(+), 135 deletions(-) diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index 5f2a0317d2..4964db7f94 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -3782,54 +3782,81 @@ protected explicit.StateValues computeSteadyStateProbabilitiesExplicit(explicit. } /** - * Compute transient probabilities (forwards) for the current model (DTMCs/CTMCs only). - * Output probability distribution to log. - * For a discrete-time model, {@code time} will be cast to an integer. + * Compute/export transient probabilities (forwards) for the current model, building it first if needed. + * Applicable for DTMCs/CTMCs only. For a DTMC, {@code time} will be cast to an integer. + * @param time Time instant for transient probabilities + * @param file File to export to (if null, print to the log instead) + * @param exportFormat The format to use for export */ - public void doTransient(double time) throws PrismException + public void exportTransientProbabilities(double time, File file, ModelExportFormat exportFormat) throws PrismException { - doTransient(time, EXPORT_PLAIN, null, null); + exportTransientProbabilities(time, file, new ModelExportOptions(exportFormat), null); } /** - * Compute transient probabilities (forwards) for the current model (DTMCs/CTMCs only). - * Output probability distribution to a file (or, if {@code fileOut} is null, to log). - * For a discrete-time model, {@code time} will be cast to an integer. - * The exportType should be EXPORT_PLAIN or EXPORT_MATLAB. + * Compute/export transient probabilities (forwards) for the current model, building it first if needed. + * Applicable for DTMCs/CTMCs only. For a DTMC, {@code time} will be cast to an integer. * Optionally (if non-null), read in the initial probability distribution from a file. + * @param time Time instant for transient probabilities + * @param file File to export to (if null, print to the log instead) + * @param exportFormat The format to use for export + * @param initDistFile Initial distribution (ignored if null) */ - public void doTransient(double time, int exportType, File fileOut, File fileIn) throws PrismException + public void exportTransientProbabilities(double time, File file, ModelExportFormat exportFormat, File initDistFile) throws PrismException { - long l = 0; // timer - ModelChecker mc = null; - StateValues probs = null; - explicit.StateValues probsExpl = null; - PrismLog tmpLog; + exportTransientProbabilities(time, file, new ModelExportOptions(exportFormat), initDistFile); + } - // Do some checks - if (!(getModelType() == ModelType.CTMC || getModelType() == ModelType.DTMC)) - throw new PrismException("Steady-state probabilities only computed for DTMCs/CTMCs"); - if (time < 0) - throw new PrismException("Cannot compute transient probabilities for negative time value"); - if (exportType == EXPORT_ROWS) - exportType = EXPORT_PLAIN; // rows format does not apply to states output + /** + * Compute/export transient probabilities (forwards) for the current model, building it first if needed. + * Applicable for DTMCs/CTMCs only. For a DTMC, {@code time} will be cast to an integer. + * Optionally (if non-null), read in the initial probability distribution from a file. + * @param time Time instant for transient probabilities + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + * @param initDistFile Initial distribution (ignored if null) + */ + public void exportTransientProbabilities(double time, File file, ModelExportOptions exportOptions, File initDistFile) throws PrismException + { + prism.StateVector probs = computeTransientProbabilities(time, initDistFile); + mainLog.print("\nExporting transient probabilities "); + mainLog.println(exportOptions.getFormat().description() + " " + getDestinationStringForFile(file)); + try (PrismLog out = getPrismLogForFile(file)) { + probs.print(out, file == null, exportOptions.getFormat() == ModelExportFormat.MATLAB, file == null, file == null); + } + probs.clear(); + } - // Print message + /** + * Compute transient probabilities (forwards) for the current model, building it first if needed. + * For a discrete-time model, {@code time} will be cast to an integer. + * Optionally (if non-null), read in the initial probability distribution from a file. + * @param time Time instant for transient probabilities + * @param initDistFile Initial distribution (ignored if null) + */ + public prism.StateVector computeTransientProbabilities(double time, File initDistFile) throws PrismException + { + if (!(getModelType() == ModelType.CTMC || getModelType() == ModelType.DTMC)) { + throw new PrismException("Transient probabilities only computed for DTMCs/CTMCs"); + } + if (time < 0) { + throw new PrismException("Cannot compute transient probabilities for negative time value"); + } mainLog.printSeparator(); String strTime = getModelType().continuousTime() ? Double.toString(time) : Integer.toString((int) time); mainLog.println("\nComputing transient probabilities (time = " + strTime + ")..."); - - l = System.currentTimeMillis(); - + // Do computation + long l = System.currentTimeMillis(); + prism.StateVector probs; // FAU if (getModelType() == ModelType.CTMC && settings.getString(PrismSettings.PRISM_TRANSIENT_METHOD).equals("Fast adaptive uniformisation")) { - if (fileIn != null) { + if (initDistFile != null) { throw new PrismException("Fast adaptive uniformisation cannot read an initial distribution from a file"); } ModulesFileModelGenerator prismModelGen = ModulesFileModelGenerator.createForDoubles(getPRISMModel(), this); FastAdaptiveUniformisation fau = new FastAdaptiveUniformisation(this, prismModelGen); fau.setConstantValues(getPRISMModel().getConstantValues()); - probsExpl = fau.doTransient(time); + probs = fau.doTransient(time); } // Non-FAU else { @@ -3838,112 +3865,79 @@ public void doTransient(double time, int exportType, File fileOut, File fileIn) // Symbolic if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { if (getModelType() == ModelType.DTMC) { - mc = new ProbModelChecker(this, getBuiltModelSymbolic(), null); - probs = ((ProbModelChecker) mc).doTransient((int) time, fileIn); + ModelChecker mcDTMC = new ProbModelChecker(this, getBuiltModelSymbolic(), null); + probs = ((ProbModelChecker) mcDTMC).doTransient((int) time, initDistFile); } else { - mc = new StochModelChecker(this, getBuiltModelSymbolic(), null); - probs = ((StochModelChecker) mc).doTransient(time, fileIn); + ModelChecker mcCTMC = new StochModelChecker(this, getBuiltModelSymbolic(), null); + probs = ((StochModelChecker) mcCTMC).doTransient(time, initDistFile); } } // Explicit else { if (getModelType() == ModelType.DTMC) { DTMCModelChecker mcDTMC = new DTMCModelChecker(this); - probsExpl = mcDTMC.doTransient((DTMC) getBuiltModelExplicit(), (int) time, fileIn); + probs = mcDTMC.doTransient((DTMC) getBuiltModelExplicit(), (int) time, initDistFile); } else if (getModelType() == ModelType.CTMC) { CTMCModelChecker mcCTMC = new CTMCModelChecker(this); - probsExpl = mcCTMC.doTransient((CTMC) getBuiltModelExplicit(), time, fileIn); + probs = mcCTMC.doTransient((CTMC) getBuiltModelExplicit(), time, initDistFile); } else { throw new PrismException("Transient probabilities only computed for DTMCs/CTMCs"); } } } - l = System.currentTimeMillis() - l; - - // print message - mainLog.print("\nPrinting transient probabilities "); - mainLog.print(getStringForExportType(exportType) + " "); - mainLog.println(getDestinationStringForFile(fileOut)); - - // create new file log or use main log - tmpLog = getPrismLogForFile(fileOut); - - // print out or export probabilities - if (probs != null) - probs.print(tmpLog, fileOut == null, exportType == EXPORT_MATLAB, fileOut == null, fileOut == null); - else - probsExpl.print(tmpLog, fileOut == null, exportType == EXPORT_MATLAB, fileOut == null, fileOut == null); - - // print out computation time mainLog.println("\nTime for transient probability computation: " + l / 1000.0 + " seconds."); - - // tidy up - if (probs != null) - probs.clear(); - if (probsExpl != null) - probsExpl.clear(); - if (fileOut != null) - tmpLog.close(); + return probs; } /** - * Compute transient probabilities (forwards) for the current model (DTMCs/CTMCs only) - * for a range of time points. Each distribution is computed incrementally. - * Output probability distribution to a file (or, if file is null, to log). - * Time points are specified using an UndefinedConstants with a single ranging variable - * (of the appropriate type (int/double) and with arbitrary name). - * The exportType should be EXPORT_PLAIN or EXPORT_MATLAB. + * Compute/export transient probabilities (forwards) for the current model, building it first if needed. + * Applicable for DTMCs/CTMCs only. For a DTMC, {@code time} will be cast to an integer. * Optionally (if non-null), read in the initial probability distribution from a file. + * @param times Time instants for transient probabilities + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + * @param initDistFile Initial distribution (ignored if null) */ - public void doTransient(UndefinedConstants times, int exportType, File fileOut, File fileIn) throws PrismException + public void exportTransientProbabilities(UndefinedConstants times, File file, ModelExportOptions exportOptions, File initDistFile) throws PrismException { - int i, timeInt = 0, initTimeInt = 0; - double timeDouble = 0, initTimeDouble = 0; - Object time; - long l = 0; // timer - StateValues probs = null, initDist = null; - explicit.StateValues probsExpl = null, initDistExpl = null; - PrismLog tmpLog = null; - File fileOutActual = null; - - // Do some checks - if (!(getModelType() == ModelType.CTMC || getModelType() == ModelType.DTMC)) - throw new PrismException("Steady-state probabilities only computed for DTMCs/CTMCs"); - if (exportType == EXPORT_ROWS) - exportType = EXPORT_PLAIN; // rows format does not apply to states output - + if (!(getModelType() == ModelType.CTMC || getModelType() == ModelType.DTMC)) { + throw new PrismException("Transient probabilities only computed for DTMCs/CTMCs"); + } // Step through required time points - for (i = 0; i < times.getNumPropertyIterations(); i++) { + prism.StateVector probs = null; + symbolic.states.StateValues probsSym = null, initDistSym = null; + explicit.StateValues probsExpl = null, initDistExpl = null; + int timeInt = 0, initTimeInt = 0; + double timeDouble = 0, initTimeDouble = 0; + for (int i = 0; i < times.getNumPropertyIterations(); i++) { // Get time, check non-negative - time = times.getPFConstantValues().getValue(0); - if (getModelType().continuousTime()) + Object time = times.getPFConstantValues().getValue(0); + if (getModelType().continuousTime()) { timeDouble = ((Double) time).doubleValue(); - else + } else { timeInt = ((Integer) time).intValue(); - if (getModelType().continuousTime() ? (((Double) time).doubleValue() < 0) : (((Integer) time).intValue() < 0)) + } + if (getModelType().continuousTime() ? (((Double) time).doubleValue() < 0) : (((Integer) time).intValue() < 0)) { throw new PrismException("Cannot compute transient probabilities for negative time value"); - - // Print message + } mainLog.printSeparator(); mainLog.println("\nComputing transient probabilities (time = " + time + ")..."); - - l = System.currentTimeMillis(); - + long l = System.currentTimeMillis(); // FAU if (getModelType() == ModelType.CTMC && settings.getString(PrismSettings.PRISM_TRANSIENT_METHOD).equals("Fast adaptive uniformisation")) { - if (fileIn != null) { + if (initDistFile != null) { throw new PrismException("Fast adaptive uniformisation cannot read an initial distribution from a file"); } ModulesFileModelGenerator prismModelGen = ModulesFileModelGenerator.createForDoubles(getPRISMModel(), this); FastAdaptiveUniformisation fau = new FastAdaptiveUniformisation(this, prismModelGen); fau.setConstantValues(getPRISMModel().getConstantValues()); if (i == 0) { - probsExpl = fau.doTransient(timeDouble); + probs = probsExpl = fau.doTransient(timeDouble); initTimeDouble = 0.0; } else { - probsExpl = fau.doTransient(timeDouble - initTimeDouble, probsExpl); + probs = probsExpl = fau.doTransient(timeDouble - initTimeDouble, probsExpl); } } // Non-FAU @@ -3955,17 +3949,17 @@ public void doTransient(UndefinedConstants times, int exportType, File fileOut, if (getModelType().continuousTime()) { StochModelChecker mc = new StochModelChecker(this, getBuiltModelSymbolic(), null); if (i == 0) { - initDist = mc.readDistributionFromFile(fileIn); + initDistSym = mc.readDistributionFromFile(initDistFile); initTimeDouble = 0; } - probs = ((StochModelChecker) mc).doTransient(timeDouble - initTimeDouble, initDist); + probs = probsSym = ((StochModelChecker) mc).doTransient(timeDouble - initTimeDouble, initDistSym); } else { ProbModelChecker mc = new ProbModelChecker(this, getBuiltModelSymbolic(), null); if (i == 0) { - initDist = mc.readDistributionFromFile(fileIn); + initDistSym = mc.readDistributionFromFile(initDistFile); initTimeInt = 0; } - probs = ((ProbModelChecker) mc).doTransient(timeInt - initTimeInt, initDist); + probs = probsSym = ((ProbModelChecker) mc).doTransient(timeInt - initTimeInt, initDistSym); } } // Explicit @@ -3973,66 +3967,50 @@ public void doTransient(UndefinedConstants times, int exportType, File fileOut, if (getModelType().continuousTime()) { CTMCModelChecker mc = new CTMCModelChecker(this); if (i == 0) { - initDistExpl = mc.readDistributionFromFile(fileIn, getBuiltModelExplicit()); + initDistExpl = mc.readDistributionFromFile(initDistFile, getBuiltModelExplicit()); initTimeDouble = 0; } - probsExpl = mc.doTransient((CTMC) getBuiltModelExplicit(), timeDouble - initTimeDouble, initDistExpl); + probs = probsExpl = mc.doTransient((CTMC) getBuiltModelExplicit(), timeDouble - initTimeDouble, initDistExpl); } else { DTMCModelChecker mc = new DTMCModelChecker(this); if (i == 0) { - initDistExpl = mc.readDistributionFromFile(fileIn, getBuiltModelExplicit()); + initDistExpl = mc.readDistributionFromFile(initDistFile, getBuiltModelExplicit()); initTimeInt = 0; } - probsExpl = mc.doTransient((DTMC) getBuiltModelExplicit(), timeInt - initTimeInt, initDistExpl); + probs = probsExpl = mc.doTransient((DTMC) getBuiltModelExplicit(), timeInt - initTimeInt, initDistExpl); } } } - l = System.currentTimeMillis() - l; + mainLog.println("\nTime for transient probability computation: " + l / 1000.0 + " seconds."); // If output is to a file and there are multiple points, change filename - if (fileOut != null && times.getNumPropertyIterations() > 1) { - fileOutActual = new File(PrismUtils.addSuffixToFilename(fileOut.getPath(), time.toString())); + File fileOutActual; + if (file != null && times.getNumPropertyIterations() > 1) { + fileOutActual = new File(PrismUtils.addSuffixToFilename(file.getPath(), time.toString())); } else { - fileOutActual = fileOut; + fileOutActual = file; } - - // print message - mainLog.print("\nPrinting transient probabilities "); - mainLog.print(getStringForExportType(exportType) + " "); - mainLog.println(getDestinationStringForFile(fileOutActual)); - - // create new file log or use main log - tmpLog = getPrismLogForFile(fileOutActual); - - // print out or export probabilities - if (probs != null) - probs.print(tmpLog, fileOut == null, exportType == EXPORT_MATLAB, fileOut == null); - else if (!settings.getString(PrismSettings.PRISM_TRANSIENT_METHOD).equals("Fast adaptive uniformisation")) { - probsExpl.print(tmpLog, fileOut == null, exportType == EXPORT_MATLAB, fileOut == null, true); - } else { - // If full state space not computed, don't print vectors and always show states - probsExpl.print(tmpLog, fileOut == null, exportType == EXPORT_MATLAB, true, false); + // Print/export probabilities + mainLog.print("\nExporting transient probabilities "); + mainLog.println(exportOptions.getFormat().description() + " " + getDestinationStringForFile(fileOutActual)); + try (PrismLog out = getPrismLogForFile(fileOutActual)) { + if (!settings.getString(PrismSettings.PRISM_TRANSIENT_METHOD).equals("Fast adaptive uniformisation")) { + probs.print(out, file == null, exportOptions.getFormat() == ModelExportFormat.MATLAB, file == null, true); + } else { + // If full state space not computed, don't print vectors and always show states + probs.print(out, file == null, exportOptions.getFormat() == ModelExportFormat.MATLAB, true, false); + } } - // print out computation time - mainLog.println("\nTime for transient probability computation: " + l / 1000.0 + " seconds."); - // Prepare for next iteration - initDist = probs; + initDistSym = probsSym; initDistExpl = probsExpl; initTimeInt = timeInt; initTimeDouble = timeDouble; times.iterateProperty(); } - - // tidy up - if (probs != null) - probs.clear(); - if (probsExpl != null) - probsExpl.clear(); - if (fileOut != null) - tmpLog.close(); + probs.clear(); } public void explicitBuildTest() throws PrismException @@ -4482,6 +4460,48 @@ public void doSteadyState(int exportType, File fileOut, File fileIn) throws Pris exportSteadyStateProbabilities(fileOut, convertExportType(exportType), fileIn); } + /** + * Compute transient probabilities (forwards) for the current model (DTMCs/CTMCs only). + * Output probability distribution to log. + * For a discrete-time model, {@code time} will be cast to an integer. + * @deprecated Use {@link #exportTransientProbabilities(double, File, ModelExportFormat)} + */ + @Deprecated + public void doTransient(double time) throws PrismException + { + exportTransientProbabilities(time, null, ModelExportFormat.EXPLICIT, null); + } + + /** + * Compute transient probabilities (forwards) for the current model (DTMCs/CTMCs only). + * Output probability distribution to a file (or, if {@code fileOut} is null, to log). + * For a discrete-time model, {@code time} will be cast to an integer. + * The exportType should be EXPORT_PLAIN or EXPORT_MATLAB. + * Optionally (if non-null), read in the initial probability distribution from a file. + * @deprecated Use {@link #exportTransientProbabilities(double, File, ModelExportFormat, File)} + */ + @Deprecated + public void doTransient(double time, int exportType, File fileOut, File fileIn) throws PrismException + { + exportTransientProbabilities(time, fileOut, convertExportType(exportType), fileIn); + } + + /** + * Compute transient probabilities (forwards) for the current model (DTMCs/CTMCs only) + * for a range of time points. Each distribution is computed incrementally. + * Output probability distribution to a file (or, if file is null, to log). + * Time points are specified using an UndefinedConstants with a single ranging variable + * (of the appropriate type (int/double) and with arbitrary name). + * The exportType should be EXPORT_PLAIN or EXPORT_MATLAB. + * Optionally (if non-null), read in the initial probability distribution from a file. + * @deprecated Use {@link #exportTransientProbabilities(UndefinedConstants, File, ModelExportOptions, File)}. + */ + @Deprecated + public void doTransient(UndefinedConstants times, int exportType, File fileOut, File fileIn) throws PrismException + { + exportTransientProbabilities(times, fileOut, convertExportType(exportType), fileIn); + } + /** * Convert a {@code ModelExportOptions} object to an {@code exportType} value. */ From e065fcd5da7fd95adebdcab5b78912b9271acc54 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sun, 5 Jan 2025 23:05:51 +0000 Subject: [PATCH 13/44] Refactor steady-state/transient probability export methods in Prism. Push some logic (type checks, range handling) into Prism. This means that the GUI can compute multiple transient probabilities. Also fix a memory leak (vectors not clear()ed in multi-time transient). --- prism/src/prism/Prism.java | 167 ++++++++++++------ prism/src/prism/PrismCL.java | 31 +--- .../userinterface/model/GUIMultiModel.java | 4 +- .../model/GUIMultiModelHandler.java | 8 +- .../userinterface/model/GUITransientTime.java | 23 +-- .../computation/ComputeSteadyStateThread.java | 3 +- .../computation/ComputeTransientThread.java | 9 +- 7 files changed, 130 insertions(+), 115 deletions(-) diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index 4964db7f94..ebd539f8ed 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -55,7 +55,6 @@ import jdd.JDDVars; import mtbdd.PrismMTBDD; import odd.ODDUtils; -import param.Function; import param.ParamMode; import param.ParamModelChecker; import parser.PrismParser; @@ -3721,6 +3720,9 @@ public prism.StateVector computeSteadyStateProbabilities(File initDistFile) thro if (!(getModelType() == ModelType.CTMC || getModelType() == ModelType.DTMC)) { throw new PrismException("Steady-state probabilities only computed for DTMCs/CTMCs"); } + if (getCurrentEngine() == PrismEngine.EXACT || getCurrentEngine() == PrismEngine.PARAM) { + throw new PrismException("Steady-state probabilities cannot be computed with " + getCurrentEngine().description() + " engine"); + } mainLog.printSeparator(); mainLog.println("\nComputing steady-state probabilities..."); // Build model, if necessary @@ -3728,10 +3730,15 @@ public prism.StateVector computeSteadyStateProbabilities(File initDistFile) thro // Do computation long l = System.currentTimeMillis(); prism.StateVector probs; - if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - probs = computeSteadyStateProbabilities(getBuiltModelSymbolic(), initDistFile); - } else { - probs = computeSteadyStateProbabilitiesExplicit(getBuiltModelExplicit(), initDistFile); + switch (getBuiltModelType()) { + case SYMBOLIC: + probs = computeSteadyStateProbabilities(getBuiltModelSymbolic(), initDistFile); + break; + case EXPLICIT: + probs = computeSteadyStateProbabilitiesExplicit(getBuiltModelExplicit(), initDistFile); + break; + default: + throw new PrismException("Steady-state probability computation not supported for " + getBuiltModelType().description() + " models"); } l = System.currentTimeMillis() - l; mainLog.println("\nTime for steady-state probability computation: " + l / 1000.0 + " seconds."); @@ -3807,6 +3814,35 @@ public void exportTransientProbabilities(double time, File file, ModelExportForm exportTransientProbabilities(time, file, new ModelExportOptions(exportFormat), initDistFile); } + /** + * Compute/export transient probabilities (forwards) for the current model, building it first if needed. + * Applicable for DTMCs/CTMCs only. The time (or times) for which transient probabilities are to be computed + * are specified as a string, which should give an integer/double for discrete/continuous time models. + * Multiple times can also be given in "experiment" notation ("1:10", "0.1:0.1:1.0"). + * Optionally (if non-null), read in the initial probability distribution from a file. + * @param timeSpec Time instant(s) for transient probabilities + * @param file File to export to (if null, print to the log instead) + * @param exportOptions The options for export + * @param initDistFile Initial distribution (ignored if null) + */ + public void exportTransientProbabilities(String timeSpec, File file, ModelExportOptions exportOptions, File initDistFile) throws PrismException + { + // Parse time specification, store as UndefinedConstant for constant T + // (NB: use "null" for model to avoid a potential name clash with T) + String timeType = getModelType().continuousTime() ? "double" : "int"; + UndefinedConstants ucTransient = new UndefinedConstants(null, parsePropertiesString(null, "const " + timeType + " T; T;")); + try { + ucTransient.defineUsingConstSwitch("T=" + timeSpec); + } catch (PrismException e) { + if (timeSpec.contains(":")) { + throw new PrismException("\"" + timeSpec + "\" is not a valid time range for a " + getModelType()); + } else { + throw new PrismException("\"" + timeSpec + "\" is not a valid time for a " + getModelType()); + } + } + exportTransientProbabilities(ucTransient, file, exportOptions, initDistFile); + } + /** * Compute/export transient probabilities (forwards) for the current model, building it first if needed. * Applicable for DTMCs/CTMCs only. For a DTMC, {@code time} will be cast to an integer. @@ -3839,6 +3875,9 @@ public prism.StateVector computeTransientProbabilities(double time, File initDis if (!(getModelType() == ModelType.CTMC || getModelType() == ModelType.DTMC)) { throw new PrismException("Transient probabilities only computed for DTMCs/CTMCs"); } + if (getCurrentEngine() == PrismEngine.EXACT || getCurrentEngine() == PrismEngine.PARAM) { + throw new PrismException("Transient probabilities cannot be computed with " + getCurrentEngine().description() + " engine"); + } if (time < 0) { throw new PrismException("Cannot compute transient probabilities for negative time value"); } @@ -3862,27 +3901,28 @@ public prism.StateVector computeTransientProbabilities(double time, File initDis else { // Build model, if necessary buildModelIfRequired(); - // Symbolic - if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - if (getModelType() == ModelType.DTMC) { - ModelChecker mcDTMC = new ProbModelChecker(this, getBuiltModelSymbolic(), null); - probs = ((ProbModelChecker) mcDTMC).doTransient((int) time, initDistFile); - } else { - ModelChecker mcCTMC = new StochModelChecker(this, getBuiltModelSymbolic(), null); - probs = ((StochModelChecker) mcCTMC).doTransient(time, initDistFile); - } - } - // Explicit - else { - if (getModelType() == ModelType.DTMC) { - DTMCModelChecker mcDTMC = new DTMCModelChecker(this); - probs = mcDTMC.doTransient((DTMC) getBuiltModelExplicit(), (int) time, initDistFile); - } else if (getModelType() == ModelType.CTMC) { - CTMCModelChecker mcCTMC = new CTMCModelChecker(this); - probs = mcCTMC.doTransient((CTMC) getBuiltModelExplicit(), time, initDistFile); - } else { - throw new PrismException("Transient probabilities only computed for DTMCs/CTMCs"); - } + // Then solve + switch (getBuiltModelType()) { + case SYMBOLIC: + if (getModelType() == ModelType.DTMC) { + ModelChecker mcDTMC = new ProbModelChecker(this, getBuiltModelSymbolic(), null); + probs = ((ProbModelChecker) mcDTMC).doTransient((int) time, initDistFile); + } else { + ModelChecker mcCTMC = new StochModelChecker(this, getBuiltModelSymbolic(), null); + probs = ((StochModelChecker) mcCTMC).doTransient(time, initDistFile); + } + break; + case EXPLICIT: + if (getModelType() == ModelType.DTMC) { + DTMCModelChecker mcDTMC = new DTMCModelChecker(this); + probs = mcDTMC.doTransient((DTMC) getBuiltModelExplicit(), (int) time, initDistFile); + } else { + CTMCModelChecker mcCTMC = new CTMCModelChecker(this); + probs = mcCTMC.doTransient((CTMC) getBuiltModelExplicit(), time, initDistFile); + } + break; + default: + throw new PrismException("Transient probability computation not supported for " + getBuiltModelType().description() + " models"); } } l = System.currentTimeMillis() - l; @@ -3904,6 +3944,9 @@ public void exportTransientProbabilities(UndefinedConstants times, File file, Mo if (!(getModelType() == ModelType.CTMC || getModelType() == ModelType.DTMC)) { throw new PrismException("Transient probabilities only computed for DTMCs/CTMCs"); } + if (getCurrentEngine() == PrismEngine.EXACT || getCurrentEngine() == PrismEngine.PARAM) { + throw new PrismException("Transient probabilities cannot be computed with " + getCurrentEngine().description() + " engine"); + } // Step through required time points prism.StateVector probs = null; symbolic.states.StateValues probsSym = null, initDistSym = null; @@ -3924,6 +3967,7 @@ public void exportTransientProbabilities(UndefinedConstants times, File file, Mo } mainLog.printSeparator(); mainLog.println("\nComputing transient probabilities (time = " + time + ")..."); + // Do computation long l = System.currentTimeMillis(); // FAU if (getModelType() == ModelType.CTMC && settings.getString(PrismSettings.PRISM_TRANSIENT_METHOD).equals("Fast adaptive uniformisation")) { @@ -3944,41 +3988,50 @@ public void exportTransientProbabilities(UndefinedConstants times, File file, Mo else { // Build model, if necessary buildModelIfRequired(); - // Symbolic - if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - if (getModelType().continuousTime()) { - StochModelChecker mc = new StochModelChecker(this, getBuiltModelSymbolic(), null); - if (i == 0) { - initDistSym = mc.readDistributionFromFile(initDistFile); - initTimeDouble = 0; + // Then solve + switch (getBuiltModelType()) { + case SYMBOLIC: + if (getModelType().continuousTime()) { + StochModelChecker mc = new StochModelChecker(this, getBuiltModelSymbolic(), null); + if (i == 0) { + initDistSym = mc.readDistributionFromFile(initDistFile); + initTimeDouble = 0; + } + probs = probsSym = ((StochModelChecker) mc).doTransient(timeDouble - initTimeDouble, initDistSym); + } else { + ProbModelChecker mc = new ProbModelChecker(this, getBuiltModelSymbolic(), null); + if (i == 0) { + initDistSym = mc.readDistributionFromFile(initDistFile); + initTimeInt = 0; + } + probs = probsSym = ((ProbModelChecker) mc).doTransient(timeInt - initTimeInt, initDistSym); } - probs = probsSym = ((StochModelChecker) mc).doTransient(timeDouble - initTimeDouble, initDistSym); - } else { - ProbModelChecker mc = new ProbModelChecker(this, getBuiltModelSymbolic(), null); - if (i == 0) { - initDistSym = mc.readDistributionFromFile(initDistFile); - initTimeInt = 0; + if (initDistSym != null) { + initDistSym.clear(); } - probs = probsSym = ((ProbModelChecker) mc).doTransient(timeInt - initTimeInt, initDistSym); - } - } - // Explicit - else { - if (getModelType().continuousTime()) { - CTMCModelChecker mc = new CTMCModelChecker(this); - if (i == 0) { - initDistExpl = mc.readDistributionFromFile(initDistFile, getBuiltModelExplicit()); - initTimeDouble = 0; + break; + case EXPLICIT: + if (getModelType().continuousTime()) { + CTMCModelChecker mc = new CTMCModelChecker(this); + if (i == 0) { + initDistExpl = mc.readDistributionFromFile(initDistFile, getBuiltModelExplicit()); + initTimeDouble = 0; + } + probs = probsExpl = mc.doTransient((CTMC) getBuiltModelExplicit(), timeDouble - initTimeDouble, initDistExpl); + } else { + DTMCModelChecker mc = new DTMCModelChecker(this); + if (i == 0) { + initDistExpl = mc.readDistributionFromFile(initDistFile, getBuiltModelExplicit()); + initTimeInt = 0; + } + probs = probsExpl = mc.doTransient((DTMC) getBuiltModelExplicit(), timeInt - initTimeInt, initDistExpl); } - probs = probsExpl = mc.doTransient((CTMC) getBuiltModelExplicit(), timeDouble - initTimeDouble, initDistExpl); - } else { - DTMCModelChecker mc = new DTMCModelChecker(this); - if (i == 0) { - initDistExpl = mc.readDistributionFromFile(initDistFile, getBuiltModelExplicit()); - initTimeInt = 0; + if (initDistExpl != null) { + initDistExpl.clear(); } - probs = probsExpl = mc.doTransient((DTMC) getBuiltModelExplicit(), timeInt - initTimeInt, initDistExpl); - } + break; + default: + throw new PrismException("Transient probability computation not supported for " + getBuiltModelType().description() + " models"); } } l = System.currentTimeMillis() - l; diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 90539ff80c..7246956f6e 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -880,11 +880,6 @@ private void doSteadyState() File exportSteadyStateFile = null; if (steadystate) { - if (param || prism.getSettings().getBoolean(PrismSettings.PRISM_EXACT_ENABLED)) { - mainLog.printWarning("Skipping steady-state computation in parametric / exact model checking mode, currently not supported."); - return; - } - try { // Choose destination for output (file or log) if (exportSteadyStateFilename == null || exportSteadyStateFilename.equals("stdout")) @@ -892,7 +887,7 @@ private void doSteadyState() else exportSteadyStateFile = new File(exportSteadyStateFilename); // Compute steady-state probabilities - prism.doSteadyState(exportType, exportSteadyStateFile, importinitdist ? new File(importInitDistFilename) : null); + prism.exportSteadyStateProbabilities(exportSteadyStateFile, Prism.convertExportType(exportType), importinitdist ? new File(importInitDistFilename) : null); } catch (PrismException e) { // In case of error, report it and proceed error(e); @@ -910,35 +905,13 @@ private void doTransient() if (dotransient) { try { - if (param || prism.getSettings().getBoolean(PrismSettings.PRISM_EXACT_ENABLED)) { - mainLog.printWarning("Skipping transient probability computation in parametric / exact model checking mode, currently not supported."); - return; - } - // Choose destination for output (file or log) if (exportTransientFilename == null || exportTransientFilename.equals("stdout")) exportTransientFile = null; else exportTransientFile = new File(exportTransientFilename); - - // Determine model type - modelType = prism.getModelType(); - - // Parse time specification, store as UndefinedConstant for constant T - // (NB: use "null" for model to avoid a potential name clash with T) - String timeType = modelType.continuousTime() ? "double" : "int"; - UndefinedConstants ucTransient = new UndefinedConstants(null, prism.parsePropertiesString(null, "const " + timeType + " T; T;")); - try { - ucTransient.defineUsingConstSwitch("T=" + transientTime); - } catch (PrismException e) { - if (transientTime.contains(":")) - errorAndExit("\"" + transientTime + "\" is not a valid time range for a " + modelType); - else - errorAndExit("\"" + transientTime + "\" is not a valid time for a " + modelType); - } - // Compute transient probabilities - prism.doTransient(ucTransient, exportType, exportTransientFile, importinitdist ? new File(importInitDistFilename) : null); + prism.exportTransientProbabilities(transientTime, exportTransientFile, Prism.convertExportType(exportType), importinitdist ? new File(importInitDistFilename) : null); } // In case of error, report it and proceed catch (PrismException e) { diff --git a/prism/src/userinterface/model/GUIMultiModel.java b/prism/src/userinterface/model/GUIMultiModel.java index 1fe6abdc73..68128509a0 100644 --- a/prism/src/userinterface/model/GUIMultiModel.java +++ b/prism/src/userinterface/model/GUIMultiModel.java @@ -464,7 +464,7 @@ protected void a_exportTransient(ModelExportFormat exportFormat) // Reset warnings counter getPrism().getMainLog().resetNumberOfWarnings(); // Do transient - handler.computeTransient(GUITransientTime.getTime(), new ModelExportTask(ModelExportEntity.MODEL, getChooserFile(), new ModelExportOptions(exportFormat))); + handler.computeTransient(GUITransientTime.getTimeSpec(), new ModelExportTask(ModelExportEntity.MODEL, getChooserFile(), new ModelExportOptions(exportFormat))); } protected void a_computeTransient() @@ -476,7 +476,7 @@ protected void a_computeTransient() if (result != GUITransientTime.OK) return; // Do transient - handler.computeTransient(GUITransientTime.getTime(), new ModelExportTask(ModelExportEntity.MODEL, (File) null, new ModelExportOptions(ModelExportFormat.EXPLICIT))); + handler.computeTransient(GUITransientTime.getTimeSpec(), new ModelExportTask(ModelExportEntity.MODEL, (File) null, new ModelExportOptions(ModelExportFormat.EXPLICIT))); } protected void a_convertToPrismTextModel() diff --git a/prism/src/userinterface/model/GUIMultiModelHandler.java b/prism/src/userinterface/model/GUIMultiModelHandler.java index 12b54c0a40..3cb11dabed 100644 --- a/prism/src/userinterface/model/GUIMultiModelHandler.java +++ b/prism/src/userinterface/model/GUIMultiModelHandler.java @@ -121,7 +121,7 @@ public class GUIMultiModelHandler extends JPanel implements PrismModelListener private boolean computeSSAfterReceiveParseNotification = false; private boolean computeTransientAfterReceiveParseNotification = false; private ModelExportTask exportTask; - private double transientTime; + private String transientTimeSpec; // GUI private JSplitPane splitter; @@ -781,10 +781,10 @@ private void computeSteadyStateAfterParse() // Compute transient probabilities... - public void computeTransient(double time, ModelExportTask exportTask) + public void computeTransient(String timeSpec, ModelExportTask exportTask) { computeTransientAfterReceiveParseNotification = true; - transientTime = time; + transientTimeSpec = timeSpec; this.exportTask = exportTask; // do a parse if necessary requestParse(false); @@ -815,7 +815,7 @@ private void computeTransientAfterParse() if (exportTask.getFile() == null) { theModel.logToFront(); } - new ComputeTransientThread(this, transientTime, exportTask).start(); + new ComputeTransientThread(this, transientTimeSpec, exportTask).start(); } public void requestViewModel() diff --git a/prism/src/userinterface/model/GUITransientTime.java b/prism/src/userinterface/model/GUITransientTime.java index 4662c1c559..c41fd71bd0 100644 --- a/prism/src/userinterface/model/GUITransientTime.java +++ b/prism/src/userinterface/model/GUITransientTime.java @@ -26,7 +26,7 @@ //============================================================================== package userinterface.model; -import javax.swing.*; + import userinterface.*; public class GUITransientTime extends javax.swing.JDialog @@ -34,7 +34,7 @@ public class GUITransientTime extends javax.swing.JDialog public static final int OK = 0; public static final int CANCELLED = 1; - static double time = 0.0; + static String timeSpec = ""; static boolean first = true; private boolean cancelled = true; @@ -51,9 +51,9 @@ public int requestTime() return cancelled ? CANCELLED : OK; } - public static double getTime() + public static String getTimeSpec() { - return time; + return timeSpec; } /** Creates new form GUITransientTime */ @@ -62,7 +62,7 @@ public GUITransientTime(java.awt.Frame parent) { initComponents(); this.getRootPane().setDefaultButton(okayButton); setLocationRelativeTo(getParent()); // centre - if (!first) timeField.setText(""+time); + if (!first) timeField.setText(timeSpec); } /** This method is called from within the constructor to @@ -107,7 +107,7 @@ private void initComponents() {//GEN-BEGIN:initComponents gridBagConstraints.gridy = 4; jPanel1.add(jPanel5, gridBagConstraints); - jLabel1.setText("Please specify a time for which to compute transient probabilities:"); + jLabel1.setText("Please specify the time(s) for which to compute transient probabilities:"); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 1; @@ -153,16 +153,7 @@ private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN- }//GEN-LAST:event_cancelButtonActionPerformed private void okayButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okayButtonActionPerformed - double d = 0.0; - try { - d = Double.parseDouble(timeField.getText()); - if (d < 0) throw new NumberFormatException(); - } - catch (NumberFormatException e) { - JOptionPane.showMessageDialog(this, "Error: Invalid time value.", "Error", JOptionPane.ERROR_MESSAGE); - return; - } - time = d; + timeSpec = timeField.getText(); first = false; cancelled = false; dispose(); diff --git a/prism/src/userinterface/model/computation/ComputeSteadyStateThread.java b/prism/src/userinterface/model/computation/ComputeSteadyStateThread.java index 7cad80c9e2..569822287f 100644 --- a/prism/src/userinterface/model/computation/ComputeSteadyStateThread.java +++ b/prism/src/userinterface/model/computation/ComputeSteadyStateThread.java @@ -32,7 +32,6 @@ import io.ModelExportTask; import userinterface.*; import userinterface.model.*; -import prism.*; import userinterface.util.*; /** @@ -67,7 +66,7 @@ public void run() // Do Computation try { - prism.doSteadyState(Prism.convertExportType(exportTask.getExportOptions()), exportTask.getFile(), null); + prism.exportSteadyStateProbabilities(exportTask.getFile(), exportTask.getExportOptions(), null); } catch (Throwable e) { error(e); SwingUtilities.invokeLater(new Runnable() diff --git a/prism/src/userinterface/model/computation/ComputeTransientThread.java b/prism/src/userinterface/model/computation/ComputeTransientThread.java index 26e0ba02c5..624672d030 100644 --- a/prism/src/userinterface/model/computation/ComputeTransientThread.java +++ b/prism/src/userinterface/model/computation/ComputeTransientThread.java @@ -32,7 +32,6 @@ import io.ModelExportTask; import userinterface.*; import userinterface.model.*; -import prism.*; import userinterface.util.*; /** @@ -42,15 +41,15 @@ public class ComputeTransientThread extends GUIComputationThread { @SuppressWarnings("unused") private GUIMultiModelHandler handler; - private double transientTime; + private String transientTimeSpec; private ModelExportTask exportTask; /** Creates a new instance of ComputeTransientThread */ - public ComputeTransientThread(GUIMultiModelHandler handler, double transientTime, ModelExportTask exportTask) + public ComputeTransientThread(GUIMultiModelHandler handler, String transientTimeSpec, ModelExportTask exportTask) { super(handler.getGUIPlugin()); this.handler = handler; - this.transientTime = transientTime; + this.transientTimeSpec = transientTimeSpec; this.exportTask = exportTask; } @@ -69,7 +68,7 @@ public void run() // Do Computation try { - prism.doTransient(transientTime, Prism.convertExportType(exportTask.getExportOptions()), exportTask.getFile(), null); + prism.exportTransientProbabilities(transientTimeSpec, exportTask.getFile(), exportTask.getExportOptions(), null); } catch (Throwable e) { error(e); SwingUtilities.invokeLater(new Runnable() From c45f781c7768c0263d442c456e6760edd259ca41 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 7 Jan 2025 19:57:56 +0000 Subject: [PATCH 14/44] Push model export reward/label storage to superclass ModelExporter. Also make all exporter classes implement exportModel(). --- prism/src/explicit/StateModelChecker.java | 40 ++-- prism/src/io/DRNExporter.java | 40 ++-- prism/src/io/DotExporter.java | 8 +- prism/src/io/MatlabExporter.java | 9 +- prism/src/io/ModelExportTask.java | 16 ++ .../io/{Exporter.java => ModelExporter.java} | 204 +++++++++++++++++- prism/src/io/PrismExplicitExporter.java | 8 +- prism/src/prism/Prism.java | 8 +- 8 files changed, 288 insertions(+), 45 deletions(-) rename prism/src/io/{Exporter.java => ModelExporter.java} (59%) diff --git a/prism/src/explicit/StateModelChecker.java b/prism/src/explicit/StateModelChecker.java index 90f6bf0e8b..c968b74d91 100644 --- a/prism/src/explicit/StateModelChecker.java +++ b/prism/src/explicit/StateModelChecker.java @@ -45,6 +45,7 @@ import io.ModelExportFormat; import io.ModelExportOptions; import io.ModelExportTask; +import io.ModelExporter; import io.PrismExplicitExporter; import io.PrismExplicitImporter; import parser.EvaluateContext.EvalMode; @@ -77,6 +78,7 @@ import parser.visitor.ASTTraverseModify; import parser.visitor.ReplaceLabels; import prism.Accuracy; +import prism.Evaluator; import prism.Filter; import prism.ModelInfo; import prism.ModelType; @@ -1531,32 +1533,44 @@ public void exportModel(Model model, ModelExportTask exportTask) File file = exportTask.getFile(); ModelExportOptions exportOptions = exportTask.getExportOptions(); try (PrismLog out = getPrismLogForFile(file)) { + ModelExporter exporter; switch (exportOptions.getFormat()) { case EXPLICIT: if (exportOptions.getExplicitRows()) { throw new PrismNotSupportedException("Export in rows format not yet supported by explicit engine"); } - new PrismExplicitExporter(exportOptions).exportTransitions(model, out); + exporter = new PrismExplicitExporter<>(exportOptions); break; case DOT: - new DotExporter(exportOptions).exportModel(model, out, null); + exporter = new DotExporter<>(exportOptions); break; case DRN: - List labelNames = new ArrayList(); - labelNames.add("init"); - labelNames.add("deadlock"); - labelNames.addAll(modelInfo.getLabelNames()); - List> rewards = new ArrayList<>(); - for (int r = 0; r < rewardGen.getNumRewardStructs(); r++) { - rewards.add(constructRewards(model, r)); - } - List labelStates = checkLabels(model, labelNames); - DRNExporter exporter = new DRNExporter<>(exportOptions); - exporter.exportModel(model, (RewardGenerator) rewardGen, rewards, labelNames, labelStates, out); + exporter = new DRNExporter<>(exportOptions); break; default: throw new PrismNotSupportedException("Export " + exportOptions.getFormat().description() + " not supported by explicit engine"); } + if (exportOptions.getFormat() == ModelExportFormat.DRN) { + // Get reward/label info + List> rewards = new ArrayList<>(); + for (int r = 0; r < rewardGen.getNumRewardStructs(); r++) { + rewards.add(constructRewards(model, r)); + } + List labelNames = new ArrayList<>(); + if (exportTask.initLabelIncluded()) { + labelNames.add("init"); + } + if (exportTask.deadlockLabelIncluded()) { + labelNames.add("deadlock"); + } + labelNames.addAll(modelInfo.getLabelNames()); + List labelStates = checkLabels(model, labelNames); + // Add to exporter + exporter.addRewards(rewards, rewardGen.getRewardStructNames()); + exporter.setRewardEvaluator((Evaluator) rewardGen.getRewardEvaluator()); + exporter.addLabels(labelStates, labelNames); + } + exporter.exportModel(model, out); } } diff --git a/prism/src/io/DRNExporter.java b/prism/src/io/DRNExporter.java index 5224f4b9c5..c086360a6a 100644 --- a/prism/src/io/DRNExporter.java +++ b/prism/src/io/DRNExporter.java @@ -44,35 +44,33 @@ /** * Class to manage export of built models to Storm's DRN file format. */ -public class DRNExporter extends Exporter +public class DRNExporter extends ModelExporter { + /** + * Construct a DRNExporter with default export options. + */ public DRNExporter() { super(); } + /** + * Construct a DRNExporter with the specified export options. + */ public DRNExporter(ModelExportOptions modelExportOptions) { super(modelExportOptions); } - /** - * Export a model. - * @param model The model - * @param rewardGen The RewardGenerator for reward info - * @param allRewards All the rewards - * @param labelNames The names of the labels to export - * @param labelStates The states that satisfy each label, specified as a BitSet - * @param out Where to export - */ - public void exportModel(Model model, RewardGenerator rewardGen, List> allRewards, List labelNames, List labelStates, PrismLog out) throws PrismException + @Override + public void exportModel(Model model, PrismLog out) throws PrismException { // Get model info and options setEvaluator(model.getEvaluator()); - Evaluator evalRewards = rewardGen.getRewardEvaluator(); + Evaluator evalRewards = getRewardEvaluator(); ModelType modelType = model.getModelType(); - int numRewardStructs = rewardGen.getNumRewardStructs(); - int numLabels = labelNames.size(); + int numRewards = getNumRewards(); + int numLabels = getNumLabels(); int numStates = model.getNumStates(); // By default, we only show actions for nondeterministic models boolean showActions = modelExportOptions.getShowActions(modelType.nondeterministic()); @@ -88,7 +86,7 @@ public void exportModel(Model model, RewardGenerator rewardGen, Li // Output reward structure info out.println("@reward_models"); - out.println(String.join(" ", PrismUtils.listReversed(rewardGen.getRewardStructNames()))); + out.println(String.join(" ", PrismUtils.listReversed(getRewardNames()))); // Output model stats out.println("@nr_states"); @@ -114,12 +112,12 @@ public void exportModel(Model model, RewardGenerator rewardGen, Li if (modelType.continuousTime()) { out.print(" !" + ((CTMC) model).getExitRate(s)); } - if (numRewardStructs > 0) { - out.print(" " + getStateRewardTuple(allRewards, s).toStringReversed(e -> formatValue(e, evalRewards), ", ")); + if (numRewards > 0) { + out.print(" " + getStateRewardTuple(getRewards(), s).toStringReversed(e -> formatValue(e, evalRewards), ", ")); } for (int i = 0; i < numLabels; i++) { - if (labelStates.get(i).get(s)) { - out.print(" " + labelNames.get(i)); + if (getLabel(i).get(s)) { + out.print(" " + getLabelName(i)); } } out.println(); @@ -137,8 +135,8 @@ public void exportModel(Model model, RewardGenerator rewardGen, Li } else { out.print(j); } - if (numRewardStructs > 0) { - out.print(" " + getTransitionRewardTuple(allRewards, s, j).toStringReversed(e -> formatValue(e, evalRewards), ", ")); + if (numRewards > 0) { + out.print(" " + getTransitionRewardTuple(getRewards(), s, j).toStringReversed(e -> formatValue(e, evalRewards), ", ")); } out.println(); // Print out (sorted) transitions diff --git a/prism/src/io/DotExporter.java b/prism/src/io/DotExporter.java index ed5aa4f41d..b4c42b0621 100644 --- a/prism/src/io/DotExporter.java +++ b/prism/src/io/DotExporter.java @@ -36,7 +36,7 @@ /** * Class to manage export of built models to Dot format. */ -public class DotExporter extends Exporter +public class DotExporter extends ModelExporter { public DotExporter() { @@ -48,6 +48,12 @@ public DotExporter(ModelExportOptions modelExportOptions) super(modelExportOptions); } + @Override + public void exportModel(Model model, PrismLog out) + { + exportModel(model, out, null); + } + /** * Export a model in Dot format. * @param model Model to export diff --git a/prism/src/io/MatlabExporter.java b/prism/src/io/MatlabExporter.java index 17682edaf4..39a6dbf624 100644 --- a/prism/src/io/MatlabExporter.java +++ b/prism/src/io/MatlabExporter.java @@ -33,6 +33,7 @@ import prism.ModelInfo; import prism.PrismException; import prism.PrismLog; +import prism.PrismNotSupportedException; import java.util.BitSet; import java.util.List; @@ -40,7 +41,7 @@ /** * Class to manage export of built models to Matlab format. */ -public class MatlabExporter extends Exporter +public class MatlabExporter extends ModelExporter { public MatlabExporter() { @@ -52,6 +53,12 @@ public MatlabExporter(ModelExportOptions modelExportOptions) super(modelExportOptions); } + @Override + public void exportModel(Model model, PrismLog out) throws PrismException + { + throw new PrismNotSupportedException("Model export not supported in Matlab format"); + } + /** * Export the states for a model. * @param model The model diff --git a/prism/src/io/ModelExportTask.java b/prism/src/io/ModelExportTask.java index be761441d4..1ec52932e5 100644 --- a/prism/src/io/ModelExportTask.java +++ b/prism/src/io/ModelExportTask.java @@ -368,6 +368,22 @@ public boolean extraLabelsUsed() return labelExportSet == LabelExportSet.EXTRA || labelExportSet == LabelExportSet.ALL; } + /** + * Get whether "init" should be included with model labels. + */ + public boolean initLabelIncluded() + { + return true; + } + + /** + * Get whether "deadlock" should be included with model labels. + */ + public boolean deadlockLabelIncluded() + { + return true; + } + /** * Get a message describing the export task to be done. */ diff --git a/prism/src/io/Exporter.java b/prism/src/io/ModelExporter.java similarity index 59% rename from prism/src/io/Exporter.java rename to prism/src/io/ModelExporter.java index f02f44eca1..3f4290233c 100644 --- a/prism/src/io/Exporter.java +++ b/prism/src/io/ModelExporter.java @@ -34,53 +34,245 @@ import explicit.rewards.Rewards; import prism.Evaluator; import prism.Pair; +import prism.PrismException; +import prism.PrismLog; +import java.util.ArrayList; +import java.util.BitSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeSet; /** - * Base class for exporter classes. + * Base class for model exporter classes. */ -public class Exporter +public abstract class ModelExporter { - /** Evaluator for model (defaults to oen for doubles if not provided). */ + /** Evaluator for model (defaults to one for doubles if not provided). */ + @SuppressWarnings("unchecked") protected Evaluator eval = (Evaluator) Evaluator.forDouble(); /** Model export options */ protected ModelExportOptions modelExportOptions; - public Exporter() + // Model annotations + + protected List> rewards = new ArrayList<>(); + protected List rewardNames = new ArrayList<>(); + protected Evaluator evalRewards = null; + + protected List labels = new ArrayList<>(); + protected List labelNames = new ArrayList<>(); + + /** + * Construct a ModelExporter with default export options. + */ + public ModelExporter() { this(new ModelExportOptions()); } - public Exporter(ModelExportOptions modelExportOptions) + /** + * Construct a ModelExporter with the specified export options. + */ + public ModelExporter(ModelExportOptions modelExportOptions) { setModelExportOptions(modelExportOptions); } + // Set methods + + /** + * Set the evaluator for dealing with model values. + */ public void setEvaluator(Evaluator eval) { this.eval = eval; } + /** + * Set the export options. + */ public void setModelExportOptions(ModelExportOptions modelExportOptions) { this.modelExportOptions = modelExportOptions; } + /** + * Add an unnamed reward to be exported. + */ + public void addReward(Rewards rew) + { + addReward(rew, ""); + } + + /** + * Add a reward to be exported. + * {@code rewName} can be null or "" if the reward is unnamed. + */ + public void addReward(Rewards rew, String rewName) + { + rewards.add(rew); + rewardNames.add(rewName == null ? "" : rewName); + } + + /** + * Add multiple rewards to be exported. + * Strings in {@code rewName} can be null or "" if the reward is unnamed. + */ + public void addRewards(List> rews, List rewNames) + { + int numRewards = rews.size(); + for (int i = 0; i < numRewards; i++) { + addReward(rews.get(i), rewNames.get(i)); + } + } + + /** + * Set a separate evaluator for dealing with reward values. + * If not set, the evaluator for model values is used. + */ + public void setRewardEvaluator(Evaluator evalRewards) + { + this.evalRewards = evalRewards; + } + + /** + * Add a label to be exported. + */ + public void addLabel(BitSet label, String labelName) + { + labels.add(label); + labelNames.add(labelName); + } + + /** + * Add multiple labels to be exported. + */ + public void addLabels(List labels, List labelNames) + { + int numLabels = labels.size(); + for (int i = 0; i < numLabels; i++) { + addLabel(labels.get(i), labelNames.get(i)); + } + } + + // Get methods + + /** + * Get the evaluator for dealing with model values. + */ public Evaluator getEvaluator() { return eval; } + /** + * Get the export options. + */ public ModelExportOptions getModelExportOptions() { return modelExportOptions; } + /** + * Get the number of rewards to be exported. + */ + public int getNumRewards() + { + return rewards.size(); + } + + /** + * Get the rewards to be exported. + */ + public List> getRewards() + { + return rewards; + } + + /** + * Get the ith reward to be exported. + */ + public Rewards getReward(int i) + { + return rewards.get(i); + } + + /** + * Get the names of the rewards to be exported. + * Some names may be empty (""); + */ + public List getRewardNames() + { + return rewardNames; + } + + /** + * Get the names of the ith reward to be exported. + * May be empty (""); + */ + public String getRewardName(int i) + { + return rewardNames.get(i); + } + + /** + * Get a separate evaluator for dealing with reward values. + */ + public Evaluator getRewardEvaluator() + { + return evalRewards == null ? getEvaluator() : evalRewards; + } + + /** + * Get the number of labels to be exported. + */ + public int getNumLabels() + { + return labels.size(); + } + + /** + * Get the labels to be exported. + */ + public List getLabels() + { + return labels; + } + + /** + * Get the ith label to be exported. + */ + public BitSet getLabel(int i) + { + return labels.get(i); + } + + /** + * Get the names of the labels to be exported. + */ + public List getLabelNames() + { + return labelNames; + } + + /** + * Get the name of the ith label to be exported. + */ + public String getLabelName(int i) + { + return labelNames.get(i); + } + + /** + * Export a model. + * @param model The model + * @param out Where to export + */ + public abstract void exportModel(Model model, PrismLog out) throws PrismException; + // Utility functions /** @@ -149,7 +341,7 @@ public Iterable> getSortedTransitionRewardsIterator(DTMC> sorted = new TreeSet<>(Transition::compareTo); Object action = null; - Iterator>> iter = ((DTMC) model).getTransitionsAndActionsIterator(s); + Iterator>> iter = model.getTransitionsAndActionsIterator(s); int k = 0; while (iter.hasNext()) { Map.Entry> e = iter.next(); diff --git a/prism/src/io/PrismExplicitExporter.java b/prism/src/io/PrismExplicitExporter.java index 500f7c2780..6baaf7001e 100644 --- a/prism/src/io/PrismExplicitExporter.java +++ b/prism/src/io/PrismExplicitExporter.java @@ -47,7 +47,7 @@ /** * Class to manage export of built models to PRISM's explicit file formats. */ -public class PrismExplicitExporter extends Exporter +public class PrismExplicitExporter extends ModelExporter { public PrismExplicitExporter() { @@ -59,6 +59,12 @@ public PrismExplicitExporter(ModelExportOptions modelExportOptions) super(modelExportOptions); } + @Override + public void exportModel(Model model, PrismLog out) throws PrismException + { + exportTransitions(model, out); + } + /** * Export a model in PRISM's .tra format. * @param model Model to export diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index ebd539f8ed..d0d5885a52 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -2827,8 +2827,12 @@ private void doExportBuiltModelLabels(ModelExportTask exportTask) throws PrismEx // Collect names of labels to export from model and/or properties file List labelNames = new ArrayList<>(); if (exportTask.getLabelExportSet() == ModelExportTask.LabelExportSet.MODEL || exportTask.getLabelExportSet() == ModelExportTask.LabelExportSet.ALL) { - labelNames.add("init"); - labelNames.add("deadlock"); + if (exportTask.initLabelIncluded()) { + labelNames.add("init"); + } + if (exportTask.deadlockLabelIncluded()) { + labelNames.add("deadlock"); + } labelNames.addAll(getModelInfo().getLabelNames()); } if (exportTask.getLabelExportSet() == ModelExportTask.LabelExportSet.EXTRA || exportTask.getLabelExportSet() == ModelExportTask.LabelExportSet.ALL) { From 9f5fb22ee2e65b76c366873471d3d7c93101ebe9 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 14 Jan 2025 11:32:05 +0000 Subject: [PATCH 15/44] Allow engine switching (to explicit) for some model export formats. In the same (non-ideal) way currently ued for model checking. We also change the export workflow in the command-line. We no longer force a build initially, and instead bail out when the first export fails, if any. --- prism/src/prism/Prism.java | 74 ++++++++++++++++++++++++------------ prism/src/prism/PrismCL.java | 52 ++++++++----------------- 2 files changed, 65 insertions(+), 61 deletions(-) diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index d0d5885a52..95364a67f4 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -2123,6 +2123,14 @@ public boolean modelIsBuilt() return modelBuildTypeForEngine != null && getBuiltModelType() == modelBuildTypeForEngine; } + /** + * Returns true if the current model has been built (for any engine). + */ + public boolean someModelIsBuilt() + { + return getBuiltModelType() != null; + } + /** * Get the currently stored strategy (null if none) */ @@ -2673,30 +2681,48 @@ public void exportBuiltModelTask(ModelExportTask exportTask) throws PrismExcepti if (!exportTask.isApplicable(getModelInfo())) { return; } - // Build model, if necessary - buildModelIfRequired(); - // Merge export options with PRISM settings and do export - mainLog.println("\n" + exportTask.getMessage()); - ModelExportOptions exportOptions = newMergedModelExportOptions(exportTask.getExportOptions()); - switch (exportTask.getEntity()) { - case MODEL: - doExportBuiltModel(new ModelExportTask(exportTask, exportOptions)); - break; - case STATE_REWARDS: - doExportBuiltModelStateRewards(exportTask.getFile(), exportOptions); - break; - case TRANSITION_REWARDS: - doExportBuiltModelTransRewards(exportTask.getFile(), exportOptions); - break; - case STATES: - doExportBuiltModelStates(exportTask.getFile(), exportOptions); - break; - case OBSERVATIONS: - doExportBuiltModelObservations(exportTask.getFile(), exportOptions); - break; - case LABELS: - doExportBuiltModelLabels(new ModelExportTask(exportTask, exportOptions)); - break; + boolean engineSwitch = false; + int lastEngine = -1; + try { + // Auto-switch to explicit engine if required + if (exportTask.getExportOptions().getFormat() == ModelExportFormat.DRN) { + if (getCurrentEngine() == PrismEngine.SYMBOLIC) { + mainLog.printWarning("Switching to explicit engine to allow export " + exportTask.getExportOptions().getFormat().description()); + engineSwitch = true; + lastEngine = getEngine(); + setEngine(Prism.EXPLICIT); + } + } + // Build model, if necessary + buildModelIfRequired(); + // Merge export options with PRISM settings and do export + mainLog.println("\n" + exportTask.getMessage()); + ModelExportOptions exportOptions = newMergedModelExportOptions(exportTask.getExportOptions()); + switch (exportTask.getEntity()) { + case MODEL: + doExportBuiltModel(new ModelExportTask(exportTask, exportOptions)); + break; + case STATE_REWARDS: + doExportBuiltModelStateRewards(exportTask.getFile(), exportOptions); + break; + case TRANSITION_REWARDS: + doExportBuiltModelTransRewards(exportTask.getFile(), exportOptions); + break; + case STATES: + doExportBuiltModelStates(exportTask.getFile(), exportOptions); + break; + case OBSERVATIONS: + doExportBuiltModelObservations(exportTask.getFile(), exportOptions); + break; + case LABELS: + doExportBuiltModelLabels(new ModelExportTask(exportTask, exportOptions)); + break; + } + } finally { + // Undo auto-switch (if any) + if (engineSwitch) { + setEngine(lastEngine); + } } } diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 7246956f6e..153c7659b2 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -365,7 +365,11 @@ public void run(String[] args) } // Do any model exports - doExports(); + try { + doExports(); + } catch (PrismException e) { + error(e); + } if (modelBuildFail) continue; @@ -507,7 +511,7 @@ public void run(String[] args) } // Explicitly request a build if necessary - if (propertiesToCheck.size() == 0 && !steadystate && !dotransient && !simpath && !nobuild && prism.modelCanBeBuilt() && !prism.modelIsBuilt()) { + if (propertiesToCheck.size() == 0 && !steadystate && !dotransient && !simpath && !nobuild && prism.modelCanBeBuilt() && !prism.someModelIsBuilt()) { try { prism.buildModel(); } catch (PrismException e) { @@ -764,7 +768,7 @@ else if (propertyIndices == null) { // do any exporting requested - private void doExports() + private void doExports() throws PrismException { // export prism model (with constants), if requested if (exportprismconst) { @@ -780,36 +784,18 @@ private void doExports() } } - if (!modelExportTasks.isEmpty() || - exportmodeldotview || - exportsccs || - exportbsccs || - exportmecs) { - // if there are any exports to do, - // force model construction to catch errors during build - try { - prism.buildModel(); - } catch (PrismException e) { - error(e); - return; - } - } + // Exceptions from the remaining exports are thrown + // since they usually indicate a model build problem, affecting all // Do export tasks for (ModelExportTask exportTask : modelExportTasks) { - try { - exportTask.getExportOptions().apply(modelExportOptionsGlobal); - if (exportTask.extraLabelsUsed()) { - definedPFConstants = undefinedMFConstants.getPFConstantValues(); - propertiesFile.setSomeUndefinedConstants(definedPFConstants, exactConstants); - exportTask.setExtraLabelsSource(propertiesFile); - } - prism.exportBuiltModelTask(exportTask); - } - // In case of error, report it and proceed - catch (PrismException e) { - error(e); + exportTask.getExportOptions().apply(modelExportOptionsGlobal); + if (exportTask.extraLabelsUsed()) { + definedPFConstants = undefinedMFConstants.getPFConstantValues(); + propertiesFile.setSomeUndefinedConstants(definedPFConstants, exactConstants); + exportTask.setExtraLabelsSource(propertiesFile); } + prism.exportBuiltModelTask(exportTask); } // export transition matrix graph to dot file and view it @@ -824,8 +810,6 @@ private void doExports() // in case of error, report it and proceed catch (IOException | InterruptedException e) { error("Problem generating dot file: " + e.getMessage()); - } catch (PrismException e) { - error(e); } } @@ -838,8 +822,6 @@ private void doExports() // in case of error, report it and proceed catch (FileNotFoundException e) { error("Couldn't open file \"" + exportSCCsFilename + "\" for output"); - } catch (PrismException e) { - error(e); } } @@ -852,8 +834,6 @@ private void doExports() // in case of error, report it and proceed catch (FileNotFoundException e) { error("Couldn't open file \"" + exportBSCCsFilename + "\" for output"); - } catch (PrismException e) { - error(e); } } @@ -866,8 +846,6 @@ private void doExports() // in case of error, report it and proceed catch (FileNotFoundException e) { error("Couldn't open file \"" + exportMECsFilename + "\" for output"); - } catch (PrismException e) { - error(e); } } } From ce5432e8ea410299dd7eeba51218ffafce09714c Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sat, 18 Jan 2025 16:34:52 +0000 Subject: [PATCH 16/44] Add "format" to -exportmodel switch (explicit/matlab/dot/drn). Also, allow any or no filename extension. E.g.: prism model.prism -exportmodel model.txt:format=explicit prism model.prism -exportmodel stdout:format=drn --- prism/src/io/ModelExportTask.java | 7 +++- prism/src/prism/PrismCL.java | 66 +++++++++++++++---------------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/prism/src/io/ModelExportTask.java b/prism/src/io/ModelExportTask.java index 1ec52932e5..ed4115d026 100644 --- a/prism/src/io/ModelExportTask.java +++ b/prism/src/io/ModelExportTask.java @@ -174,9 +174,13 @@ public ModelExportTask(ModelExportTask exportTask, ModelExportOptions exportOpti * Create a ModelExportTask based on a filename, supplied as separate basename and extension. * The basename can also be "stdout". It can also be left empty ("") and later replaced * (e.g. with the model basename) using {@link #replaceEmptyFileBasename(String)}. + * An unknown (or missing) extension is treated as ".tra". */ public static ModelExportTask fromFilename(String basename, String ext) throws PrismException { + if (ext == null || ext.equals("")) { + return new ModelExportTask(ModelExportEntity.MODEL, basename); + } String filename = "stdout".equals(basename) ? "stdout" : basename + "." + ext; switch (ext) { case "tra": @@ -196,7 +200,8 @@ public static ModelExportTask fromFilename(String basename, String ext) throws P case "drn": return fromFormat(filename, ModelExportFormat.DRN); default: - throw new PrismException("Unknown extension \"" + ext + "\" for model export"); + // Treat unknown extensions as .tra + return new ModelExportTask(ModelExportEntity.MODEL, filename); } } diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 153c7659b2..3dbe8adbec 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -2044,16 +2044,13 @@ private void processExportModelSwitch(String filesOptionsString) throws PrismExc String optionsString = halves[1]; // Split files into basename/extensions int i = filesString.lastIndexOf('.'); - if (i == -1) { - throw new PrismException("No file name extension(s) in file(s) \"" + filesString + "\" for -exportmodel"); - } - String basename = filesString.substring(0, i); - String extList = filesString.substring(i + 1); + String basename = i == -1 ? filesString : filesString.substring(0, i); + String extList = i == -1 ? "" : filesString.substring(i + 1); String exts[] = extList.split(","); // Process file extensions List newModelExportTasks = new ArrayList<>(); for (String ext : exts) { - // Items to export + // Some extensions get expanded to multiple exports if (ext.equals("all")) { newModelExportTasks.add(ModelExportTask.fromFilename(basename, "tra")); newModelExportTasks.add(ModelExportTask.fromFilename(basename, "srew")); @@ -2061,29 +2058,13 @@ private void processExportModelSwitch(String filesOptionsString) throws PrismExc newModelExportTasks.add(ModelExportTask.fromFilename(basename, "sta")); newModelExportTasks.add(ModelExportTask.fromFilename(basename, "obs")); newModelExportTasks.add(ModelExportTask.fromFilename(basename, "lab")); - } else if (ext.equals("tra")) { - newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); - } else if (ext.equals("srew")) { - newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); - } else if (ext.equals("trew")) { - newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); } else if (ext.equals("rew")) { newModelExportTasks.add(ModelExportTask.fromFilename(basename, "srew")); newModelExportTasks.add(ModelExportTask.fromFilename(basename, "trew")); - } else if (ext.equals("sta")) { - newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); - } else if (ext.equals("obs")) { - newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); - } else if (ext.equals("lab")) { - newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); - } else if (ext.equals("dot")) { - newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); - } else if (ext.equals("drn")) { - newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); } - // Unknown extension + // For any other extension (including none/unknown), deduce export else { - throw new PrismException("Unknown extension \"" + ext + "\" for -exportmodel switch"); + newModelExportTasks.add(ModelExportTask.fromFilename(basename, ext)); } } // Process options @@ -2093,6 +2074,29 @@ private void processExportModelSwitch(String filesOptionsString) throws PrismExc // Ignore "" if (opt.equals("")) { } + // Export format + else if (opt.startsWith("format")) { + if (!opt.startsWith("format=")) { + throw new PrismException("No value provided for \"format\" option of -exportmodel"); + } + String optVal = opt.substring(7); + switch (optVal) { + case "explicit": + exportOptions.setFormat(ModelExportFormat.EXPLICIT); + break; + case "matlab": + exportOptions.setFormat(ModelExportFormat.MATLAB); + break; + case "dot": + exportOptions.setFormat(ModelExportFormat.DOT); + break; + case "drn": + exportOptions.setFormat(ModelExportFormat.DRN); + break; + default: + throw new PrismException("Unknown value \"" + optVal + "\" provided for \"format\" option of -exportmodel"); + } + } // Export type else if (opt.equals("matlab")) { exportOptions.setFormat(ModelExportFormat.MATLAB); @@ -2100,16 +2104,7 @@ else if (opt.equals("matlab")) { } else if (opt.equals("rows")) { exportOptions.setExplicitRows(true); exportType = Prism.EXPORT_ROWS; - } /*else if (opt.startsWith("type=")) { - String exportTypeString = opt.substring(5); - if (exportTypeString.equals("matlab")) { - exportType = Prism.EXPORT_MATLAB; - } else if (exportTypeString.equals("rows")) { - exportType = Prism.EXPORT_ROWS; - } else { - throw new PrismException("Unknown type \"" + opt + "\" for -exportmodel switch"); - } - }*/ + } else if (opt.equals("proplabels")) { for (ModelExportTask exportTask : newModelExportTasks) { if (exportTask.getEntity() == ModelExportTask.ModelExportEntity.LABELS) { @@ -2683,7 +2678,8 @@ else if (sw.equals("exportmodel")) { mainLog.println("Omit the file basename to use the basename of the model file, e.g.:"); mainLog.println("\n -exportmodel .all\n"); mainLog.println("If provided, is a comma-separated list of options taken from:"); - mainLog.println(" * matlab - export data in Matlab format"); + mainLog.println(" * format (=explicit/matlab/dot/drn) - model export format"); + mainLog.println(" * matlab - same as format=matlab"); mainLog.println(" * rows - export matrices with one row/distribution on each line"); mainLog.println(" * proplabels - export labels from a properties file into the same file, too"); mainLog.println(" * actions (=true/false) - shows actions on choices/transitions"); From cdfe0f9396b994b4cdaa7c86f102272d4f656cea Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sun, 19 Jan 2025 11:24:46 +0000 Subject: [PATCH 17/44] ModelExporter can export to either a PrismLog or a File. --- prism/src/io/ModelExporter.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/prism/src/io/ModelExporter.java b/prism/src/io/ModelExporter.java index 3f4290233c..d861f265f0 100644 --- a/prism/src/io/ModelExporter.java +++ b/prism/src/io/ModelExporter.java @@ -35,8 +35,10 @@ import prism.Evaluator; import prism.Pair; import prism.PrismException; +import prism.PrismFileLog; import prism.PrismLog; +import java.io.File; import java.util.ArrayList; import java.util.BitSet; import java.util.Iterator; @@ -267,12 +269,24 @@ public String getLabelName(int i) } /** - * Export a model. + * Export a model to a {@link PrismLog}. * @param model The model * @param out Where to export */ public abstract void exportModel(Model model, PrismLog out) throws PrismException; + /** + * Export a model to a file. + * @param model The model + * @param fileOut File to export to + */ + public void exportModel(Model model, File fileOut) throws PrismException + { + try (PrismLog out = new PrismFileLog(fileOut.getPath())) { + exportModel(model, out); + } + } + // Utility functions /** From a20ebca8e510d14c0b6316dff56638d5cd4cc01d Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sun, 19 Jan 2025 16:14:56 +0000 Subject: [PATCH 18/44] Code tidying in explicit model export. --- prism/src/explicit/StateModelChecker.java | 77 ++++++++++++----------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/prism/src/explicit/StateModelChecker.java b/prism/src/explicit/StateModelChecker.java index c968b74d91..9229030e0f 100644 --- a/prism/src/explicit/StateModelChecker.java +++ b/prism/src/explicit/StateModelChecker.java @@ -1530,46 +1530,49 @@ public static Map loadLabelsFile(String filename) throws PrismEx */ public void exportModel(Model model, ModelExportTask exportTask) throws PrismException { - File file = exportTask.getFile(); ModelExportOptions exportOptions = exportTask.getExportOptions(); - try (PrismLog out = getPrismLogForFile(file)) { - ModelExporter exporter; - switch (exportOptions.getFormat()) { - case EXPLICIT: - if (exportOptions.getExplicitRows()) { - throw new PrismNotSupportedException("Export in rows format not yet supported by explicit engine"); - } - exporter = new PrismExplicitExporter<>(exportOptions); - break; - case DOT: - exporter = new DotExporter<>(exportOptions); - break; - case DRN: - exporter = new DRNExporter<>(exportOptions); - break; - default: - throw new PrismNotSupportedException("Export " + exportOptions.getFormat().description() + " not supported by explicit engine"); - } - if (exportOptions.getFormat() == ModelExportFormat.DRN) { - // Get reward/label info - List> rewards = new ArrayList<>(); - for (int r = 0; r < rewardGen.getNumRewardStructs(); r++) { - rewards.add(constructRewards(model, r)); - } - List labelNames = new ArrayList<>(); - if (exportTask.initLabelIncluded()) { - labelNames.add("init"); - } - if (exportTask.deadlockLabelIncluded()) { - labelNames.add("deadlock"); + // Build an exporter of the required type + ModelExporter exporter; + switch (exportOptions.getFormat()) { + case EXPLICIT: + if (exportOptions.getExplicitRows()) { + throw new PrismNotSupportedException("Export in rows format not yet supported by explicit engine"); } - labelNames.addAll(modelInfo.getLabelNames()); - List labelStates = checkLabels(model, labelNames); - // Add to exporter - exporter.addRewards(rewards, rewardGen.getRewardStructNames()); - exporter.setRewardEvaluator((Evaluator) rewardGen.getRewardEvaluator()); - exporter.addLabels(labelStates, labelNames); + exporter = new PrismExplicitExporter<>(exportOptions); + break; + case DOT: + exporter = new DotExporter<>(exportOptions); + break; + case DRN: + exporter = new DRNExporter<>(exportOptions); + break; + default: + throw new PrismNotSupportedException("Export " + exportOptions.getFormat().description() + " not supported by explicit engine"); + } + File file = exportTask.getFile(); + // If needed, add label/reward info + if (exportOptions.getFormat() == ModelExportFormat.DRN) { + // Get rewards/labels + List> rewards = new ArrayList<>(); + for (int r = 0; r < rewardGen.getNumRewardStructs(); r++) { + rewards.add(constructRewards(model, r)); + } + List labelNames = new ArrayList<>(); + if (exportTask.initLabelIncluded()) { + labelNames.add("init"); } + if (exportTask.deadlockLabelIncluded()) { + labelNames.add("deadlock"); + } + labelNames.addAll(modelInfo.getLabelNames()); + List labelStates = checkLabels(model, labelNames); + // Add to exporter + exporter.addRewards(rewards, rewardGen.getRewardStructNames()); + exporter.setRewardEvaluator((Evaluator) rewardGen.getRewardEvaluator()); + exporter.addLabels(labelStates, labelNames); + } + // Export to log + try (PrismLog out = getPrismLogForFile(file)) { exporter.exportModel(model, out); } } From 4583dd52f1bc394b65d08b2d174d645ef0e9c57c Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Wed, 16 Apr 2025 08:57:38 +0100 Subject: [PATCH 19/44] PRISM explicit export transition reward fix (order matches transitions). --- prism/src/io/PrismExplicitExporter.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/prism/src/io/PrismExplicitExporter.java b/prism/src/io/PrismExplicitExporter.java index 6baaf7001e..6958fd6a34 100644 --- a/prism/src/io/PrismExplicitExporter.java +++ b/prism/src/io/PrismExplicitExporter.java @@ -221,9 +221,10 @@ public void exportTransRewards(Model model, Rewards rewards, Strin for (int j = 0; j < numChoices; j++) { Value d = rewards.getTransitionReward(s, j); if (!evalRewards.isZero(d)) { - for (SuccessorsIterator succ = ((NondetModel) model).getSuccessors(s, j); succ.hasNext();) { - int s2 = succ.nextInt(); - out.println(s + " " + j + " " + s2 + " " + formatValue(d, evalRewards)); + // For nondet models, the choice reward is displayed by all transitions + // (which we sort, in order to match the output for the model) + for (Transition transition : getSortedTransitionsIterator(model, s, j, modelExportOptions.getShowActions())) { + out.println(s + " " + j + " " + transition.target + " " + formatValue(d, evalRewards)); } } } From b151cd3017b44eac78655fa3a9eff01eafc4cc91 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sun, 20 Apr 2025 11:07:20 +0100 Subject: [PATCH 20/44] Additional querying of action info for models. All models support getActions(), providing a list of all action labels used, including null for unlabelled choices/transitions. For now, inefficient defaults are available that recompute this information by checking all choices/transitions. explicit.DTMC provides getActionsIterator(s) to get the actions for all transitions from states s. Previously, these were only available in combination with the transitions. The indices of actions (into getActions()) can also be queried: getActionIndex(s, i) for NondetModel and its subclasses and getActionIndicesIterator(s) for DTMC and its subclasses. For now, explicit engine model storage continues to store actions directly as objects and this is looked up as needed. --- prism/src/explicit/DTMC.java | 61 +++++++++++++++++-- .../explicit/DTMCFromMDPAndMDStrategy.java | 17 ++++-- .../DTMCFromMDPMemorylessAdversary.java | 16 +++-- prism/src/explicit/DTMCSimple.java | 22 +++++++ prism/src/explicit/DTMCSparse.java | 23 +++++++ prism/src/explicit/NondetModel.java | 31 +++++++++- prism/src/prism/Model.java | 43 +++++++++++++ prism/src/symbolic/model/ModelSymbolic.java | 11 ++++ 8 files changed, 210 insertions(+), 14 deletions(-) diff --git a/prism/src/explicit/DTMC.java b/prism/src/explicit/DTMC.java index 8e0e145e09..55bccd0232 100644 --- a/prism/src/explicit/DTMC.java +++ b/prism/src/explicit/DTMC.java @@ -29,10 +29,13 @@ import java.io.FileWriter; import java.io.IOException; import java.util.AbstractMap; +import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; +import java.util.Collections; import java.util.Iterator; -import java.util.Map; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Map.Entry; import java.util.PrimitiveIterator; import java.util.PrimitiveIterator.OfInt; @@ -43,12 +46,10 @@ import common.iterable.FunctionalIterator; import common.iterable.PrimitiveIterable; import common.iterable.Reducible; -import explicit.graphviz.Decorator; import explicit.rewards.MCRewards; import prism.ModelType; import prism.Pair; import prism.PrismException; -import prism.PrismLog; /** * Interface for classes that provide (read) access to an explicit-state DTMC. @@ -92,7 +93,23 @@ default void exportToPrismLanguage(final String filename, int precision) throws } // Accessors - + + @Override + default List getActions() + { + // Default implementation for DTMC: find unique actions across all transitions + // This should be cached/optimised if action indices are looked up frequently + LinkedHashSet actions = new LinkedHashSet<>(); + int numStates = getNumStates(); + for (int s = 0; s < numStates; s++) { + for (Iterator>> transitions = getTransitionsAndActionsIterator(s); transitions.hasNext();) { + final Entry> transition = transitions.next(); + actions.add(transition.getValue().second); + } + } + return new ArrayList<>(actions); + } + /** * Get an iterator over the transitions from state s. */ @@ -108,6 +125,42 @@ public default Iterator>> getTransitionsAndAc return Reducible.extend(transitions).map(transition -> attachAction(transition, null)); } + /** + * Get an iterator over the actions attached to transitions from state s. + */ + public default Iterator getActionsIterator(int s) + { + // Default implementation just assumes null actions + return Collections.nCopies(getNumTransitions(s), null).iterator(); + } + + /** + * Get an iterator over the indices of actions attached to transitions from state s. + * Indices are into the list given by {@link #getActions()}, + * which includes null if there are unlabelled choices, + * so this method should always return values >= 0. + */ + public default PrimitiveIterator.OfInt getActionIndicesIterator(int s) + { + // Default implementation looks up indices from getActionsIterator + return new PrimitiveIterator.OfInt() + { + private final Iterator iter = getActionsIterator(s); + + @Override + public boolean hasNext() + { + return iter.hasNext(); + } + + @Override + public int nextInt() + { + return actionIndex(iter.next()); + } + }; + } + /** * Get an iterator over the transitions from state {@code s}, * after mapping probability values using the provided function. diff --git a/prism/src/explicit/DTMCFromMDPAndMDStrategy.java b/prism/src/explicit/DTMCFromMDPAndMDStrategy.java index fa48640e4a..11e21749ee 100644 --- a/prism/src/explicit/DTMCFromMDPAndMDStrategy.java +++ b/prism/src/explicit/DTMCFromMDPAndMDStrategy.java @@ -150,9 +150,7 @@ public Iterator> getTransitionsIterator(int s) if (strat.isChoiceDefined(s)) { return mdp.getTransitionsIterator(s, strat.getChoiceIndex(s)); } else { - // Empty iterator - Map empty = Collections.emptyMap(); - return empty.entrySet().iterator(); + return Collections.emptyIterator(); } } @@ -162,8 +160,17 @@ public Iterator>> getTransitionsAndActionsIte final Iterator> transitions = mdp.getTransitionsIterator(s, strat.getChoiceIndex(s)); return Reducible.extend(transitions).map(transition -> DTMC.attachAction(transition, mdp.getAction(s, strat.getChoiceIndex(s)))); } else { - // Empty iterator - return Collections.>>emptyIterator(); + return Collections.emptyIterator(); + } + } + + @Override + public Iterator getActionsIterator(int s) + { + if (strat.isChoiceDefined(s)) { + return Collections.nCopies(getNumTransitions(s), mdp.getAction(s, strat.getChoiceIndex(s))).iterator(); + } else { + return Collections.emptyIterator(); } } diff --git a/prism/src/explicit/DTMCFromMDPMemorylessAdversary.java b/prism/src/explicit/DTMCFromMDPMemorylessAdversary.java index 34400c988c..8c353e8213 100644 --- a/prism/src/explicit/DTMCFromMDPMemorylessAdversary.java +++ b/prism/src/explicit/DTMCFromMDPMemorylessAdversary.java @@ -153,8 +153,7 @@ public Iterator> getTransitionsIterator(int s) if (adv[s] >= 0) { return mdp.getTransitionsIterator(s, adv[s]); } else { - // Empty iterator - return Collections.>emptyIterator(); + return Collections.emptyIterator(); } } @@ -165,8 +164,17 @@ public Iterator>> getTransitionsAndActionsIte final Iterator> transitions = mdp.getTransitionsIterator(s, adv[s]); return Reducible.extend(transitions).map(transition -> DTMC.attachAction(transition, mdp.getAction(s, adv[s]))); } else { - // Empty iterator - return Collections.>>emptyIterator(); + return Collections.emptyIterator(); + } + } + + @Override + public Iterator getActionsIterator(int s) + { + if (adv[s] >= 0) { + return Collections.nCopies(getNumTransitions(s), mdp.getAction(s, adv[s])).iterator(); + } else { + return Collections.emptyIterator(); } } diff --git a/prism/src/explicit/DTMCSimple.java b/prism/src/explicit/DTMCSimple.java index bd5dc672ae..b8e5f5aa2e 100644 --- a/prism/src/explicit/DTMCSimple.java +++ b/prism/src/explicit/DTMCSimple.java @@ -366,6 +366,28 @@ public boolean hasNext() }; } + @Override + public Iterator getActionsIterator(int s) + { + // Create iterator (no removal of duplicates) + return new Iterator<>() { + private final int n = succ.get(s).size(); + private int i = 0; + + @Override + public Object next() + { + return actions.getAction(s, i++); + } + + @Override + public boolean hasNext() + { + return i < n; + } + }; + } + // Accessors (other) /** diff --git a/prism/src/explicit/DTMCSparse.java b/prism/src/explicit/DTMCSparse.java index bffebbfa2f..62593a98e2 100644 --- a/prism/src/explicit/DTMCSparse.java +++ b/prism/src/explicit/DTMCSparse.java @@ -348,6 +348,29 @@ public Entry> next() }; } + @Override + public Iterator getActionsIterator(int s) + { + return new Iterator<>() + { + final int start = rows[s]; + int col = start; + final int end = rows[s + 1]; + + @Override + public boolean hasNext() + { + return col < end; + } + + @Override + public Object next() + { + return actions == null ? null : actions[col++]; + } + }; + } + @Override public boolean prob0step(final int s, final BitSet u) { diff --git a/prism/src/explicit/NondetModel.java b/prism/src/explicit/NondetModel.java index 55196a05c2..04edcac01a 100644 --- a/prism/src/explicit/NondetModel.java +++ b/prism/src/explicit/NondetModel.java @@ -31,6 +31,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.function.IntPredicate; @@ -47,6 +48,22 @@ public interface NondetModel extends Model { // Accessors + @Override + default List getActions() + { + // Default implementation for NondetModel: find unique actions across all choices + // This should be cached/optimised if action indices are looked up frequently + LinkedHashSet actions = new LinkedHashSet<>(); + int numStates = getNumStates(); + for (int s = 0; s < numStates; s++) { + int numChoices = getNumChoices(s); + for (int i = 0; i < numChoices; i++) { + actions.add(getAction(s, i)); + } + } + return new ArrayList<>(actions); + } + /** * Get the number of nondeterministic choices in state s. */ @@ -77,10 +94,22 @@ default int getNumChoices() } /** - * Get the action label (if any) for choice {@code i} of state {@code s}. + * Get the action label for choice {@code i} of state {@code s}. + * The action is null for an unlabelled choice. */ public Object getAction(int s, int i); + /** + * Get the index of the action label for choice {@code i} of state {@code s}. + * Indices are into the list given by {@link #getActions()}, + * which includes null if there are unlabelled choices, + * so this method should always return a value >= 0. + */ + public default int getActionIndex(int s, int i) + { + return actionIndex(getAction(s, i)); + } + /** * Get a list of the actions labelling the choices of state {@code s}. */ diff --git a/prism/src/prism/Model.java b/prism/src/prism/Model.java index ec255c4386..5dbb453968 100644 --- a/prism/src/prism/Model.java +++ b/prism/src/prism/Model.java @@ -26,6 +26,9 @@ package prism; +import java.util.List; +import java.util.stream.Collectors; + /** * Interface for classes that store models. * This is generic, where probabilities/rates/etc. are of type {@code Value}. @@ -39,6 +42,46 @@ public interface Model */ ModelType getModelType(); + /** + * Get a list of the action labels attached to choices/transitions. + * This can be a superset of the action labels that are actually used in the model. + * Action labels can be any Object, but will often be treated as a string, + * so should at least have a meaningful toString() method implemented. + * Absence of an action label is denoted by null, + * and null is also included in this list if there are unlabelled choices/transitions. + */ + List getActions(); + + /** + * Get strings for the action labels attached to choices/transitions. + */ + default List getActionStrings() + { + // By default, just apply toString to getActions() + return getActions().stream().map(Model::actionString).collect(Collectors.toList()); + } + + /** + * Default conversion of an action label to a string + * ({@code toString()} or {@code ""} for {@code null}). + */ + static String actionString(Object action) + { + return action == null ? "" : action.toString(); + } + + /** + * Get the index of an action label. + * Indices are into the list given by {@link #getActions()}, + * which includes null if there are unlabelled choices, + * so this method should always return a value >= 0 for a valid action. + */ + default int actionIndex(Object action) + { + // Default (inefficient) implementation: just look up in getActions() + return getActions().indexOf(action); + } + /** * Get the number of states. */ diff --git a/prism/src/symbolic/model/ModelSymbolic.java b/prism/src/symbolic/model/ModelSymbolic.java index 15c039d781..9f1c418fa9 100644 --- a/prism/src/symbolic/model/ModelSymbolic.java +++ b/prism/src/symbolic/model/ModelSymbolic.java @@ -42,6 +42,7 @@ import java.io.File; import java.io.FileNotFoundException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -423,6 +424,16 @@ public void filterReachableStates() // Accessors (for prism.Model & symbolic.Model interface) + @Override + public List getActions() + { + // Over-approximate and assume that unlabelled actions could occur + ArrayList actions = new ArrayList<>(); + actions.add(null); + actions.addAll(synchs); + return actions; + } + @Override public int getNumStates() { From de5f233190ed42c5f37db79c745bb1b71b172530 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Mon, 21 Apr 2025 19:19:20 +0100 Subject: [PATCH 21/44] Update action name storage in explicit models. New method findActionsUsed() in prism.Model interface, used in default implementation of getActions(). The execution of this can be optimised by implementing onlyNullActionUsed() and when all actions are null. New class ActionList to encapsulate info about action names. This is stored in model base classes, notably ModelExplicit, meaning that looking up action indices is much faster. For now, the usage of ActionList is that it is initialised to be empty, and the actual list is computed lazily when needed. Changes to the model that affect actions should call the method markNeedsRecomputing() in case the list has been computed. --- prism/src/explicit/ChoiceActionsSimple.java | 10 + prism/src/explicit/DTMC.java | 8 +- prism/src/explicit/DTMCSimple.java | 9 + prism/src/explicit/DTMCSparse.java | 14 ++ prism/src/explicit/LTSSimple.java | 12 +- prism/src/explicit/MDPSimple.java | 13 +- prism/src/explicit/MDPSparse.java | 15 +- prism/src/explicit/ModelExplicit.java | 113 +++++++--- prism/src/explicit/NondetModel.java | 8 +- prism/src/explicit/STPGAbstrSimple.java | 3 + prism/src/explicit/STPGSimple.java | 1 + prism/src/explicit/SubNondetModel.java | 24 ++- prism/src/explicit/modelviews/ModelView.java | 25 ++- prism/src/prism/ActionList.java | 204 +++++++++++++++++++ prism/src/prism/ActionListOwner.java | 38 ++++ prism/src/prism/Model.java | 24 ++- prism/src/symbolic/model/ModelSymbolic.java | 7 + 17 files changed, 479 insertions(+), 49 deletions(-) create mode 100644 prism/src/prism/ActionList.java create mode 100644 prism/src/prism/ActionListOwner.java diff --git a/prism/src/explicit/ChoiceActionsSimple.java b/prism/src/explicit/ChoiceActionsSimple.java index 24bc7f50cb..0ed51eb5bd 100644 --- a/prism/src/explicit/ChoiceActionsSimple.java +++ b/prism/src/explicit/ChoiceActionsSimple.java @@ -154,6 +154,16 @@ public void setAction(int s, int i, Object action) // Accessors + /** + * Do all choices/transitions have empty (null) action labels? + */ + public boolean onlyNullActionUsed() + { + // NB: it's still possible that action storage has been created but has ended up all null + // We ignore this possibility + return actions == null; + } + /** * Get the action label for choice {@code i} of state {@code s}. */ diff --git a/prism/src/explicit/DTMC.java b/prism/src/explicit/DTMC.java index 55bccd0232..6937f9eaf9 100644 --- a/prism/src/explicit/DTMC.java +++ b/prism/src/explicit/DTMC.java @@ -95,10 +95,12 @@ default void exportToPrismLanguage(final String filename, int precision) throws // Accessors @Override - default List getActions() + default List findActionsUsed() { - // Default implementation for DTMC: find unique actions across all transitions - // This should be cached/optimised if action indices are looked up frequently + // Find unique actions across all transitions + if (onlyNullActionUsed()) { + return Collections.singletonList(null); + } LinkedHashSet actions = new LinkedHashSet<>(); int numStates = getNumStates(); for (int s = 0; s < numStates; s++) { diff --git a/prism/src/explicit/DTMCSimple.java b/prism/src/explicit/DTMCSimple.java index b8e5f5aa2e..630ac28cf5 100644 --- a/prism/src/explicit/DTMCSimple.java +++ b/prism/src/explicit/DTMCSimple.java @@ -164,6 +164,7 @@ public void clearState(int i) succ.get(i).clear(); trans.get(i).clear(); actions.clearState(i); + actionList.markNeedsRecomputing(); } @Override @@ -224,6 +225,7 @@ public void setProbability(int i, int j, Value prob, Object action) iSucc.add(j); iTrans.add(prob); actions.setAction(i, numSucc, action); + actionList.markNeedsRecomputing(); } /** @@ -256,10 +258,17 @@ public void addToProbability(int i, int j, Value prob, Object action) iSucc.add(j); iTrans.add(prob); actions.setAction(i, numSucc, action); + actionList.markNeedsRecomputing(); } // Accessors (for Model) + @Override + public boolean onlyNullActionUsed() + { + return actions.onlyNullActionUsed(); + } + @Override public int getNumTransitions(int s) { diff --git a/prism/src/explicit/DTMCSparse.java b/prism/src/explicit/DTMCSparse.java index 62593a98e2..1ffd4c097b 100644 --- a/prism/src/explicit/DTMCSparse.java +++ b/prism/src/explicit/DTMCSparse.java @@ -42,6 +42,7 @@ import explicit.rewards.MCRewards; import io.ExplicitModelImporter; import io.IOUtils; +import prism.ActionListOwner; import prism.Pair; import prism.PrismException; import prism.PrismNotSupportedException; @@ -67,6 +68,9 @@ public class DTMCSparse extends DTMCExplicit public DTMCSparse(final DTMC dtmc) { initialise(dtmc.getNumStates()); + if (dtmc instanceof ActionListOwner) { + actionList.copyFrom(((ActionListOwner) dtmc).getActionList()); + } for (Integer state : dtmc.getDeadlockStates()) { deadlocks.add(state); } @@ -110,6 +114,9 @@ public DTMCSparse(final DTMC dtmc) public DTMCSparse(final DTMC dtmc, int[] permut) { initialise(dtmc.getNumStates()); + if (dtmc instanceof ActionListOwner) { + actionList.copyFrom(((ActionListOwner) dtmc).getActionList()); + } for (Integer state : dtmc.getDeadlockStates()) { deadlocks.add(permut[state]); } @@ -162,6 +169,12 @@ public DTMCSparse() //--- Model --- + @Override + public boolean onlyNullActionUsed() + { + return actions == null; + } + @Override public int getNumTransitions() { @@ -250,6 +263,7 @@ public void checkForDeadlocks(BitSet except) throws PrismException public void buildFromExplicitImport(ExplicitModelImporter modelImporter) throws PrismException { initialise(modelImporter.getNumStates()); + actionList.markNeedsRecomputing(); int numTransitions = modelImporter.getNumTransitions(); rows = new int[numStates + 1]; columns = new int[numTransitions]; diff --git a/prism/src/explicit/LTSSimple.java b/prism/src/explicit/LTSSimple.java index 9af2baaa99..0c29940156 100644 --- a/prism/src/explicit/LTSSimple.java +++ b/prism/src/explicit/LTSSimple.java @@ -145,6 +145,7 @@ public void clearState(int s) numTransitions -= list.size(); list.clear(); actions.clearState(s); + actionList.markNeedsRecomputing(); } @Override @@ -173,6 +174,7 @@ public void addTransition(int s, int t) // We don't care if a transition from s to t already exists trans.get(s).add(t); numTransitions++; + actionList.markNeedsRecomputing(); } /** @@ -185,6 +187,7 @@ public void addActionLabelledTransition(int s, int t, Object action) trans.get(s).add(t); actions.setAction(s, trans.get(s).size() - 1, action); numTransitions++; + actionList.markNeedsRecomputing(); } /** @@ -194,10 +197,17 @@ public void addActionLabelledTransition(int s, int t, Object action) public void setAction(int s, int i, Object action) { actions.setAction(s, i, action); + actionList.markNeedsRecomputing(); } // Accessors (for Model) - + + @Override + public boolean onlyNullActionUsed() + { + return actions.onlyNullActionUsed(); + } + @Override public int getNumTransitions() { diff --git a/prism/src/explicit/MDPSimple.java b/prism/src/explicit/MDPSimple.java index 7b1affb91b..b5ba3364e4 100644 --- a/prism/src/explicit/MDPSimple.java +++ b/prism/src/explicit/MDPSimple.java @@ -110,8 +110,9 @@ public MDPSimple(DTMCSimple dtmc) { this(dtmc.getNumStates()); copyFrom(dtmc); + // NB: actions (on transitions) from the DTMC are not copied to (choices of) the MDP + actionList.clear(); for (int s = 0; s < numStates; s++) { - // Note: DTMCSimple has no actions so can ignore these addChoice(s, new Distribution(dtmc.getTransitions(s))); } } @@ -226,6 +227,7 @@ public void clearState(int s) maxNumDistrsOk = false; trans.get(s).clear(); actions.clearState(s); + actionList.markNeedsRecomputing(); } @Override @@ -296,6 +298,7 @@ public int addChoice(int s, Distribution distr) } set = trans.get(s); set.add(distr); + actionList.markNeedsRecomputing(); // Update stats numDistrs++; maxNumDistrs = Math.max(maxNumDistrs, set.size()); @@ -326,6 +329,7 @@ public int addActionLabelledChoice(int s, Distribution distr, Object acti set.add(distr); // Set action actions.setAction(s, set.size() - 1, action); + actionList.markNeedsRecomputing(); // Update stats numDistrs++; maxNumDistrs = Math.max(maxNumDistrs, set.size()); @@ -342,10 +346,17 @@ public int addActionLabelledChoice(int s, Distribution distr, Object acti public void setAction(int s, int i, Object o) { actions.setAction(s, i, o); + actionList.markNeedsRecomputing(); } // Accessors (for Model) + @Override + public boolean onlyNullActionUsed() + { + return actions.onlyNullActionUsed(); + } + @Override public int getNumTransitions() { diff --git a/prism/src/explicit/MDPSparse.java b/prism/src/explicit/MDPSparse.java index 5cd1d0116e..e5c34c1054 100644 --- a/prism/src/explicit/MDPSparse.java +++ b/prism/src/explicit/MDPSparse.java @@ -42,6 +42,7 @@ import io.ExplicitModelImporter; import io.IOUtils; import parser.State; +import prism.ActionListOwner; import prism.PrismException; import prism.PrismUtils; @@ -95,7 +96,9 @@ public MDPSparse(final MDP mdp) public MDPSparse(final MDP mdp, boolean sort) { initialise(mdp.getNumStates()); - + if (mdp instanceof ActionListOwner) { + actionList.copyFrom(((ActionListOwner) mdp).getActionList()); + } setStatesList(mdp.getStatesList()); setConstantValues(mdp.getConstantValues()); for (String label : mdp.getLabels()) { @@ -311,6 +314,9 @@ public MDPSparse(MDPSimple mdp, boolean sort, int permut[]) public MDPSparse(MDP mdp, List states, List> actions) { initialise(states.size()); + if (mdp instanceof ActionListOwner) { + actionList.copyFrom(((ActionListOwner) mdp).getActionList()); + } for (int in : mdp.getInitialStates()) { addInitialState(in); } @@ -421,6 +427,7 @@ public void accept(int s, int i, int s2, Double d, Object a) rowStarts[numStates] = numDistrs; choiceStarts[numDistrs] = numTransitions; modelImporter.extractMDPTransitions(cons); + actionList.markNeedsRecomputing(); // Compute maxNumDistrs maxNumDistrs = 0; for (int s = 0; s < numStates; s++) { @@ -430,6 +437,12 @@ public void accept(int s, int i, int s2, Double d, Object a) // Accessors (for Model) + @Override + public boolean onlyNullActionUsed() + { + return actions == null; + } + @Override public int getNumTransitions() { diff --git a/prism/src/explicit/ModelExplicit.java b/prism/src/explicit/ModelExplicit.java index 4ea51d0e3c..e0fdc184ea 100644 --- a/prism/src/explicit/ModelExplicit.java +++ b/prism/src/explicit/ModelExplicit.java @@ -40,6 +40,8 @@ import parser.State; import parser.Values; import parser.VarList; +import prism.ActionList; +import prism.ActionListOwner; import prism.Evaluator; import prism.ModelType; import prism.PrismException; @@ -47,35 +49,55 @@ /** * Base class for explicit-state model representations. */ -public abstract class ModelExplicit implements Model +public abstract class ModelExplicit implements Model, ActionListOwner { - /** Evaluator for manipulating values in the model (of type {@code Value}) */ + /** + * Evaluator for manipulating values in the model (of type {@code Value}) + */ @SuppressWarnings("unchecked") protected Evaluator eval = (Evaluator) Evaluator.forDouble(); - + // Basic model information - /** Number of states */ + /** + * Action list + */ + protected ActionList actionList; + /** + * Number of states + */ protected int numStates; - /** Which states are initial states */ + /** + * Which states are initial states + */ protected List initialStates; // TODO: should be a (linkedhash?) set really - /** States that are/were deadlocks. Where requested and where appropriate (DTMCs/MDPs), - * these states may have been fixed at build time by adding self-loops. */ + /** + * States that are/were deadlocks. Where requested and where appropriate (DTMCs/MDPs), + * these states may have been fixed at build time by adding self-loops. + */ protected TreeSet deadlocks; // Additional, optional information associated with the model - /** (Optionally) information about the states of this model, - * i.e. the State object corresponding to each state index. */ + /** + * (Optionally) information about the states of this model, + * i.e. the State object corresponding to each state index. + */ protected List statesList; - /** (Optionally) a list of values for constants associated with this model. */ + /** + * (Optionally) a list of values for constants associated with this model. + */ protected Values constantValues; - /** (Optionally) the list of variables */ + /** + * (Optionally) the list of variables + */ protected VarList varList; - /** (Optionally) some labels (atomic propositions) associated with the model, - * represented as a String->BitSet mapping from their names to the states that satisfy them. */ + /** + * (Optionally) some labels (atomic propositions) associated with the model, + * represented as a String->BitSet mapping from their names to the states that satisfy them. + */ protected Map labels = new TreeMap(); - + /** * (Optionally) the stored predecessor relation. Becomes inaccurate after the model is changed! */ @@ -90,7 +112,7 @@ public void setEvaluator(Evaluator eval) { this.eval = eval; } - + /** * Copy data from another Model (used by superclass copy constructors). * Assumes that this has already been initialise()ed. @@ -98,6 +120,9 @@ public void setEvaluator(Evaluator eval) public void copyFrom(Model model) { setEvaluator((Evaluator) model.getEvaluator()); + if (model instanceof ActionListOwner) { + actionList.copyFrom(((ActionListOwner) model).getActionList()); + } numStates = model.getNumStates(); for (int in : model.getInitialStates()) { addInitialState(in); @@ -122,6 +147,9 @@ public void copyFrom(Model model) public void copyFrom(Model model, int permut[]) { setEvaluator(model.getEvaluator()); + if (model instanceof ActionListOwner) { + actionList.copyFrom(((ActionListOwner) model).getActionList()); + } numStates = model.getNumStates(); for (int in : model.getInitialStates()) { addInitialState(permut[in]); @@ -142,6 +170,7 @@ public void copyFrom(Model model, int permut[]) */ public void initialise(int numStates) { + actionList = new ActionList(this::findActionsUsed); this.numStates = numStates; initialStates = new ArrayList(); deadlocks = new TreeSet(); @@ -183,7 +212,7 @@ public void addDeadlockState(int i) public void buildFromExplicitImport(ExplicitModelImporter modelImporter) throws PrismException { // Not implemented by default - throw new PrismException("Explicit model not yet supported for this model"); + throw new PrismException("Explicit model import not yet supported for this model"); } /** @@ -225,8 +254,9 @@ public void setVarList(VarList varList) /** * Adds a label and the set the states that satisfy it. * Any existing label with the same name is overwritten. - * @param name The name of the label - * @param states The states that satisfy the label + * + * @param name The name of the label + * @param states The states that satisfy the label */ public void addLabel(String name, BitSet states) { @@ -244,8 +274,8 @@ public void addLabel(String name, BitSet states) * Note that a stored label takes precedence over the on-the-fly calculation * of an ExpressionLabel, cf. {@link explicit.StateModelChecker#checkExpressionLabel} * - * @param prefix the prefix for the unique label - * @param labelStates the BitSet with the state set for the label + * @param prefix the prefix for the unique label + * @param labelStates the BitSet with the state set for the label * @param definedLabelNames set of names (optional, may be {@code null}) to check for existing labels * @return the generated unique label */ @@ -265,7 +295,7 @@ public String addUniqueLabel(String prefix, BitSet labelStates, Set defi } // prepare next label to try - label = prefix+"_"+i; + label = prefix + "_" + i; if (i == Integer.MAX_VALUE) throw new UnsupportedOperationException("Integer overflow trying to add unique label"); @@ -283,7 +313,29 @@ public Evaluator getEvaluator() { return eval; } - + + @Override + public ActionList getActionList() + { + return actionList; + } + + @Override + public List getActions() + { + return actionList.getActions(); + } + + @Override + public int actionIndex(Object action) + { + int actionIndex = actionList.actionIndex(action); + if (actionIndex == -1) { + throw new RuntimeException("Action storage error: " + action + " not found"); + } + return actionIndex; + } + @Override public int getNumStates() { @@ -360,7 +412,7 @@ public Values getConstantValues() { return constantValues; } - + @Override public VarList getVarList() { @@ -384,13 +436,13 @@ public Set getLabels() { return labels.keySet(); } - + @Override public Map getLabelToStatesMap() { return labels; } - + @Override public void checkForDeadlocks() throws PrismException { @@ -413,13 +465,15 @@ public boolean equals(Object o) return true; } -@Override - public boolean hasStoredPredecessorRelation() { + @Override + public boolean hasStoredPredecessorRelation() + { return (predecessorRelation != null); } @Override - public PredecessorRelation getPredecessorRelation(prism.PrismComponent parent, boolean storeIfNew) { + public PredecessorRelation getPredecessorRelation(prism.PrismComponent parent, boolean storeIfNew) + { if (predecessorRelation != null) { return predecessorRelation; } @@ -433,7 +487,8 @@ public PredecessorRelation getPredecessorRelation(prism.PrismComponent parent, b } @Override - public void clearPredecessorRelation() { + public void clearPredecessorRelation() + { predecessorRelation = null; } } diff --git a/prism/src/explicit/NondetModel.java b/prism/src/explicit/NondetModel.java index 04edcac01a..15572aef75 100644 --- a/prism/src/explicit/NondetModel.java +++ b/prism/src/explicit/NondetModel.java @@ -49,10 +49,12 @@ public interface NondetModel extends Model // Accessors @Override - default List getActions() + default List findActionsUsed() { - // Default implementation for NondetModel: find unique actions across all choices - // This should be cached/optimised if action indices are looked up frequently + // Find unique actions across all choices + if (onlyNullActionUsed()) { + return Collections.singletonList(null); + } LinkedHashSet actions = new LinkedHashSet<>(); int numStates = getNumStates(); for (int s = 0; s < numStates; s++) { diff --git a/prism/src/explicit/STPGAbstrSimple.java b/prism/src/explicit/STPGAbstrSimple.java index 2e060241f6..dfdce9ce83 100644 --- a/prism/src/explicit/STPGAbstrSimple.java +++ b/prism/src/explicit/STPGAbstrSimple.java @@ -144,6 +144,7 @@ public void clearState(int i) //TODO: recompute maxNumDistrs // Remove all distribution sets trans.set(i, new ArrayList<>(0)); + actionList.markNeedsRecomputing(); } @Override @@ -234,6 +235,7 @@ public void buildFromPrismExplicit(String filename) throws PrismException addDistributionSet(iLast, distrs); // Close file in.close(); + actionList.markNeedsRecomputing(); } catch (IOException e) { System.out.println(e); System.exit(1); @@ -282,6 +284,7 @@ public int addDistributionSet(int s, DistributionSet newSet) maxNumDistrs = Math.max(maxNumDistrs, newSet.size()); for (Distribution distr : newSet) numTransitions += distr.size(); + actionList.markNeedsRecomputing(); return set.size() - 1; } diff --git a/prism/src/explicit/STPGSimple.java b/prism/src/explicit/STPGSimple.java index 9ac25468f6..f37e5275e2 100644 --- a/prism/src/explicit/STPGSimple.java +++ b/prism/src/explicit/STPGSimple.java @@ -96,6 +96,7 @@ public void clearState(int s) { super.clearState(s); stateOwners.clearState(s); + actionList.markNeedsRecomputing(); } @Override diff --git a/prism/src/explicit/SubNondetModel.java b/prism/src/explicit/SubNondetModel.java index 621d303361..b09a29453d 100644 --- a/prism/src/explicit/SubNondetModel.java +++ b/prism/src/explicit/SubNondetModel.java @@ -39,6 +39,8 @@ import parser.State; import parser.Values; import parser.VarList; +import prism.ActionList; +import prism.ActionListOwner; import prism.ModelType; import prism.PrismException; import prism.PrismLog; @@ -49,10 +51,11 @@ * used to translate between state ids for model and sub-model. Created sub-model will have new * state numbering from 0 to number of states in the sub model. */ -public class SubNondetModel implements NondetModel +public class SubNondetModel implements NondetModel, ActionListOwner { private NondetModel model = null; + private ActionList actionList; private BitSet states = null; private Map actions = null; private BitSet initialStates = null; @@ -73,6 +76,7 @@ public class SubNondetModel implements NondetModel public SubNondetModel(NondetModel model, BitSet states, Map actions, BitSet initialStates) { this.model = model; + actionList = new ActionList(this::findActionsUsed); this.states = states; this.actions = actions; this.initialStates = initialStates; @@ -87,6 +91,24 @@ public ModelType getModelType() return model.getModelType(); } + @Override + public ActionList getActionList() + { + return actionList; + } + + @Override + public List getActions() + { + return actionList.getActions(); + } + + @Override + public int actionIndex(Object action) + { + return actionList.actionIndex(action); + } + @Override public int getNumStates() { diff --git a/prism/src/explicit/modelviews/ModelView.java b/prism/src/explicit/modelviews/ModelView.java index 8a733838a4..a59dde678d 100644 --- a/prism/src/explicit/modelviews/ModelView.java +++ b/prism/src/explicit/modelviews/ModelView.java @@ -40,6 +40,8 @@ import explicit.StateValues; import parser.State; import parser.VarList; +import prism.ActionList; +import prism.ActionListOwner; import prism.Prism; import prism.PrismComponent; import prism.PrismException; @@ -49,14 +51,13 @@ * Base class for an DTMCView or MDPView, * handling common tasks. */ -public abstract class ModelView implements Model +public abstract class ModelView implements Model, ActionListOwner { + protected ActionList actionList = new ActionList(this::findActionsUsed); protected BitSet deadlockStates = new BitSet(); protected boolean fixedDeadlocks = false; protected PredecessorRelation predecessorRelation; - - public ModelView() { } @@ -67,9 +68,25 @@ public ModelView(final ModelView model) fixedDeadlocks = model.fixedDeadlocks; } + //--- Model --- + @Override + public ActionList getActionList() + { + return actionList; + } - //--- Model --- + @Override + public List getActions() + { + return actionList.getActions(); + } + + @Override + public int actionIndex(Object action) + { + return actionList.actionIndex(action); + } @Override public int getNumDeadlockStates() diff --git a/prism/src/prism/ActionList.java b/prism/src/prism/ActionList.java new file mode 100644 index 0000000000..6867a0e3e5 --- /dev/null +++ b/prism/src/prism/ActionList.java @@ -0,0 +1,204 @@ +//============================================================================== +// +// Copyright (c) 2025- +// Authors: +// * Dave Parker (University of Oxford) +// +//------------------------------------------------------------------------------ +// +// This file is part of PRISM. +// +// PRISM 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 2 of the License, or +// (at your option) any later version. +// +// PRISM 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 PRISM; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//============================================================================== + +package prism; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +/** + * Class to manage the (ordered) list of action labels associated with a model. + * The list can be obtained via {@link #getActions()} and the index of a given + * action in the list can be found with {@link #actionIndex(Object)}. + *

+ * A list of actions can be provided at the time of creation or, if not, + * the list is created lazily from the {@code newActionSource} provided on creation. + * Call {@link #markNeedsRecomputing()} to indicate that actions in the model + * may have changed and the list therefore needs recomputing. + * Actions can be added manually with {@link #addAction(Object)} + * or {@link #addActions(List)}, which does not change whether + * the list needs recomputing. The full list can be set by calling + * {@link #setActions(List)}, after which it is assumed recomputation is _not_ needed. + * In general, actions are not removed (unless {@link #clear()} is called) + * in order to preserve the order/indexing of actions. + */ +public class ActionList +{ + /** Where to get new actions when the list needs recomputing */ + protected Supplier> newActionSource; + + /** The list of actions */ + protected List actionList; + + /** Map from actions to indices */ + protected Map actionLookup; + + /** Does the list need recomputing? */ + protected boolean needsRecomputing; + + /** + * Construct a (for now empty) new {@link ActionList} + * with a supplier of new actions for when the list needs (re)computing. + * Since the list is empty, it is assumed that it needs recomputing. + */ + public ActionList(Supplier> newActionSource) + { + this.newActionSource = newActionSource; + actionList = new ArrayList<>(); + actionLookup = new HashMap<>(); + // Initially empty and therefore almost certainly incomplete + needsRecomputing = true; + } + + /** + * Construct a new {@link ActionList} from a list of (Object) actions. + * plus a supplier of new actions for when the list needs recomputing. + * For now, it is assumed that the list does _not_ need recomputing. + */ + public ActionList(List actions, Supplier> newActionSource) + { + this(newActionSource); + setActions(actions); + } + + /** + * Copy action info from another ActionList + * (but not the source of new actions) + */ + public void copyFrom(ActionList other) + { + actionList = new ArrayList<>(other.actionList); + actionLookup = new HashMap<>(other.actionLookup); + needsRecomputing = other.needsRecomputing; + } + + /** + * Set the list of actions. + * After this, it is assumed that the list does _not_ need recomputing. + */ + public void setActions(List actions) + { + actionList.clear(); + actionLookup.clear(); + addActions(actions); + needsRecomputing = false; + } + + /** + * Clear the action info. + * Since the list is now empty, it is assumed that it needs recomputing. + */ + public void clear() + { + actionList.clear(); + actionLookup.clear(); + // Empty and therefore almost certainly incomplete + needsRecomputing = true; + } + + /** + * Add an action to the list (if it is not already present) + * and return its index in the list. + */ + public int addAction(Object action) + { + Integer index = actionLookup.get(action); + if (index == null) { + index = actionList.size(); + actionLookup.put(action, index); + actionList.add(action); + } + return index; + } + + /** + * Add actions to the list (any that are not already present). + */ + public void addActions(List actionList) + { + actionList.forEach(this::addAction); + } + + /** + * Mark the action list as needing recomputing. + */ + public void markNeedsRecomputing() + { + needsRecomputing = true; + } + + /** + * Recompute the list of actions, from the new action source. + * Existing actions and indices are kept. + */ + private void recompute() + { + newActionSource.get().forEach(this::addAction); + needsRecomputing = false; + } + + /** + * Get the list of actions, recomputing and caching if needed. + */ + public List getActions() + { + if (needsRecomputing) { + recompute(); + } + return actionList; + } + + /** + * Get the index (in this list) of an action label. + * Returns -1 if the list is not found. + */ + public int actionIndex(Object action) + { + if (needsRecomputing) { + recompute(); + } + Integer index = actionLookup.get(action); + return index == null ? -1 : index; + } + + /** + * Default conversion of an action label to a string + * ({@code toString()} or {@code ""} for {@code null}). + */ + static String actionString(Object action) + { + return action == null ? "" : action.toString(); + } + + @Override + public String toString() + { + return actionList.toString(); + } +} diff --git a/prism/src/prism/ActionListOwner.java b/prism/src/prism/ActionListOwner.java new file mode 100644 index 0000000000..21250ccab5 --- /dev/null +++ b/prism/src/prism/ActionListOwner.java @@ -0,0 +1,38 @@ +//============================================================================== +// +// Copyright (c) 2025- +// Authors: +// * Dave Parker (University of Oxford) +// +//------------------------------------------------------------------------------ +// +// This file is part of PRISM. +// +// PRISM 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 2 of the License, or +// (at your option) any later version. +// +// PRISM 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 PRISM; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//============================================================================== + +package prism; + +/** + * Interface for model classes that store action label info as a {@link ActionList}. + */ +public interface ActionListOwner +{ + /** + * Get the {@link ActionList} object storing info about actions + */ + ActionList getActionList(); +} diff --git a/prism/src/prism/Model.java b/prism/src/prism/Model.java index 5dbb453968..33e121be8b 100644 --- a/prism/src/prism/Model.java +++ b/prism/src/prism/Model.java @@ -50,7 +50,12 @@ public interface Model * Absence of an action label is denoted by null, * and null is also included in this list if there are unlabelled choices/transitions. */ - List getActions(); + default List getActions() + { + // Default implementation: find unique actions used across the model. + // This should be cached/optimised if action indices are looked up frequently. + return findActionsUsed(); + } /** * Get strings for the action labels attached to choices/transitions. @@ -58,16 +63,23 @@ public interface Model default List getActionStrings() { // By default, just apply toString to getActions() - return getActions().stream().map(Model::actionString).collect(Collectors.toList()); + return getActions().stream().map(ActionList::actionString).collect(Collectors.toList()); } /** - * Default conversion of an action label to a string - * ({@code toString()} or {@code ""} for {@code null}). + * Produce a list of the action labels attached to choices/transitions. + * Absence of an action label is denoted by null, + * and null is also included in this list if there are unlabelled choices/transitions. + */ + List findActionsUsed(); + + /** + * Do all choices/transitions have empty (null) action labels? */ - static String actionString(Object action) + default boolean onlyNullActionUsed() { - return action == null ? "" : action.toString(); + // Can't assume this is true + return false; } /** diff --git a/prism/src/symbolic/model/ModelSymbolic.java b/prism/src/symbolic/model/ModelSymbolic.java index 9f1c418fa9..4e52b1c11e 100644 --- a/prism/src/symbolic/model/ModelSymbolic.java +++ b/prism/src/symbolic/model/ModelSymbolic.java @@ -434,6 +434,13 @@ public List getActions() return actions; } + @Override + public List findActionsUsed() + { + // Not yet implemented + throw new UnsupportedOperationException(); + } + @Override public int getNumStates() { From 935f63c4c85ffca8d9babfb8143d8766f8d2d087 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Wed, 23 Apr 2025 12:36:11 +0100 Subject: [PATCH 22/44] Slightly faster findActionsUsed() for specific DTMC/MDP implementations. --- prism/src/explicit/ChoiceActionsSimple.java | 44 +++++++++++++++++++++ prism/src/explicit/DTMCSimple.java | 6 +++ prism/src/explicit/DTMCSparse.java | 19 +++++++++ prism/src/explicit/LTSSimple.java | 6 +++ prism/src/explicit/MDPSimple.java | 6 +++ prism/src/explicit/MDPSparse.java | 17 ++++++++ 6 files changed, 98 insertions(+) diff --git a/prism/src/explicit/ChoiceActionsSimple.java b/prism/src/explicit/ChoiceActionsSimple.java index 0ed51eb5bd..f0d50090ec 100644 --- a/prism/src/explicit/ChoiceActionsSimple.java +++ b/prism/src/explicit/ChoiceActionsSimple.java @@ -27,6 +27,10 @@ package explicit; import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.function.IntUnaryOperator; /** * Explicit-state storage of the action labels attached to choices in a model. @@ -154,6 +158,46 @@ public void setAction(int s, int i, Object action) // Accessors + /** + * Produce a list of the action labels attached to choices/transitions. + * Absence of an action label is denoted by null, + * and null is also included in this list if there are unlabelled choices/transitions. + * @param numStates The number of states in the model + * @param counts A function giving the number of choices/transitions for each state + */ + public List findActionsUsed(int numStates, IntUnaryOperator counts) + { + if (actions == null) { + // Null storage means all choices/transitions are unlabelled + return Collections.singletonList(null); + } else { + LinkedHashSet allActions = new LinkedHashSet<>(); + int numStatesStored = actions.size(); + for (int s = 0; s < numStatesStored; s++) { + ArrayList list = actions.get(s); + if (list == null) { + // Null list means any/all choices/transitions are unlabelled for s + if (counts.applyAsInt(s) > 0) { + allActions.add(null); + } + } else { + allActions.addAll(list); + if (list.size() < counts.applyAsInt(s)) { + // Undersized list means there are unlabelled choices/transitions + allActions.add(null); + } + } + } + for (int s = numStatesStored; s < numStates; s++) { + // Missing list means any/all choices/transitions are unlabelled for s + if (counts.applyAsInt(s) > 0) { + allActions.add(null); + } + } + return new ArrayList<>(allActions); + } + } + /** * Do all choices/transitions have empty (null) action labels? */ diff --git a/prism/src/explicit/DTMCSimple.java b/prism/src/explicit/DTMCSimple.java index 630ac28cf5..012f9e3f3f 100644 --- a/prism/src/explicit/DTMCSimple.java +++ b/prism/src/explicit/DTMCSimple.java @@ -263,6 +263,12 @@ public void addToProbability(int i, int j, Value prob, Object action) // Accessors (for Model) + @Override + public List findActionsUsed() + { + return actions.findActionsUsed(getNumStates(), this::getNumTransitions); + } + @Override public boolean onlyNullActionUsed() { diff --git a/prism/src/explicit/DTMCSparse.java b/prism/src/explicit/DTMCSparse.java index 1ffd4c097b..1a89df5858 100644 --- a/prism/src/explicit/DTMCSparse.java +++ b/prism/src/explicit/DTMCSparse.java @@ -30,9 +30,13 @@ package explicit; import java.util.AbstractMap; +import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; +import java.util.Collections; import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Map.Entry; import java.util.PrimitiveIterator.OfInt; import java.util.function.Function; @@ -169,6 +173,21 @@ public DTMCSparse() //--- Model --- + @Override + public List findActionsUsed() + { + if (actions == null) { + return Collections.singletonList(null); + } else { + LinkedHashSet allActions = new LinkedHashSet<>(); + int n = actions.length; + for (int i = 0; i < n; i++) { + allActions.add(actions[i]); + } + return new ArrayList<>(allActions); + } + } + @Override public boolean onlyNullActionUsed() { diff --git a/prism/src/explicit/LTSSimple.java b/prism/src/explicit/LTSSimple.java index 0c29940156..904a26cca1 100644 --- a/prism/src/explicit/LTSSimple.java +++ b/prism/src/explicit/LTSSimple.java @@ -202,6 +202,12 @@ public void setAction(int s, int i, Object action) // Accessors (for Model) + @Override + public List findActionsUsed() + { + return actions.findActionsUsed(getNumStates(), this::getNumChoices); + } + @Override public boolean onlyNullActionUsed() { diff --git a/prism/src/explicit/MDPSimple.java b/prism/src/explicit/MDPSimple.java index b5ba3364e4..48c03b38f0 100644 --- a/prism/src/explicit/MDPSimple.java +++ b/prism/src/explicit/MDPSimple.java @@ -351,6 +351,12 @@ public void setAction(int s, int i, Object o) // Accessors (for Model) + @Override + public List findActionsUsed() + { + return actions.findActionsUsed(getNumStates(), this::getNumChoices); + } + @Override public boolean onlyNullActionUsed() { diff --git a/prism/src/explicit/MDPSparse.java b/prism/src/explicit/MDPSparse.java index e5c34c1054..47f4755a0d 100644 --- a/prism/src/explicit/MDPSparse.java +++ b/prism/src/explicit/MDPSparse.java @@ -30,7 +30,9 @@ import java.util.AbstractMap; import java.util.ArrayList; import java.util.BitSet; +import java.util.Collections; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -437,6 +439,21 @@ public void accept(int s, int i, int s2, Double d, Object a) // Accessors (for Model) + @Override + public List findActionsUsed() + { + if (actions == null) { + return Collections.singletonList(null); + } else { + LinkedHashSet allActions = new LinkedHashSet<>(); + int n = actions.length; + for (int i = 0; i < n; i++) { + allActions.add(actions[i]); + } + return new ArrayList<>(allActions); + } + } + @Override public boolean onlyNullActionUsed() { From 4fdf3c186f9667d90be116c71e0672c612025c04 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 27 May 2025 18:03:30 +0100 Subject: [PATCH 23/44] Java implementation of symbolic-to-explicit model conversion. C-based version already exists for the purposes of model export. --- prism/src/io/IOUtils.java | 18 + .../symbolic/build/MTBDD2ExplicitModel.java | 516 ++++++++++++++++++ prism/src/symbolic/model/Model.java | 5 + prism/src/symbolic/model/ModelSymbolic.java | 6 + prism/src/symbolic/states/StateListMTBDD.java | 64 +++ 5 files changed, 609 insertions(+) create mode 100644 prism/src/symbolic/build/MTBDD2ExplicitModel.java diff --git a/prism/src/io/IOUtils.java b/prism/src/io/IOUtils.java index c4244627eb..8be09b660c 100644 --- a/prism/src/io/IOUtils.java +++ b/prism/src/io/IOUtils.java @@ -69,6 +69,24 @@ public interface LTSTransitionConsumer { void accept(int s, int i, int s2, Object a) throws PrismException; } + /** + * Functional interface for a consumer accepting state values (s,v), + * i.e., state s, value v. + */ + @FunctionalInterface + public interface StateValueConsumer { + void accept(int s, V v) throws PrismException; + } + + /** + * Functional interface for a consumer accepting state rewards (s,v), + * i.e., state s, value v. + */ + @FunctionalInterface + public interface StateRewardConsumer { + void accept(int s, V v) throws PrismException; + } + /** * Functional interface for a consumer accepting transition rewards (s,i,v), * i.e., source state s, index i, value v. diff --git a/prism/src/symbolic/build/MTBDD2ExplicitModel.java b/prism/src/symbolic/build/MTBDD2ExplicitModel.java new file mode 100644 index 0000000000..51b1a36a59 --- /dev/null +++ b/prism/src/symbolic/build/MTBDD2ExplicitModel.java @@ -0,0 +1,516 @@ +//============================================================================== +// +// Copyright (c) 2025- +// Authors: +// * Dave Parker (University of Oxford) +// +//------------------------------------------------------------------------------ +// +// This file is part of PRISM. +// +// PRISM 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 2 of the License, or +// (at your option) any later version. +// +// PRISM 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 PRISM; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//============================================================================== + +package symbolic.build; + +import common.SafeCast; +import explicit.CTMCSimple; +import explicit.DTMC; +import explicit.DTMCSimple; +import explicit.Distribution; +import explicit.MDP; +import explicit.MDPSimple; +import explicit.ModelExplicit; +import explicit.SuccessorsIterator; +import explicit.rewards.Rewards; +import explicit.rewards.Rewards2RewardGenerator; +import explicit.rewards.RewardsExplicit; +import explicit.rewards.RewardsSimple; +import io.IOUtils; +import jdd.JDD; +import jdd.JDDNode; +import jdd.JDDVars; +import odd.ODDNode; +import prism.Evaluator; +import prism.Pair; +import prism.PrismComponent; +import prism.PrismException; +import prism.RewardGenerator; +import prism.RewardInfo; +import symbolic.model.NondetModel; +import symbolic.model.ProbModel; +import symbolic.states.StateListMTBDD; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.List; +import java.util.function.IntConsumer; + +/** + * Class to convert symbolic representations of models to explicit-state ones. + */ +public class MTBDD2ExplicitModel extends PrismComponent +{ + public MTBDD2ExplicitModel() + { + super(); + } + + public MTBDD2ExplicitModel(PrismComponent parent) + { + super(parent); + } + + /** + * Convert a symbolically stored model to an explicit-state one. + * @param modelSymb The symbolic model + */ + public explicit.Model convertModel(symbolic.model.Model modelSymb) throws PrismException + { + ModelExplicit modelExpl; + DTMCSimple dtmcExpl; + CTMCSimple ctmcExpl; + MDPSimple mdpExpl; + int numStates = modelSymb.getNumStates(); + // Build new model and convert/add transitions + switch (modelSymb.getModelType()) { + case DTMC: + modelExpl = dtmcExpl = new DTMCSimple<>(numStates); + convertMarkovChainTransitions((ProbModel) modelSymb, dtmcExpl); + break; + case CTMC: + modelExpl = ctmcExpl = new CTMCSimple<>(numStates); + convertMarkovChainTransitions((ProbModel) modelSymb, ctmcExpl); + break; + case MDP: + modelExpl = mdpExpl = new MDPSimple<>(numStates); + convertMDPTransitions((NondetModel) modelSymb, mdpExpl); + break; + default: + throw new PrismException("Can't do symbolic-explicit conversion for " + modelSymb.getModelType() + "s"); + } + // Convert/add initial states + traverseStatesBDD(modelSymb.getStart(), modelSymb.getAllDDRowVars(), modelSymb.getODD(), modelExpl::addInitialState); + // If the symbolic model has labels attached, convert/add them + for (String label : modelSymb.getLabels()) { + BitSet labelStates = new BitSet(); + traverseStatesBDD(modelSymb.getLabelDD(label), modelSymb.getAllDDRowVars(), modelSymb.getODD(), labelStates::set); + modelExpl.addLabel(label, labelStates); + } + // If the symbolic model has deadlock info attached, convert/add it + if (modelSymb.getDeadlocks() != null) { + BitSet deadlockStates = new BitSet(); + traverseStatesBDD(modelSymb.getDeadlocks(), modelSymb.getAllDDRowVars(), modelSymb.getODD(), deadlockStates::set); + for (int s = deadlockStates.nextSetBit(0); s >= 0; s = deadlockStates.nextSetBit(s + 1)) { + modelExpl.addDeadlockState(s); + } + } + // Convert/add state information + modelExpl.setStatesList(((StateListMTBDD) modelSymb.getReachableStates()).getAsListOfStates()); + return modelExpl; + } + + /** + * Get a {@link RewardGenerator}, which converts the rewards for a + * symbolically stored model to explicit-state reward storage. + * @param modelSymb The symbolic model + * @param modelExpl The corresponding explicit-state model + * @param rewardInfo Reward info + */ + public RewardGenerator getRewardConverter(symbolic.model.Model modelSymb, explicit.Model modelExpl, RewardInfo rewardInfo) throws PrismException + { + return new Rewards2RewardGenerator<>(rewardInfo, modelExpl, Evaluator.forDouble()) + { + @Override + public Rewards getTheRewardObject(int r) throws PrismException + { + return convertRewards(modelSymb, modelExpl, r, rewardInfo); + } + }; + } + + /** + * Converts the rewards for a symbolically stored model to explicit-state reward storage. + * @param modelSymb The symbolic model + * @param modelExpl The corresponding explicit-state model + * @param r The index of the reward structure + * @param rewardInfo Reward info + */ + public explicit.rewards.Rewards convertRewards(symbolic.model.Model modelSymb, explicit.Model modelExpl, int r, RewardInfo rewardInfo) throws PrismException + { + // Construct new Rewards object + RewardsSimple rewards = new RewardsSimple<>(modelSymb.getNumStates()); + // Extract state rewards (if present) + if (rewardInfo.rewardStructHasStateRewards(r)) { + JDDNode stateRewards = modelSymb.getStateRewards(r); + traverseVectorDD(stateRewards, modelSymb.getAllDDRowVars(), modelSymb.getODD(), rewards::addToStateReward); + } + // Extract transition rewards (if present) + if (rewardInfo.rewardStructHasTransitionRewards(r)) { + JDDNode transRewards = modelSymb.getTransRewards(r); + switch (modelSymb.getModelType()) { + case DTMC: + case CTMC: + convertMarkovChainTransitionRewards((ProbModel) modelSymb, (DTMC) modelExpl, transRewards, rewards); + break; + case MDP: + convertMDPTransitionRewards((NondetModel) modelSymb, (MDP) modelExpl, transRewards, rewards); + break; + default: + throw new PrismException("Can't do symbolic-explicit reward conversion for " + modelSymb.getModelType() + "s"); + } + } + return rewards; + } + + /** + * Convert the transitions and actions for a symbolically represented Markov chain + * @param mcSymb The symbolic Markov chain + * @param mcExpl The explicit-state Markov chain to add to + */ + private void convertMarkovChainTransitions(symbolic.model.ProbModel mcSymb, DTMCSimple mcExpl) throws PrismException + { + // Get action info + List actions = mcSymb.getActions(); + JDDNode[] transPerAction = mcSymb.getTransPerAction(); + // If action info is stored, extract each DD separately + if (transPerAction != null) { + int n = transPerAction.length; + for (int i = 0; i < n; i++) { + Object iAct = actions.get(i); + traverseMatrixDD(transPerAction[i], mcSymb.getAllDDRowVars(), mcSymb.getAllDDColVars(), mcSymb.getODD(), ((s, s2, p, a) -> { + mcExpl.addToProbability(s, s2, p, iAct); + })); + } + } + // Otherwise, just use the main transition matrix DD + else { + traverseMatrixDD(mcSymb.getTrans(), mcSymb.getAllDDRowVars(), mcSymb.getAllDDColVars(), mcSymb.getODD(), mcExpl::addToProbability); + } + } + + /** + * Convert the transitions and actions for a symbolically represented MDP + * @param mdpSymb The symbolic MDP + * @param mdpExpl The explicit-state MDP to add to + */ + private void convertMDPTransitions(symbolic.model.NondetModel mdpSymb, MDPSimple mdpExpl) throws PrismException + { + // Get action info + List actions = mdpSymb.getActions(); + // Split the transition/action DDs across nondeterministic choices + List> mdpDDs = splitMDPDD(mdpSymb.getTrans(), mdpSymb.getTransActions(), mdpSymb.getAllDDNondetVars()); + // For each one + for (Pair ddPair : mdpDDs) { + // Store the transitions in a map from source state to distribution + JDDNode dd = ddPair.getKey(); + HashMap> distrs = new HashMap<>(); + traverseMatrixDD(dd, mdpSymb.getAllDDRowVars(), mdpSymb.getAllDDColVars(), mdpSymb.getODD(), (s,s2,v,a) -> { + Distribution distr = distrs.get(s); + if (distr == null) { + distrs.put(s, distr = Distribution.ofDouble()); + } + distr.add(s2, v); + }); + // Store any actions in a map from state to action + JDDNode ddActions = ddPair.getValue(); + HashMap distrActions = new HashMap<>(); + traverseVectorDD(ddActions, mdpSymb.getAllDDRowVars(), mdpSymb.getODD(), (s, v) -> { + int a = (int) Math.round(v); + if (a < actions.size()) { + Object act = actions.get(a); + if (act != null) { + distrActions.put(s, act); + } + } + }); + // Add info to the MDP + distrs.forEach((s, d) -> mdpExpl.addActionLabelledChoice(s, d, distrActions.get(s))); + JDD.Deref(dd); + JDD.Deref(ddActions); + } + } + + /** + * Convert the transitions and actions for a symbolically represented Markov chain + * @param mcSymb The symbolic Markov chain + * @param mcExpl The explicit Markov chain + * @param rewardsSymb symbolic rewards to convert + * @param rewardsExpl The explicit-state reward storage to add to + */ + private void convertMarkovChainTransitionRewards(symbolic.model.ProbModel mcSymb, DTMC mcExpl, JDDNode rewardsSymb, RewardsExplicit rewardsExpl) throws PrismException + { + // Traverse the DD to extract the rewards + traverseMatrixDD(rewardsSymb, mcSymb.getAllDDRowVars(), mcSymb.getAllDDColVars(), mcSymb.getODD(), (s, s2, v, a) -> { + // Find successor index for state s2 (from state s) + SuccessorsIterator it = mcExpl.getSuccessors(s); + int i = 0; + while (it.hasNext()) { + if (it.nextInt() == s2) { + rewardsExpl.setTransitionReward(s, i, v); + return; + } + i++; + } + }); + } + + /** + * Convert the transitions and actions for a symbolically represented Markov chain + * @param mdpSymb The symbolic MDP + * @param mdpExpl The explicit MDP + * @param rewardsSymb symbolic rewards to convert + * @param rewardsExpl The explicit-state reward storage to add to + */ + private void convertMDPTransitionRewards(symbolic.model.NondetModel mdpSymb, MDP mdpExpl, JDDNode rewardsSymb, RewardsExplicit rewardsExpl) throws PrismException + { + int numStates = mdpSymb.getNumStates(); + int[] choiceIndices = new int[numStates]; + // Split the transition/reward DDs across nondeterministic choices + List> mdpDDs = splitMDPDD(mdpSymb.getTrans01(), rewardsSymb, mdpSymb.getAllDDNondetVars()); + // For each one + for (Pair ddPair : mdpDDs) { + // Update counts of choices for each state + JDDNode ddChoices = JDD.ThereExists(ddPair.getKey(), mdpSymb.getAllDDColVars()); + traverseStatesBDD(ddChoices, mdpSymb.getAllDDRowVars(), mdpSymb.getODD(), s -> choiceIndices[s]++); + // Store transition rewards + // (note: assumes regards are non-negative) + JDDNode ddRewards = JDD.MaxAbstract(ddPair.getValue(), mdpSymb.getAllDDColVars()); + traverseVectorDD(ddRewards, mdpSymb.getAllDDRowVars(), mdpSymb.getODD(), (s, v) -> { + rewardsExpl.setTransitionReward(s, choiceIndices[s] - 1, v); + }); + JDD.Deref(ddChoices); + JDD.Deref(ddRewards); + } + } + + /** + * Split the DD for an MDP's transition function, + * and a corresponding DD defined for a subset of the transitions, + * into (pairs of) separate DDs, resolving nondeterminism. + * @param trans MDP transition function DD + * @param dd Choice actions DD + * @param ddNondetVars DD variables for nondeterminism + */ + private List> splitMDPDD(JDDNode trans, JDDNode dd, JDDVars ddNondetVars) + { + List> mdpDDs = new ArrayList<>(); + splitMDPDDRec(trans, dd, ddNondetVars, 0, mdpDDs); + return mdpDDs; + } + + /** + * Recursive helper for {@link #splitMDPDD(JDDNode, JDDNode, JDDVars)}. + * @param trans MDP transition function DD + * @param transActions Choice actions DD + * @param ddNondetVars DD variables for nondeterminism + * @param level Level of recursion + * @param mdpDDs List to store DD pairs + */ + private void splitMDPDDRec(JDDNode trans, JDDNode transActions, JDDVars ddNondetVars, int level, List> mdpDDs) + { + // Base case: zero terminal + if (trans.equals(JDD.ZERO)) { + return; + } + // Base case: non-zero terminal + if (level == ddNondetVars.n()) { + // Store DDs + mdpDDs.add(new Pair<>(trans.copy(), transActions.copy())); + return; + } + // Recurse + JDDNode e, t; + if (trans.getIndex() > ddNondetVars.getVarIndex(level)) { + e = t = trans; + } + else { + e = trans.getElse(); + t = trans.getThen(); + } + JDDNode eActions, tActions; + if (transActions.getIndex() > ddNondetVars.getVarIndex(level)) { + eActions = tActions = transActions; + } + else { + eActions = transActions.getElse(); + tActions = transActions.getThen(); + } + splitMDPDDRec(e, eActions, ddNondetVars, level + 1, mdpDDs); + splitMDPDDRec(t, tActions, ddNondetVars, level + 1, mdpDDs); + } + + /** + * Traverse a BDD representing a set of states and extract the indices of those states + * @param dd The BDD + * @param ddVars The BDD variables + * @param odd The ODD + * @param consumer Consumer to accept indices + */ + private void traverseStatesBDD(JDDNode dd, JDDVars ddVars, ODDNode odd, IntConsumer consumer) throws PrismException + { + traverseStatesBDDRec(dd, ddVars, 0, odd, 0, consumer); + } + + /** + * Recursive helper for {@link #traverseStatesBDD(JDDNode, JDDVars, ODDNode, IntConsumer)}. + * @param dd The BDD + * @param ddVars The BDD variables + * @param level Level of recursion + * @param odd The ODD + * @param i State index counter + * @param consumer Consumer to accept indices + */ + private void traverseStatesBDDRec(JDDNode dd, JDDVars ddVars, int level, ODDNode odd, long i, IntConsumer consumer) throws PrismException + { + // Base case: zero terminal + if (dd.equals(JDD.ZERO)) { + return; + } + // Base case: non-zero terminal + if (level == ddVars.n()) { + consumer.accept(SafeCast.toInt(i)); + return; + } + // Recurse + JDDNode e, t; + if (dd.getIndex() > ddVars.getVarIndex(level)) { + e = t = dd; + } + else { + e = dd.getElse(); + t = dd.getThen(); + } + traverseStatesBDDRec(e, ddVars, level + 1, odd.getElse(), i, consumer); + traverseStatesBDDRec(t, ddVars, level + 1, odd.getThen(), i + odd.getEOff(), consumer); + } + + /** + * Traverse a DD representing a state-indexed vector of doubles and extract the non-zero elements of the vector + * @param dd The DD + * @param ddVars The DD variables + * @param odd The ODD + * @param consumer Consumer to accept indices/values + */ + private void traverseVectorDD(JDDNode dd, JDDVars ddVars, ODDNode odd, IOUtils.StateValueConsumer consumer) throws PrismException + { + traverseVectorDDRec(dd, ddVars, 0, odd, 0, consumer); + } + + /** + * Recursive helper for {@link #traverseVectorDD(JDDNode, JDDVars, ODDNode, IOUtils.StateValueConsumer)} + * @param dd The DD + * @param ddVars The DD variables + * @param level Level of recursion + * @param odd The ODD + * @param i State index counter + * @param consumer Consumer to accept indices/values + */ + private void traverseVectorDDRec(JDDNode dd, JDDVars ddVars, int level, ODDNode odd, long i, IOUtils.StateValueConsumer consumer) throws PrismException + { + // Base case: zero terminal + if (dd.equals(JDD.ZERO)) { + return; + } + // Base case: non-zero terminal + if (level == ddVars.n()) { + consumer.accept(SafeCast.toInt(i), dd.getValue()); + return; + } + // Recurse + JDDNode e, t; + if (dd.getIndex() > ddVars.getVarIndex(level)) { + e = t = dd; + } + else { + e = dd.getElse(); + t = dd.getThen(); + } + traverseVectorDDRec(e, ddVars, level + 1, odd.getElse(), i, consumer); + traverseVectorDDRec(t, ddVars, level + 1, odd.getThen(), i + odd.getEOff(), consumer); + } + + /** + * Traverse a DD representing a state-indexed matrix of doubles and extract the non-zero elements of the vector + * + * @param dd The DD + * @param ddRowVars The DD variables for rows + * @param ddColVars The DD variables for cols + * @param odd The ODD + * @param consumer Consumer to accept indices/values + */ + private void traverseMatrixDD(JDDNode dd, JDDVars ddRowVars, JDDVars ddColVars, ODDNode odd, IOUtils.MCTransitionConsumer consumer) throws PrismException + { + traverseMatrixDD(dd, ddRowVars, ddColVars, 0, odd, odd, 0, 0, consumer); + } + + /** + * Recursive helper for {@link #traverseMatrixDD(JDDNode, JDDVars, JDDVars, ODDNode, IOUtils.MCTransitionConsumer)}. + * @param dd The DD + * @param ddRowVars The DD variables for rows + * @param ddColVars The DD variables for cols + * @param level Level of recursion + * @param oddRow The ODD for rows + * @param oddCol The ODD for cols + * @param r State index counter for rows + * @param c State index counter for cols + * @param consumer Consumer to accept indices/values + */ + private void traverseMatrixDD(JDDNode dd, JDDVars ddRowVars, JDDVars ddColVars, int level, ODDNode oddRow, ODDNode oddCol, long r, long c, IOUtils.MCTransitionConsumer consumer) throws PrismException + { + // Base case: zero terminal + if (dd.equals(JDD.ZERO)) { + return; + } + // Base case: non-zero terminal + if (level == ddRowVars.n()) { + consumer.accept(SafeCast.toInt(r), SafeCast.toInt(c), dd.getValue(), null); + return; + } + // Recurse + JDDNode e, t, ee, et, te, tt; + if (dd.getIndex() > ddColVars.getVarIndex(level)) { + ee = et = te = tt = dd; + } + else if (dd.getIndex() > ddRowVars.getVarIndex(level)) { + ee = te = dd.getElse(); + et = tt = dd.getThen(); + } + else { + e = dd.getElse(); + if (e.getIndex() > ddColVars.getVarIndex(level)) { + ee = et = e; + } + else { + ee = e.getElse(); + et = e.getThen(); + } + t = dd.getThen(); + if (t.getIndex() > ddColVars.getVarIndex(level)) { + te = tt = t; + } + else { + te = t.getElse(); + tt = t.getThen(); + } + } + traverseMatrixDD(ee, ddRowVars, ddColVars, level + 1, oddRow.getElse(), oddCol.getElse(), r, c, consumer); + traverseMatrixDD(et, ddRowVars, ddColVars, level + 1, oddRow.getElse(), oddCol.getThen(), r, c + oddCol.getEOff(), consumer); + traverseMatrixDD(te, ddRowVars, ddColVars, level + 1, oddRow.getThen(), oddCol.getElse(), r + oddRow.getEOff(), c, consumer); + traverseMatrixDD(tt, ddRowVars, ddColVars, level + 1, oddRow.getThen(), oddCol.getThen(), r + oddRow.getEOff(), c + oddCol.getEOff(), consumer); + } +} diff --git a/prism/src/symbolic/model/Model.java b/prism/src/symbolic/model/Model.java index d6e09f9959..a13e565cfb 100644 --- a/prism/src/symbolic/model/Model.java +++ b/prism/src/symbolic/model/Model.java @@ -162,6 +162,11 @@ default boolean hasLabelDD(String label) */ int getNumRewardStructs(); + /** + * Get the name of the {@code i}th reward structure. + */ + String getRewardStructName(int i); + /** * Get an MTBDD for the state rewards for the {@code i}th reward structure. */ diff --git a/prism/src/symbolic/model/ModelSymbolic.java b/prism/src/symbolic/model/ModelSymbolic.java index 4e52b1c11e..3db313ef75 100644 --- a/prism/src/symbolic/model/ModelSymbolic.java +++ b/prism/src/symbolic/model/ModelSymbolic.java @@ -561,6 +561,12 @@ public int getNumRewardStructs() return numRewardStructs; } + @Override + public String getRewardStructName(int i) + { + return rewardStructNames[i]; + } + @Override public JDDNode getStateRewards(int i) { diff --git a/prism/src/symbolic/states/StateListMTBDD.java b/prism/src/symbolic/states/StateListMTBDD.java index a79864472c..1b43dbaabf 100644 --- a/prism/src/symbolic/states/StateListMTBDD.java +++ b/prism/src/symbolic/states/StateListMTBDD.java @@ -27,6 +27,7 @@ package symbolic.states; import java.util.*; +import java.util.function.Consumer; import jdd.*; import odd.*; @@ -385,6 +386,69 @@ public Values getFirstAsValues() throws PrismException return values; } + /** + * Convert this state list to a {@link List} of {@link State} objects. + */ + public List getAsListOfStates() + { + List list = new ArrayList<>(); + // Initialise variable info storage for recursion + for (int i = 0; i < varList.getNumVars(); i++) { + varValues[i] = 0; + } + currentVar = 0; + currentVarLevel = 0; + // Traverse BDD to extract states + getAsListOfStatesRec(states, 0, odd, 0, list::add); + return list; + } + + /** + * Recursive helper for {@link #getAsListOfStates()}. + * @param dd The BDD + * @param level Level of recursion + * @param o The ODD + * @param n State index counter + * @param consumer Consumer to accept states + */ + private void getAsListOfStatesRec(JDDNode dd, int level, ODDNode o, long n, Consumer consumer) + { + // Base case: zero terminal + if (dd.equals(JDD.ZERO)) { + return; + } + // Base case: non-zero terminal + if (level == numVars) { + int numVars = varList.getNumVars(); + State state = new State(numVars); + for (int i = 0; i < numVars; i++) { + state.setValue(i, varList.decodeFromInt(i, varValues[i])); + } + consumer.accept(state); + return; + } + // Recurse + JDDNode e, t; + if (dd.getIndex() > vars.getVarIndex(level)) { + e = t = dd; + } + else { + e = dd.getElse(); + t = dd.getThen(); + } + ODDNode oe = (o != null ? o.getElse() : null); + ODDNode ot = (o != null ? o.getThen() : null); + long eoff = (o != null ? o.getEOff() : 0); + currentVarLevel++; if (currentVarLevel == varSizes[currentVar]) { currentVar++; currentVarLevel = 0; } + getAsListOfStatesRec(e, level + 1, oe, n, consumer); + currentVarLevel--; if (currentVarLevel == -1) { currentVar--; currentVarLevel = varSizes[currentVar] - 1; } + varValues[currentVar] += (1 << (varSizes[currentVar] - 1 - currentVarLevel)); + currentVarLevel++; if (currentVarLevel == varSizes[currentVar]) { currentVar++; currentVarLevel = 0; } + getAsListOfStatesRec(t, level + 1, ot, n + eoff, consumer); + currentVarLevel--; if (currentVarLevel == -1) { currentVar--; currentVarLevel = varSizes[currentVar] - 1; } + varValues[currentVar] -= (1 << (varSizes[currentVar] - 1 - currentVarLevel)); + } + @Override public int getIndexOfState(State state) throws PrismNotSupportedException { From a399f4bd9abc7a068b39e23db58872b0a51c41ab Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 27 May 2025 18:05:27 +0100 Subject: [PATCH 24/44] Export to DRN format from symbolic model representations. Works by first converting to an explicit engine model. --- prism/src/prism/Prism.java | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java index 95364a67f4..d3dd3e9737 100644 --- a/prism/src/prism/Prism.java +++ b/prism/src/prism/Prism.java @@ -80,6 +80,7 @@ import symbolic.build.ExplicitFiles2MTBDD; import io.PrismExplicitImporter; import symbolic.build.ExplicitModel2MTBDD; +import symbolic.build.MTBDD2ExplicitModel; import symbolic.build.ModelGenerator2MTBDD; import symbolic.build.Modules2MTBDD; import symbolic.comp.ECComputer; @@ -2684,20 +2685,13 @@ public void exportBuiltModelTask(ModelExportTask exportTask) throws PrismExcepti boolean engineSwitch = false; int lastEngine = -1; try { - // Auto-switch to explicit engine if required - if (exportTask.getExportOptions().getFormat() == ModelExportFormat.DRN) { - if (getCurrentEngine() == PrismEngine.SYMBOLIC) { - mainLog.printWarning("Switching to explicit engine to allow export " + exportTask.getExportOptions().getFormat().description()); - engineSwitch = true; - lastEngine = getEngine(); - setEngine(Prism.EXPLICIT); - } - } + // NB: currently no engine auto-switch needed // Build model, if necessary buildModelIfRequired(); // Merge export options with PRISM settings and do export mainLog.println("\n" + exportTask.getMessage()); ModelExportOptions exportOptions = newMergedModelExportOptions(exportTask.getExportOptions()); + //long timer = System.currentTimeMillis(); switch (exportTask.getEntity()) { case MODEL: doExportBuiltModel(new ModelExportTask(exportTask, exportOptions)); @@ -2718,6 +2712,8 @@ public void exportBuiltModelTask(ModelExportTask exportTask) throws PrismExcepti doExportBuiltModelLabels(new ModelExportTask(exportTask, exportOptions)); break; } + //timer = System.currentTimeMillis() - timer; + //mainLog.println("Time for model export: " + timer / 1000.0 + " seconds."); } finally { // Undo auto-switch (if any) if (engineSwitch) { @@ -2737,8 +2733,18 @@ private void doExportBuiltModel(ModelExportTask exportTask) throws PrismExceptio { // Export via either symbolic/explicit model checker if (getBuiltModelType() == ModelBuildType.SYMBOLIC) { - symbolic.comp.StateModelChecker mcSymb = createModelChecker(null); - mcSymb.exportModel(exportTask); + // In some cases, we need to convert to an explicit model first + if (exportTask.getExportOptions().getFormat() == ModelExportFormat.DRN) { + MTBDD2ExplicitModel m2m = new MTBDD2ExplicitModel(this); + explicit.Model modelExpl = m2m.convertModel(getBuiltModelSymbolic()); + explicit.StateModelChecker mcExpl = explicit.StateModelChecker.createModelChecker(getModelType(), this); + RewardGenerator rewardGen = m2m.getRewardConverter(getBuiltModelSymbolic(), modelExpl, getRewardInfo()); + mcExpl.setModelCheckingInfo(getModelInfo(), null, rewardGen); + mcExpl.exportModel(modelExpl, exportTask); + } else { + symbolic.comp.StateModelChecker mcSymb = createModelChecker(null); + mcSymb.exportModel(exportTask); + } } else { explicit.StateModelChecker mcExpl = createModelCheckerExplicit(null); mcExpl.exportModel(getBuiltModelExplicit(), exportTask); From 70114ae0d89a4e5fb02f75f0d146d2b720ae68bf Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 27 May 2025 23:36:35 +0100 Subject: [PATCH 25/44] ModelExporter class has access to ModelInfo (if provided). Would allow inclusion of variable/observable info in the export. --- prism/src/explicit/StateModelChecker.java | 1 + prism/src/io/ModelExporter.java | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/prism/src/explicit/StateModelChecker.java b/prism/src/explicit/StateModelChecker.java index 9229030e0f..fae1df9f05 100644 --- a/prism/src/explicit/StateModelChecker.java +++ b/prism/src/explicit/StateModelChecker.java @@ -1549,6 +1549,7 @@ public void exportModel(Model model, ModelExportTask exportTask) default: throw new PrismNotSupportedException("Export " + exportOptions.getFormat().description() + " not supported by explicit engine"); } + exporter.setModelInfo(modelInfo); File file = exportTask.getFile(); // If needed, add label/reward info if (exportOptions.getFormat() == ModelExportFormat.DRN) { diff --git a/prism/src/io/ModelExporter.java b/prism/src/io/ModelExporter.java index d861f265f0..05be3c2221 100644 --- a/prism/src/io/ModelExporter.java +++ b/prism/src/io/ModelExporter.java @@ -33,6 +33,7 @@ import explicit.NondetModel; import explicit.rewards.Rewards; import prism.Evaluator; +import prism.ModelInfo; import prism.Pair; import prism.PrismException; import prism.PrismFileLog; @@ -67,6 +68,8 @@ public abstract class ModelExporter protected List labels = new ArrayList<>(); protected List labelNames = new ArrayList<>(); + protected ModelInfo modelInfo; + /** * Construct a ModelExporter with default export options. */ @@ -160,6 +163,15 @@ public void addLabels(List labels, List labelNames) } } + /** + * Provide information about the model, + * e.g., for variable/observable annotations. + */ + public void setModelInfo(ModelInfo modelInfo) + { + this.modelInfo = modelInfo; + } + // Get methods /** @@ -268,6 +280,16 @@ public String getLabelName(int i) return labelNames.get(i); } + /** + * Get information about the model, + * e.g., for variable/observable annotations. + * May be null. + */ + public ModelInfo getModelInfo() + { + return modelInfo; + } + /** * Export a model to a {@link PrismLog}. * @param model The model From 485be8cccdc703015d5596bedd2572136eb77e04 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Fri, 11 Apr 2025 23:38:07 +0100 Subject: [PATCH 26/44] CHANGELOG. --- CHANGELOG.txt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 4369393076..5d4f665e32 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,18 +1,19 @@ This file contains details of the changes in each new version of PRISM. ----------------------------------------------------------------------------- -Changes since last release (up to f25c788dd) +Changes since last release (up to 4fc1eaed3) ----------------------------------------------------------------------------- * Explicit engine improvements - support for transition rewards in Markov chains - storage (and export) of actions for Markov chains -* Model export functionality (explicit engine) +* Model export functionality - export of exact and parametric models - preliminary DRN (Storm) output - - actions=true/false option - - remove old options (MRMC format, unordered) + - actions=true/false option (explicit engine) + - multiple -exportmodel allowed and format=x option + - remove old options (MRMC format, unordered, spy) * Model import functionality (from explicit files) - import of transition rewards @@ -32,10 +33,11 @@ Changes since last release (up to f25c788dd) * Various bugfixes * Development and code-level changes - - prism.Prism API: new model export methods + - prism.Prism API: new/updated model export methods - prism.Prism API: remove some old deprecated methods - prism.Prism: refactored storage/access for model info - prism.Prism: integration of exact model building/checking + - improved access to actions and their indices in models - redesign/refactoring of explicit engine reward classes - refactoring of explicit engine model import/export code (centralised in io package, export moved from model classes) @@ -43,6 +45,7 @@ Changes since last release (up to f25c788dd) - symbolic code rearranged (new symbolic package) - symbolic model storage refactored, simplified, documented - extension of strategy classes, especially for induced models + - Java-based symbolic-to-explicit model conversion - prism-auto: --errors-only switch * [More minor development and code-level changes] @@ -55,6 +58,7 @@ Changes since last release (up to f25c788dd) - VarList aware of EvaluationContext/EvalMode - parametric engine: improved error reporting and formatting - prism.API: exportprism moved from setting to API call + - model export refactoring, including ModelExportTask ----------------------------------------------------------------------------- Version 4.8.1 (first released 13/1/2024) From 83f33702089e685abe660657b9a7a77adeb569ec Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Fri, 7 Feb 2025 22:07:24 +0000 Subject: [PATCH 27/44] Make explicit model import treat empty ("") actions as absent. --- .../functionality/import/mdp_simple_mod.auto | 1 + .../functionality/import/mdp_simple_mod.tra | 21 +++++++++++++++++++ prism/src/io/PrismExplicitImporter.java | 9 ++++++++ 3 files changed, 31 insertions(+) create mode 100644 prism-tests/functionality/import/mdp_simple_mod.auto create mode 100644 prism-tests/functionality/import/mdp_simple_mod.tra diff --git a/prism-tests/functionality/import/mdp_simple_mod.auto b/prism-tests/functionality/import/mdp_simple_mod.auto new file mode 100644 index 0000000000..15570a609e --- /dev/null +++ b/prism-tests/functionality/import/mdp_simple_mod.auto @@ -0,0 +1 @@ +-importmodel mdp_simple_mod.tra -exportmodel mdp_simple.tra diff --git a/prism-tests/functionality/import/mdp_simple_mod.tra b/prism-tests/functionality/import/mdp_simple_mod.tra new file mode 100644 index 0000000000..c887d1e958 --- /dev/null +++ b/prism-tests/functionality/import/mdp_simple_mod.tra @@ -0,0 +1,21 @@ +10 15 20 +0 0 1 0.1 b +0 0 2 0.9 b +0 1 3 1 a +1 0 1 1 +2 0 2 1 +3 0 1 0.2 b +3 0 2 0.8 b +3 1 6 1 a +4 0 4 1 +5 0 5 1 +6 0 4 0.3 b +6 0 5 0.7 b +6 1 7 1 a +7 0 1 0.4 b +7 0 2 0.6 b +7 1 8 1 a +8 0 1 0.5 b +8 0 2 0.5 b +8 1 9 1 a +9 0 1 1 diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index 143c2e5313..43a7b573ca 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -1222,8 +1222,17 @@ protected static Value checkValue(String v, Evaluator eval) throw } } + /** + * Check that a (string) action label is legal and return it if so. + * Otherwise, an explanatory exception is thrown. + * A legal action label is either "" (unlabelled) or a legal PRISM identifier. + * In the case of an empty ("") action, this returns null. + */ protected static String checkAction(String a) throws PrismException { + if (a == null || "".equals(a)) { + return null; + } if (!Prism.isValidIdentifier(a)) { throw new PrismException("invalid action name \"" + a + "\""); } From e3f9f7ab9b647122eea25a19365be1ae483cefe6 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Fri, 31 Jan 2025 12:15:52 +0000 Subject: [PATCH 28/44] Prism explicit files importer extracts model stats separately. --- prism/src/io/PrismExplicitImporter.java | 90 +++++++++++++++---------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index 43a7b573ca..c81edce947 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -95,10 +95,14 @@ public class PrismExplicitImporter implements ExplicitModelImporter // String stating the model type and how it was obtained private String modelTypeString; - // Num states/transitions - private int numStates = 0; - private int numChoices = 0; - private int numTransitions = 0; + // Model statistics (num states/choices/transitions) + private class ModelStats + { + int numStates = 0; + int numChoices = 0; + int numTransitions = 0; + } + private ModelStats modelStats; // Mapping from label indices in file to (non-built-in) label indices (also: -1=init, -2=deadlock) private List labelMap; @@ -224,10 +228,10 @@ public int getNumStates() throws PrismException { // Construct lazily, as needed // (determined from either states file or transitions file) - if (modelInfo == null) { - buildModelInfo(); + if (modelStats == null) { + buildModelStats(); } - return numStates; + return modelStats.numStates; } @Override @@ -235,10 +239,10 @@ public int getNumChoices() throws PrismException { // Construct lazily, as needed // (determined from transitions file) - if (modelInfo == null) { - buildModelInfo(); + if (modelStats == null) { + buildModelStats(); } - return numChoices; + return modelStats.numChoices; } @Override @@ -246,10 +250,10 @@ public int getNumTransitions() throws PrismException { // Construct lazily, as needed // (determined from transitions file) - if (modelInfo == null) { - buildModelInfo(); + if (modelStats == null) { + buildModelStats(); } - return numTransitions; + return modelStats.numTransitions; } @Override @@ -268,15 +272,28 @@ public RewardInfo getRewardInfo() throws PrismException return rewardInfo; } + /** + * Build/store model stats (from the transitions file). + * Can then be accessed via {@link #getNumStates()}, {@link #getNumChoices()}, {@link #getNumTransitions()}. + */ + private void buildModelStats() throws PrismException + { + // Extract model stats from header of transitions file + extractModelStatsFromTransFile(transFile); + } + /** * Build/store model info from the states/transitions/labels files. * Can then be accessed via {@link #getModelInfo()}. - * Also available: {@link #getNumStates()}, {@link #getNumChoices()}, {@link #getNumTransitions()}. + * Also calls {@link #buildModelStats()} if needed. + * Which makes available {@link #getNumStates()}, {@link #getNumChoices()}, {@link #getNumTransitions()}. */ private void buildModelInfo() throws PrismException { - // Extract model stats from header of transitions file - extractModelStatsFromTransFile(transFile); + // Build model stats, if not already done + if (modelStats == null) { + buildModelStats(); + } // Extract variable info from states, if available if (statesFile != null) { @@ -288,8 +305,8 @@ private void buildModelInfo() throws PrismException varNames = Collections.singletonList("x"); varTypes = Collections.singletonList(TypeInt.getInstance()); varMins = new int[] { 0 }; - varMaxs = new int[] { numStates - 1 }; - varRanges = new int[] { numStates - 1 }; + varMaxs = new int[] { getNumStates() - 1 }; + varRanges = new int[] { getNumStates() - 1 }; } // Generate and store label names from the labels file, if available. @@ -447,6 +464,7 @@ private void extractVarInfoFromStatesFile(File statesFile) throws PrismException private void extractModelStatsFromTransFile(File transFile) throws PrismException { try (BufferedReader in = new BufferedReader(new FileReader(transFile))) { + modelStats = new ModelStats(); BasicReader reader = BasicReader.wrap(in).normalizeLineEndings(); CsvReader csv = new CsvReader(reader, false, false, false, ' ', LF); if (!csv.hasNextRecord()) { @@ -454,17 +472,19 @@ private void extractModelStatsFromTransFile(File transFile) throws PrismExceptio } String[] record = csv.nextRecord(); checkLineSize(record, 2, 3); - numStates = Integer.parseInt(record[0]); + modelStats.numStates = Integer.parseInt(record[0]); if (record.length == 2) { - numChoices = numStates; - numTransitions = Integer.parseInt(record[1]); + modelStats.numChoices = modelStats.numStates; + modelStats.numTransitions = Integer.parseInt(record[1]); } else { - numChoices = Integer.parseInt(record[1]); - numTransitions = Integer.parseInt(record[2]); + modelStats.numChoices = Integer.parseInt(record[1]); + modelStats.numTransitions = Integer.parseInt(record[2]); } } catch (IOException e) { + modelStats = null; throw new PrismException("File I/O error reading from \"" + transFile + "\""); } catch (NumberFormatException | CsvFormatException e) { + modelStats = null; throw new PrismException("Error detected at line 1 of transitions file \"" + transFile + "\""); } } @@ -614,7 +634,7 @@ public void extractStates(IOUtils.StateDefnConsumer storeStateDefn) throws Prism int numVars = modelInfo.getNumVars(); // If there is no info, just assume that states comprise a single integer value if (getStatesFile() == null) { - for (int s = 0; s < numStates; s++) { + for (int s = 0; s < modelStats.numStates; s++) { storeStateDefn.accept(s, 0, s); } return; @@ -631,7 +651,7 @@ public void extractStates(IOUtils.StateDefnConsumer storeStateDefn) throws Prism // Split into two parts String[] ss = st.split(":"); // Determine which state this line describes - int s = checkStateIndex(Integer.parseInt(ss[0]), numStates); + int s = checkStateIndex(Integer.parseInt(ss[0]), modelStats.numStates); // Now split up middle bit and extract var info ss = ss[1].substring(ss[1].indexOf('(') + 1, ss[1].indexOf(')')).split(","); @@ -705,8 +725,8 @@ public void extractMCTransitions(IOUtils.MCTransitionConsumer sto continue; } checkLineSize(record, 3, 4); - int s = checkStateIndex(Integer.parseInt(record[0]), numStates); - int s2 = checkStateIndex(Integer.parseInt(record[1]), numStates); + int s = checkStateIndex(Integer.parseInt(record[0]), modelStats.numStates); + int s2 = checkStateIndex(Integer.parseInt(record[1]), modelStats.numStates); Value v = checkValue(record[2], eval); Object a = (record.length > 3) ? checkAction(record[3]) : null; storeTransition.accept(s, s2, v, a); @@ -734,9 +754,9 @@ public void extractMDPTransitions(IOUtils.MDPTransitionConsumer s continue; } checkLineSize(record, 4, 5); - int s = checkStateIndex(Integer.parseInt(record[0]), numStates); + int s = checkStateIndex(Integer.parseInt(record[0]), modelStats.numStates); int i = checkChoiceIndex(Integer.parseInt(record[1])); - int s2 = checkStateIndex(Integer.parseInt(record[2]), numStates); + int s2 = checkStateIndex(Integer.parseInt(record[2]), modelStats.numStates); Value v = checkValue(record[3], eval); Object a = (record.length > 4) ? checkAction(record[4]) : null; storeTransition.accept(s, i, s2, v, a); @@ -764,9 +784,9 @@ public void extractLTSTransitions(IOUtils.LTSTransitionConsumer storeTransition) continue; } checkLineSize(record, 3, 4); - int s = checkStateIndex(Integer.parseInt(record[0]), numStates); + int s = checkStateIndex(Integer.parseInt(record[0]), modelStats.numStates); int i = checkChoiceIndex(Integer.parseInt(record[1])); - int s2 = checkStateIndex(Integer.parseInt(record[2]), numStates); + int s2 = checkStateIndex(Integer.parseInt(record[2]), modelStats.numStates); Object a = (record.length > 3) ? checkAction(record[3]) : null; storeTransition.accept(s, i, s2, a); } @@ -798,7 +818,7 @@ public void extractLabelsAndInitialStates(BiConsumer storeLabe if (!st.isEmpty()) { // Split line String[] ss = st.split(":"); - int s = checkStateIndex(Integer.parseInt(ss[0].trim()), numStates); + int s = checkStateIndex(Integer.parseInt(ss[0].trim()), modelStats.numStates); ss = ss[1].trim().split(" "); for (int j = 0; j < ss.length; j++) { if (ss[j].isEmpty()) { @@ -925,7 +945,7 @@ public void extractStateRewards(int rewardIndex, BiConsumer void extractMCTransitionRewards(int rewardIndex, IOUtils.Transiti { if (rewardIndex < transRewardsReaders.size()) { RewardFile file = transRewardsReaders.get(rewardIndex); - file.extractMCTransitionRewards(storeReward, eval, numStates); + file.extractMCTransitionRewards(storeReward, eval, modelStats.numStates); } } @@ -943,7 +963,7 @@ public void extractMDPTransitionRewards(int rewardIndex, IOUtils.Transit { if (rewardIndex < transRewardsReaders.size()) { RewardFile file = transRewardsReaders.get(rewardIndex); - file.extractMDPTransitionRewards(storeReward, eval, numStates); + file.extractMDPTransitionRewards(storeReward, eval, modelStats.numStates); } } From 4980a2ef5c7beaace693c40244e9af3ca6c60b9f Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Fri, 31 Jan 2025 11:59:45 +0000 Subject: [PATCH 29/44] Explicit model import: push default variable info into superclass. --- prism/src/io/ExplicitModelImporter.java | 37 +++++++++++++++++++++++++ prism/src/io/PrismExplicitImporter.java | 26 +++++++++++------ 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/prism/src/io/ExplicitModelImporter.java b/prism/src/io/ExplicitModelImporter.java index c8bd7439d6..b49db2addd 100644 --- a/prism/src/io/ExplicitModelImporter.java +++ b/prism/src/io/ExplicitModelImporter.java @@ -26,6 +26,11 @@ package io; +import parser.ast.DeclarationInt; +import parser.ast.DeclarationType; +import parser.ast.Expression; +import parser.type.Type; +import parser.type.TypeInt; import prism.Evaluator; import prism.ModelInfo; import prism.PrismException; @@ -34,6 +39,9 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; +/** + * Interface for classes that import from explicit model sources. + */ public interface ExplicitModelImporter { /** @@ -204,4 +212,33 @@ default void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionStat * @param eval Evaluator for Value objects */ void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionStateRewardConsumer storeReward, Evaluator eval) throws PrismException; + + // Defaults for a single variable name when none is specified + + /** + * Get the default name for the (single) variable when none is specified. + * By default, this is "x". + */ + default String defaultVariableName() + { + return "x"; + } + + /** + * Get the default type for the (single) variable when none is specified. + * By default, this is {@code int}. + */ + default Type defaultVariableType() + { + return TypeInt.getInstance(); + } + + /** + * Get the default type declaration for the (single) variable when none is specified. + * By default, this is {@code [0..(numStates-1)]}. + */ + default DeclarationType defaultVariableDeclarationType() throws PrismException + { + return new DeclarationInt(Expression.Int(0), Expression.Int(getNumStates() - 1)); + } } diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index c81edce947..9d97dab05d 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -86,6 +86,7 @@ public class PrismExplicitImporter implements ExplicitModelImporter private int numVars; private List varNames; private List varTypes; + private List varDeclTypes; private int varMins[]; private int varMaxs[]; private int varRanges[]; @@ -299,11 +300,12 @@ private void buildModelInfo() throws PrismException if (statesFile != null) { extractVarInfoFromStatesFile(statesFile); } - // Otherwise store dummy variable info + // Otherwise store default variable info else { numVars = 1; - varNames = Collections.singletonList("x"); - varTypes = Collections.singletonList(TypeInt.getInstance()); + varNames = Collections.singletonList(defaultVariableName()); + varTypes = Collections.singletonList(defaultVariableType()); + varDeclTypes = Collections.singletonList(defaultVariableDeclarationType()); varMins = new int[] { 0 }; varMaxs = new int[] { getNumStates() - 1 }; varRanges = new int[] { getNumStates() - 1 }; @@ -358,11 +360,7 @@ public List getVarTypes() @Override public DeclarationType getVarDeclarationType(int i) throws PrismException { - if (varTypes.get(i) instanceof TypeInt) { - return new DeclarationInt(Expression.Int(varMins[i]), Expression.Int(varMaxs[i])); - } else { - return new DeclarationBool(); - } + return varDeclTypes.get(i); } @Override @@ -397,7 +395,8 @@ private void extractVarInfoFromStatesFile(File statesFile) throws PrismException varMins = new int[numVars]; varMaxs = new int[numVars]; varRanges = new int[numVars]; - varTypes = new ArrayList(); + varTypes = new ArrayList<>(); + varDeclTypes = new ArrayList<>(); // read remaining lines s = in.readLine(); lineNum++; @@ -449,6 +448,15 @@ private void extractVarInfoFromStatesFile(File statesFile) throws PrismException varMaxs[i]++; } } + // create variable declarations (need ranges) + for (i = 0; i < numVars; i++) { + if (varTypes.get(i) instanceof TypeBool) { + varDeclTypes.add(new DeclarationBool()); + } else { + varDeclTypes.add(new DeclarationInt(Expression.Int(varMins[i]), Expression.Int(varMaxs[i]))); + } + } + } catch (IOException e) { throw new PrismException("File I/O error reading from \"" + statesFile + "\""); } catch (NumberFormatException e) { From 97a268f3b2f6a05eacba6d40f083a07f6d9e0f73 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sat, 1 Feb 2025 15:35:57 +0000 Subject: [PATCH 30/44] New BasicModelInfo class, used by PrismExplicitImporter. Temporary storage for info needed by ModelInfo interface. Better than creating a new anonymous class each time and can be re-used by other importers. --- prism/src/io/PrismExplicitImporter.java | 133 +++++++--------------- prism/src/prism/BasicModelInfo.java | 141 ++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 95 deletions(-) create mode 100644 prism/src/prism/BasicModelInfo.java diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index 9d97dab05d..016d9bb25d 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -33,7 +33,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -53,12 +52,12 @@ import parser.State; import parser.ast.DeclarationBool; import parser.ast.DeclarationInt; -import parser.ast.DeclarationType; import parser.ast.Expression; import parser.ast.ExpressionIdent; import parser.type.Type; import parser.type.TypeBool; import parser.type.TypeInt; +import prism.BasicModelInfo; import prism.Evaluator; import prism.ModelInfo; import prism.ModelType; @@ -82,16 +81,8 @@ public class PrismExplicitImporter implements ExplicitModelImporter private List transRewardsFiles; private ModelType typeOverride; - // Model info extracted from files and then stored in a ModelInfo object - private int numVars; - private List varNames; - private List varTypes; - private List varDeclTypes; - private int varMins[]; - private int varMaxs[]; - private int varRanges[]; - private List labelNames; - private ModelInfo modelInfo; + // Model info extracted from files and then stored in a BasicModelInfo object + private BasicModelInfo basicModelInfo; // String stating the model type and how it was obtained private String modelTypeString; @@ -120,7 +111,6 @@ private class ModelStats // Regex for reward name protected static final Pattern REWARD_NAME_PATTERN = Pattern.compile("# Reward structure (\"([_a-zA-Z0-9]*)\")$"); - /** * Constructor * @param statesFile States file (may be {@code null}) @@ -218,10 +208,10 @@ public String sourceString() public ModelInfo getModelInfo() throws PrismException { // Construct lazily, as needed - if (modelInfo == null) { + if (basicModelInfo == null) { buildModelInfo(); } - return modelInfo; + return basicModelInfo; } @Override @@ -296,30 +286,6 @@ private void buildModelInfo() throws PrismException buildModelStats(); } - // Extract variable info from states, if available - if (statesFile != null) { - extractVarInfoFromStatesFile(statesFile); - } - // Otherwise store default variable info - else { - numVars = 1; - varNames = Collections.singletonList(defaultVariableName()); - varTypes = Collections.singletonList(defaultVariableType()); - varDeclTypes = Collections.singletonList(defaultVariableDeclarationType()); - varMins = new int[] { 0 }; - varMaxs = new int[] { getNumStates() - 1 }; - varRanges = new int[] { getNumStates() - 1 }; - } - - // Generate and store label names from the labels file, if available. - // This way, expressions can refer to the labels later on. - if (labelsFile != null) { - extractLabelNamesFromLabelsFile(labelsFile); - } else { - labelNames = new ArrayList<>(); - labelMap = new ArrayList<>(); - } - // Set model type: if no preference stated, try to autodetect ModelType modelType; if (typeOverride == null) { @@ -336,39 +302,25 @@ private void buildModelInfo() throws PrismException modelType = typeOverride; } - // Create and return ModelInfo object with above info - modelInfo = new ModelInfo() - { - @Override - public ModelType getModelType() - { - return modelType; - } - - @Override - public List getVarNames() - { - return varNames; - } - - @Override - public List getVarTypes() - { - return varTypes; - } - - @Override - public DeclarationType getVarDeclarationType(int i) throws PrismException - { - return varDeclTypes.get(i); - } - - @Override - public List getLabelNames() - { - return labelNames; - } - }; + // Store model info + basicModelInfo = new BasicModelInfo(modelType); + + // Extract variable info from states, if available + if (statesFile != null) { + extractVarInfoFromStatesFile(statesFile); + } + // Otherwise store default variable info + else { + basicModelInfo.getVarList().addVar(defaultVariableName(), defaultVariableDeclarationType(), -1); + } + + // Generate and store label names from the labels file, if available. + // This way, expressions can refer to the labels later on. + if (labelsFile != null) { + extractLabelNamesFromLabelsFile(labelsFile); + } else { + labelMap = new ArrayList<>(); + } } /** @@ -389,14 +341,12 @@ private void extractVarInfoFromStatesFile(File statesFile) throws PrismException if (s.charAt(0) != '(' || s.charAt(s.length() - 1) != ')') throw new PrismException("badly formatted state"); s = s.substring(1, s.length() - 1); - varNames = new ArrayList(Arrays.asList(s.split(","))); - numVars = varNames.size(); - // create arrays to store info about vars - varMins = new int[numVars]; - varMaxs = new int[numVars]; - varRanges = new int[numVars]; - varTypes = new ArrayList<>(); - varDeclTypes = new ArrayList<>(); + List varNames = new ArrayList<>(Arrays.asList(s.split(","))); + int numVars = varNames.size(); + // create arrays to (temporarily) store info about vars + int[] varMins = new int[numVars]; + int[] varMaxs = new int[numVars]; + List varTypes = new ArrayList<>(); // read remaining lines s = in.readLine(); lineNum++; @@ -439,21 +389,13 @@ private void extractVarInfoFromStatesFile(File statesFile) throws PrismException s = in.readLine(); lineNum++; } - // compute variable ranges - for (i = 0; i < numVars; i++) { - if (varTypes.get(i) instanceof TypeInt) { - varRanges[i] = varMaxs[i] - varMins[i]; - // if range = 0, increment maximum - we don't allow zero-range variables - if (varRanges[i] == 0) - varMaxs[i]++; - } - } - // create variable declarations (need ranges) + + // Add variables to the VarList for (i = 0; i < numVars; i++) { if (varTypes.get(i) instanceof TypeBool) { - varDeclTypes.add(new DeclarationBool()); + basicModelInfo.getVarList().addVar(varNames.get(i), new DeclarationBool(), -1); } else { - varDeclTypes.add(new DeclarationInt(Expression.Int(varMins[i]), Expression.Int(varMaxs[i]))); + basicModelInfo.getVarList().addVar(varNames.get(i), new DeclarationInt(Expression.Int(varMins[i]), Expression.Int(varMaxs[i])), -1); } } @@ -500,6 +442,7 @@ private void extractModelStatsFromTransFile(File transFile) throws PrismExceptio /** * Extract names of labels from the labels file. + * These are store in the label name list within basicModelInfo. * The "init" and "deadlock" labels are skipped, as they have special * meaning and are implicitly defined for all models. */ @@ -512,7 +455,7 @@ private void extractLabelNamesFromLabelsFile(File labelsFile) throws PrismExcept String labelsString = in.readLine(); Pattern label = Pattern.compile("(\\d+)=\"([^\"]+)\"\\s*"); Matcher matcher = label.matcher(labelsString); - labelNames = new ArrayList<>(); + List labelNames = basicModelInfo.getLabelNameList(); labelMap = new ArrayList<>(); while (matcher.find()) { // Check indices are ascending/contiguous @@ -639,7 +582,7 @@ private ModelType autodetectModelType(File transFile) @Override public void extractStates(IOUtils.StateDefnConsumer storeStateDefn) throws PrismException { - int numVars = modelInfo.getNumVars(); + int numVars = basicModelInfo.getNumVars(); // If there is no info, just assume that states comprise a single integer value if (getStatesFile() == null) { for (int s = 0; s < modelStats.numStates; s++) { @@ -907,7 +850,7 @@ public Map extractAllLabels() throws PrismException } else if (l == -2) { map.put("deadlock", bitsets[i]); } else if (l > -1) { - map.put(labelNames.get(l), bitsets[i]); + map.put(basicModelInfo.getLabelNameList().get(l), bitsets[i]); } } return map; diff --git a/prism/src/prism/BasicModelInfo.java b/prism/src/prism/BasicModelInfo.java new file mode 100644 index 0000000000..d43bc3c7a8 --- /dev/null +++ b/prism/src/prism/BasicModelInfo.java @@ -0,0 +1,141 @@ +//============================================================================== +// +// Copyright (c) 2025- +// Authors: +// * Dave Parker (University of Oxford) +// +//------------------------------------------------------------------------------ +// +// This file is part of PRISM. +// +// PRISM 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 2 of the License, or +// (at your option) any later version. +// +// PRISM 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 PRISM; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//============================================================================== + +package prism; + +import parser.VarList; +import parser.ast.DeclarationType; +import parser.type.Type; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * Simple storage of basic model info, implementing {@link ModelInfo}. + * Stores and provides access to mutable info about + * model type, variables, labels, etc. + */ +public class BasicModelInfo implements ModelInfo +{ + /** Model type */ + private ModelType modelType; + /** Variable list */ + private VarList varList; + /** Label names */ + private List labelNameList; + + // Constructors + + /** + * Construct a {@link BasicModelInfo} with the specified model type. + */ + public BasicModelInfo(ModelType modelType) + { + this.modelType = modelType; + varList = new VarList(); + labelNameList = new ArrayList<>(); + } + + // Setters/getters for basic model info storage + + /** + * Set the model type. + */ + public void setModelType(ModelType modelType) + { + this.modelType = modelType; + } + + /** + * Set the {@link VarList} used to store variable info. + */ + public void setVarList(VarList varList) + { + this.varList = varList; + } + + /** + * Set the list used to store label names. + */ + public void setLabelNameList(List labelNameList) + { + this.labelNameList = labelNameList; + } + + /** + * Get the {@link VarList} used to store variable info. + */ + public VarList getVarList() + { + return varList; + } + + /** + * Get the list used to store label names. + */ + public List getLabelNameList() + { + return labelNameList; + } + + // Methods to implement ModelInfo + + @Override + public ModelType getModelType() + { + return modelType; + } + + @Override + public List getVarNames() + { + return IntStream.range(0, varList.getNumVars()) + .mapToObj(varList::getName) + .collect(Collectors.toCollection(ArrayList::new)); + } + + @Override + public List getVarTypes() + { + return IntStream.range(0, varList.getNumVars()) + .mapToObj(varList::getType) + .collect(Collectors.toCollection(ArrayList::new)); + } + + @Override + public DeclarationType getVarDeclarationType(int i) + { + return varList.getDeclarationType(i); + } + + @Override + public List getLabelNames() + { + return labelNameList; + } +} From 44e0de3fa8fc67ede5ba4a4e306771da462f9179 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sat, 1 Feb 2025 13:06:59 +0000 Subject: [PATCH 31/44] New BasicRewardInfo class, used by PrismExplicitImporter. Temporary storage for info needed by RewardInfo interface. Better than creating a new anonymous class each time and can be re-used by other importers. Also fixes a bug in model import: rewardStructHasTransitionRewards(). PrismExplicitImporter was wrongly reporting that the model had no transition rewards. This didn't actually prevent transition rewards being used because ConstructRewards does not check the results of rewardStructHasTransitionRewards() for the case of imported rewards. An additional check that state/transition reward files for the same reward structure have matching names is also added. --- prism/src/io/PrismExplicitImporter.java | 43 +++++----- prism/src/prism/BasicRewardInfo.java | 103 ++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 prism/src/prism/BasicRewardInfo.java diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index 016d9bb25d..e5f9842a2d 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -44,7 +44,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import common.iterable.Reducible; import csv.BasicReader; import csv.CsvFormatException; import csv.CsvReader; @@ -58,6 +57,7 @@ import parser.type.TypeBool; import parser.type.TypeInt; import prism.BasicModelInfo; +import prism.BasicRewardInfo; import prism.Evaluator; import prism.ModelInfo; import prism.ModelType; @@ -99,8 +99,8 @@ private class ModelStats // Mapping from label indices in file to (non-built-in) label indices (also: -1=init, -2=deadlock) private List labelMap; - // Reward info extracted from files and then stored in a RewardInfo object - private RewardInfo rewardInfo; + // Reward info extracted from files and then stored in a BasicRewardInfo object + private BasicRewardInfo basicRewardInfo; // File(s) to read in rewards from private List stateRewardsReaders = new ArrayList<>(); @@ -257,10 +257,10 @@ public String getModelTypeString() public RewardInfo getRewardInfo() throws PrismException { // Construct lazily, as needed - if (rewardInfo == null) { + if (basicRewardInfo == null) { buildRewardInfo(); } - return rewardInfo; + return basicRewardInfo; } /** @@ -868,27 +868,24 @@ public Map extractAllLabels() throws PrismException */ private void buildRewardInfo() throws PrismException { - rewardInfo = new RewardInfo() - { - @Override - public List getRewardStructNames() - { - List rewardsReaders = stateRewardsReaders.size() >= transRewardsFiles.size() ? stateRewardsReaders : transRewardsReaders; - return Reducible.extend(rewardsReaders).map(f -> f.getName().orElse("")).collect(new ArrayList<>(rewardsReaders.size())); + basicRewardInfo = new BasicRewardInfo(); + int numRewards = Math.max(stateRewardsReaders.size(), transRewardsReaders.size()); + for (int r = 0; r < numRewards; r++) { + String stateRewardName = null; + String transRewardName = null; + if (r < stateRewardsReaders.size()) { + stateRewardName = stateRewardsReaders.get(r).getName().orElse(""); } - - @Override - public int getNumRewardStructs() - { - return Math.max(stateRewardsFiles.size(), transRewardsFiles.size()); + if (r < transRewardsReaders.size()) { + transRewardName = transRewardsReaders.get(r).getName().orElse(""); } - - @Override - public boolean rewardStructHasTransitionRewards(int r) - { - return false; + if (transRewardName != null && stateRewardName != null && !transRewardName.equals(stateRewardName)) { + throw new PrismException("Reward structure names do not match for state/transition rewards"); } - }; + basicRewardInfo.addReward(stateRewardName != null ? stateRewardName : transRewardName); + basicRewardInfo.setHasStateRewards(r, r < stateRewardsReaders.size()); + basicRewardInfo.setHasTransitionRewards(r, r < transRewardsReaders.size()); + } } @Override diff --git a/prism/src/prism/BasicRewardInfo.java b/prism/src/prism/BasicRewardInfo.java new file mode 100644 index 0000000000..796a0c710f --- /dev/null +++ b/prism/src/prism/BasicRewardInfo.java @@ -0,0 +1,103 @@ +//============================================================================== +// +// Copyright (c) 2025- +// Authors: +// * Dave Parker (University of Oxford) +// +//------------------------------------------------------------------------------ +// +// This file is part of PRISM. +// +// PRISM 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 2 of the License, or +// (at your option) any later version. +// +// PRISM 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 PRISM; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//============================================================================== + +package prism; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +/** + * Simple mutable storage of basic info about rewards of a model. + * Implements {@link RewardInfo}, but only provides info, not actual rewards. + */ +public class BasicRewardInfo implements RewardInfo +{ + /** Reward names */ + private final List rewardNameList = new ArrayList<>(); + /** Which rewards have state rewards */ + private final BitSet hasStateRewards = new BitSet(); + /** Which rewards have transition rewards */ + private final BitSet hasTransitionRewards = new BitSet(); + + /** + * Add a reward structure to the list. + * Use "" for {@code name} if it is unnamed. + * Assumes that it has state/transition rewards for now. + */ + public void addReward(String name) + { + rewardNameList.add(name); + hasStateRewards.set(rewardNameList.size() - 1); + hasTransitionRewards.set(rewardNameList.size() - 1); + } + + /** + * Specify whether reward structure r has state rewards. + * @param r Index of reward structure (indexed from 0). + * @param b Whether or not it has state rewards + */ + public void setHasStateRewards(int r, boolean b) + { + hasStateRewards.set(r, b); + } + + /** + * Specify whether reward structure r has transition rewards. + * @param r Index of reward structure (indexed from 0). + * @param b Whether or not it has transition rewards + */ + public void setHasTransitionRewards(int r, boolean b) + { + hasTransitionRewards.set(r, b); + } + + // Methods to implement RewardGenerator + + @Override + public List getRewardStructNames() + { + return rewardNameList; + } + + @Override + public int getNumRewardStructs() + { + return rewardNameList.size(); + } + + @Override + public boolean rewardStructHasStateRewards(int r) + { + return hasStateRewards.get(r); + } + + @Override + public boolean rewardStructHasTransitionRewards(int r) + { + return hasTransitionRewards.get(r); + } +} From 8ae6bde10c5c37e3197a2f3399d85c78db074464 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sun, 19 Jan 2025 00:26:11 +0000 Subject: [PATCH 32/44] Model import refactoring: store a list of import files in PrismCL. The interface to PrismExplicitImporter is also changed, so that rewards files can be added one by one. --- prism/src/io/PrismExplicitImporter.java | 96 +++++++++++-- prism/src/prism/PrismCL.java | 173 +++++++++++++----------- 2 files changed, 179 insertions(+), 90 deletions(-) diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index e5f9842a2d..ba5b2839a2 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -116,26 +116,96 @@ private class ModelStats * @param statesFile States file (may be {@code null}) * @param transFile Transitions file * @param labelsFile Labels file (may be {@code null}) - * @param stateRewardsFiles State rewards files list (can be empty) - * @param transRewardsFiles Transition rewards files list (can be empty) + * @param stateRewardsFiles State rewards files list (can be null/empty) + * @param transRewardsFiles Transition rewards files list (can be null/empty) * @param typeOverride Specified model type (null mean auto-detect it, or default to MDP if that cannot be done). */ public PrismExplicitImporter(File statesFile, File transFile, File labelsFile, List stateRewardsFiles, List transRewardsFiles, ModelType typeOverride) throws PrismException + { + setStatesFile(statesFile); + setTransFile(transFile); + setLabelsFile(labelsFile); + this.stateRewardsFiles = new ArrayList<>(); + this.stateRewardsReaders = new ArrayList<>(); + if (stateRewardsFiles != null) { + for (File stateRewardsFile : stateRewardsFiles) { + addStateRewardsFile(stateRewardsFile); + } + } + this.transRewardsFiles = new ArrayList<>(); + this.transRewardsReaders = new ArrayList<>(); + if (transRewardsFiles != null) { + for (File transRewardsFile : transRewardsFiles) { + addTransitionRewardsFile(transRewardsFile); + } + } + this.typeOverride = typeOverride; + } + + /** + * Constructor + * @param transFile Transitions file + * @param typeOverride Specified model type (null mean auto-detect it, or default to MDP if that cannot be done). + */ + public PrismExplicitImporter(File transFile, ModelType typeOverride) throws PrismException + { + this(null, transFile, null, null, null, typeOverride); + } + + /** + * Constructor + * @param transFile Transitions file + */ + public PrismExplicitImporter(File transFile) throws PrismException + { + this(null, null); + } + + /** + * Set the states file. + * @param statesFile States file (may be {@code null}) + */ + public void setStatesFile(File statesFile) { this.statesFile = statesFile; + } + + /** + * Set the transitions file. + * @param transFile Transitions file + */ + public void setTransFile(File transFile) + { this.transFile = transFile; + } + + /** + * Set the labels file. + * @param labelsFile Labels file (may be {@code null}) + */ + public void setLabelsFile(File labelsFile) + { this.labelsFile = labelsFile; - this.stateRewardsFiles = stateRewardsFiles == null ? new ArrayList<>() : stateRewardsFiles; - this.transRewardsFiles = transRewardsFiles == null ? new ArrayList<>() : transRewardsFiles; - this.typeOverride = typeOverride; - this.stateRewardsReaders = new ArrayList<>(this.stateRewardsFiles.size()); - for (File file : this.stateRewardsFiles) { - this.stateRewardsReaders.add(new PrismExplicitImporter.RewardFile(file)); - } - this.transRewardsReaders = new ArrayList<>(this.transRewardsFiles.size()); - for (File file : this.transRewardsFiles) { - this.transRewardsReaders.add(new PrismExplicitImporter.RewardFile(file)); - } + } + + /** + * Add a state rewards file. + * @param stateRewardsFile State rewards file + */ + public void addStateRewardsFile(File stateRewardsFile) throws PrismException + { + stateRewardsFiles.add(stateRewardsFile); + stateRewardsReaders.add(new RewardFile(stateRewardsFile)); + } + + /** + * Add a transition rewards file. + * @param transitionRewardsFile Transition rewards file + */ + public void addTransitionRewardsFile(File transitionRewardsFile) throws PrismException + { + transRewardsFiles.add(transitionRewardsFile); + transRewardsReaders.add(new RewardFile(transitionRewardsFile)); } /** diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 3dbe8adbec..943ed923f2 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -44,6 +44,7 @@ import io.ModelExportOptions; import io.ModelExportFormat; import io.ModelExportTask; +import io.PrismExplicitImporter; import parser.Values; import parser.ast.Expression; import parser.ast.ExpressionReward; @@ -73,11 +74,6 @@ public class PrismCL implements PrismModelListener // flags private boolean importpepa = false; private boolean importprismpp = false; - private boolean importtrans = false; - private boolean importstates = false; - private boolean importlabels = false; - private boolean importstaterewards = false; - private boolean importtransrewards = false; private boolean importinitdist = false; private boolean importresults = false; private boolean steadystate = false; @@ -108,6 +104,9 @@ public class PrismCL implements PrismModelListener private List modelExportTasks = new ArrayList<>(); private ModelExportOptions modelExportOptionsGlobal = new ModelExportOptions(); + // import info + private List modelImportSources = new ArrayList<>(); + // property info private List propertyIndices = null; private String propertyString = ""; @@ -128,10 +127,6 @@ public class PrismCL implements PrismModelListener private String mainLogFilename = "stdout"; private String settingsFilename = null; private String modelFilename = null; - private String importStatesFilename = null; - private String importLabelsFilename = null; - private List importStateRewardsFilenames = new ArrayList<>(); - private List importTransRewardsFilenames = new ArrayList<>(); private String importInitDistFilename = null; private String importResultsFilename = null; private String importModelWarning = null; @@ -207,6 +202,19 @@ public class PrismCL implements PrismModelListener private boolean exactConstants = false; + /** Specification of a single file for model import. */ + private class ModelImportSource + { + private ModelExportTask.ModelExportEntity entity; + private File file; + + public ModelImportSource(ModelExportTask.ModelExportEntity entity, ModelExportFormat format, File file) + { + this.entity = entity; + this.file = file; + } + } + /** * Entry point: call run method, catch CuddOutOfMemoryException */ @@ -648,24 +656,8 @@ private void doParsing() String prismppParamsList[] = ("? " + prismppParams).split(" "); modulesFile = prism.importPrismPreprocFile(new File(modelFilename), prismppParamsList); prism.loadPRISMModel(modulesFile); - } else if (importtrans) { - if (importstates) { - sf = new File(importStatesFilename); - } - if (importlabels) { - lf = new File(importLabelsFilename); - } - if (importstaterewards) { - for (int k = 0; k < importStateRewardsFilenames.size(); k++) { - srf.add(new File(importStateRewardsFilenames.get(k))); - } - } - if (importtransrewards) { - for (int k = 0; k < importTransRewardsFilenames.size(); k++) { - trf.add(new File(importTransRewardsFilenames.get(k))); - } - } - prism.loadModelFromExplicitFiles(sf, new File(modelFilename), lf, srf, trf, typeOverride); + } else if (!modelImportSources.isEmpty()) { + sortModelImports(); } else { modulesFile = prism.parseModelFile(new File(modelFilename), typeOverride); prism.loadPRISMModel(modulesFile); @@ -721,6 +713,52 @@ else if (!propertyString.equals("")) { } } + /** + * If importing a model, process the specification and import. + */ + private void sortModelImports() throws PrismException + { + // Exactly one model/transitions source should be provided + int numModelSources = (int) modelImportSources.stream().filter(s -> s.entity == ModelExportTask.ModelExportEntity.MODEL).count(); + if (numModelSources < 1) { + throw new PrismException("No transitions specified for model import"); + } + if (numModelSources > 1) { + throw new PrismException("Multiple model imports provided"); + } + // Add all requested files to the importer + ModelImportSource modelSource = modelImportSources.stream().filter(s -> s.entity == ModelExportTask.ModelExportEntity.MODEL).findFirst().get(); + PrismExplicitImporter importer = new PrismExplicitImporter(modelSource.file, typeOverride); + for (ModelImportSource modelImportSource : modelImportSources) { + switch (modelImportSource.entity) { + case MODEL: + // Skip; already handled + break; + case STATES: + if (importer.getStatesFile() != null) { + throw new PrismException("Multiple state files provided for model import"); + } + importer.setStatesFile(modelImportSource.file); + break; + case LABELS: + if (importer.getLabelsFile() != null) { + throw new PrismException("Multiple label files provided for model import"); + } + importer.setLabelsFile(modelImportSource.file); + break; + case STATE_REWARDS: + importer.addStateRewardsFile(modelImportSource.file); + break; + case TRANSITION_REWARDS: + importer.addTransitionRewardsFile(modelImportSource.file); + break; + default: + throw new PrismException("Unknown model import entity"); + } + } + prism.loadModelFromExplicitFiles(importer); + } + /** * Sort out which properties need checking. */ @@ -1218,8 +1256,9 @@ else if (sw.equals("importmodel")) { // import transition matrix from explicit format else if (sw.equals("importtrans")) { if (i < args.length - 1) { - importtrans = true; + // Recall model name in case needed as basename for model exprts modelFilename = args[++i]; + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.MODEL, ModelExportFormat.EXPLICIT, new File(modelFilename))); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1227,8 +1266,7 @@ else if (sw.equals("importtrans")) { // import states for explicit model import else if (sw.equals("importstates")) { if (i < args.length - 1) { - importstates = true; - importStatesFilename = args[++i]; + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.STATES, ModelExportFormat.EXPLICIT, new File(args[++i]))); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1236,8 +1274,7 @@ else if (sw.equals("importstates")) { // import labels for explicit model import else if (sw.equals("importlabels")) { if (i < args.length - 1) { - importlabels = true; - importLabelsFilename = args[++i]; + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.LABELS, ModelExportFormat.EXPLICIT, new File(args[++i]))); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1245,8 +1282,7 @@ else if (sw.equals("importlabels")) { // import state rewards for explicit model import else if (sw.equals("importstaterewards")) { if (i < args.length - 1) { - importstaterewards = true; - importStateRewardsFilenames.add(args[++i]); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.STATE_REWARDS, ModelExportFormat.EXPLICIT, new File(args[++i]))); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1254,8 +1290,7 @@ else if (sw.equals("importstaterewards")) { // import trans rewards for explicit model import else if (sw.equals("importtransrewards")) { if (i < args.length - 1) { - importtransrewards = true; - importTransRewardsFilenames.add(args[++i]); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, ModelExportFormat.EXPLICIT, new File(args[++i]))); } else { errorAndExit("No file specified for -" + sw + " switch"); } @@ -1817,7 +1852,7 @@ private void processFileNames(List filenameArgs) throws PrismException if (filenameArgs.size() > 2) { errorAndExit("Invalid argument syntax"); } - if (importtrans) { + if (!modelImportSources.isEmpty()) { if (filenameArgs.size() > 1) { errorAndExit("Two models provided (" + filenameArgs.get(0) + ", " + modelFilename + ")"); } else if (filenameArgs.size() == 1) { @@ -1860,37 +1895,29 @@ private void processImportModelSwitch(String filesOptionsString) throws PrismExc for (String ext : exts) { // Items to import if (ext.equals("all")) { - importtrans = true; modelFilename = basename + ".tra"; - importstates = true; - importStatesFilename = basename + ".sta"; - importlabels = true; - importLabelsFilename = basename + ".lab"; - getStateRewardsFilenames(basename, false); - getTransRewardsFilenames(basename, false); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.MODEL, ModelExportFormat.EXPLICIT, new File(basename + ".tra"))); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.STATES, ModelExportFormat.EXPLICIT, new File(basename + ".sta"))); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.LABELS, ModelExportFormat.EXPLICIT, new File(basename + ".lab"))); + addStateRewardImports(basename, false); + addTransitionRewardImports(basename, false); } else if (ext.equals("tra")) { - importtrans = true; modelFilename = basename + ".tra"; + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.MODEL, ModelExportFormat.EXPLICIT, new File(basename + ".tra"))); } else if (ext.equals("sta")) { - importstates = true; - importStatesFilename = basename + ".sta"; + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.STATES, ModelExportFormat.EXPLICIT, new File(basename + ".sta"))); } else if (ext.equals("lab")) { - importlabels = true; - importLabelsFilename = basename + ".lab"; + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.LABELS, ModelExportFormat.EXPLICIT, new File(basename + ".lab"))); } else if (ext.equals("srew")) { - getStateRewardsFilenames(basename, true); + addStateRewardImports(basename, true); } else if (ext.equals("trew")) { - getTransRewardsFilenames(basename, true); + addTransitionRewardImports(basename, true); } // Unknown extension else { throw new PrismException("Unknown extension \"" + ext + "\" for -importmodel switch"); } } - // Check at least the transition matrix was imported - if (!importtrans) { - throw new PrismException("You must import the transition matrix when using -importmodel (use option \"tra\" or \"all\")"); - } // No options supported currently /*// Process options String options[] = optionsString.split(","); @@ -1907,27 +1934,24 @@ private void processImportModelSwitch(String filesOptionsString) throws PrismExc /** * Given a file basename, find corresponding .srew files - * and add them to the {@code importStateRewardsFilenames} list. + * and add them to {@code modelImportSources}. * "corresponding" means basename.srew, or a set basename1.srew, ... * If any are present, {@code importstaterewards} is set to true. * - * If {@code assumeExists} is true, then we add basename.srew - * to {@code importStateRewardsFilenames} regardless, typically - * because the user has told us it should be there. + * If {@code assumeExists} is true, then we add basename.srew regardless, + * typically because the user has told us it should be there. */ - private void getStateRewardsFilenames(String basename, boolean assumeExists) + private void addStateRewardImports(String basename, boolean assumeExists) { boolean found = false; if (new File(basename + ".srew").exists()) { - importstaterewards = true; - importStateRewardsFilenames.add(basename + ".srew"); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.STATE_REWARDS, ModelExportFormat.EXPLICIT, new File(basename + ".srew"))); found = true; } else { int index = 1; while (true) { if (new File(basename + String.valueOf(index) + ".srew").exists()) { - importstaterewards = true; - importStateRewardsFilenames.add(basename + String.valueOf(index) + ".srew"); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.STATE_REWARDS, ModelExportFormat.EXPLICIT, new File(basename + String.valueOf(index) + ".srew"))); found = true; index++; } else { @@ -1936,34 +1960,30 @@ private void getStateRewardsFilenames(String basename, boolean assumeExists) } } if (assumeExists && !found) { - importstaterewards = true; - importStateRewardsFilenames.add(basename + ".srew"); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.STATE_REWARDS, ModelExportFormat.EXPLICIT, new File(basename + ".srew"))); } } /** * Given a file basename, find corresponding .trew files - * and add them to the {@code importTransRewardsFilenames} list. + * and add them to {@code modelImportSources}. * "corresponding" means basename.srew, or a set basename1.srew, ... * If any are present, {@code importtransrewards} is set to true. * - * If {@code assumeExists} is true, then we add basename.srew - * to {@code importTransRewardsFilenames} regardless, typically - * because the user has told us it should be there. + * If {@code assumeExists} is true, then we add basename.srew regardless, + * typically because the user has told us it should be there. */ - private void getTransRewardsFilenames(String basename, boolean assumeExists) + private void addTransitionRewardImports(String basename, boolean assumeExists) { boolean found = false; if (new File(basename + ".trew").exists()) { - importtransrewards = true; - importTransRewardsFilenames.add(basename + ".trew"); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, ModelExportFormat.EXPLICIT, new File(basename + ".trew"))); found = true; } else { int index = 1; while (true) { if (new File(basename + String.valueOf(index) + ".trew").exists()) { - importtransrewards = true; - importTransRewardsFilenames.add(basename + String.valueOf(index) + ".trew"); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, ModelExportFormat.EXPLICIT, new File(basename + String.valueOf(index) + ".trew"))); found = true; index++; } else { @@ -1972,8 +1992,7 @@ private void getTransRewardsFilenames(String basename, boolean assumeExists) } } if (assumeExists && !found) { - importtransrewards = true; - importTransRewardsFilenames.add(basename + ".trew"); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, ModelExportFormat.EXPLICIT, new File(basename + ".trew"))); } } From eb400fa5ba89dd681e6daf324f280f9acb02b4c4 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Thu, 13 Feb 2025 08:18:00 +0000 Subject: [PATCH 33/44] Convert ExplicitModelImporter from interface to abstract class. --- prism/src/io/ExplicitModelImporter.java | 60 +++++++++++++------------ prism/src/io/PrismExplicitImporter.java | 2 +- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/prism/src/io/ExplicitModelImporter.java b/prism/src/io/ExplicitModelImporter.java index b49db2addd..fee44c75d3 100644 --- a/prism/src/io/ExplicitModelImporter.java +++ b/prism/src/io/ExplicitModelImporter.java @@ -40,73 +40,77 @@ import java.util.function.Consumer; /** - * Interface for classes that import from explicit model sources. + * Base class for importers from explicit model sources. */ -public interface ExplicitModelImporter +public abstract class ExplicitModelImporter { /** * Does this importer provide info about state definitions? */ - boolean providesStates(); + public abstract boolean providesStates(); /** * Does this importer provide info about labels? */ - boolean providesLabels(); + public abstract boolean providesLabels(); /** * Get a string summarising the source, e.g. the list of files read in. */ - String sourceString(); + public abstract String sourceString(); /** * Get info about the model. */ - ModelInfo getModelInfo() throws PrismException; + public abstract ModelInfo getModelInfo() throws PrismException; /** * Get the number of states. */ - int getNumStates() throws PrismException; + public abstract int getNumStates() throws PrismException; /** * Get the total number of choices (for nondeterministic models). */ - int getNumChoices() throws PrismException; + public abstract int getNumChoices() throws PrismException; /** * Get the total number of transitions. */ - int getNumTransitions() throws PrismException; + public abstract int getNumTransitions() throws PrismException; /** * Get a string stating the model type and how it was obtained. */ - String getModelTypeString(); + public String getModelTypeString() throws PrismException + { + // By default, just return the type, no explanation + return getModelInfo().getModelType().toString(); + } /** * Get info about the rewards. */ - RewardInfo getRewardInfo() throws PrismException; + public abstract RewardInfo getRewardInfo() throws PrismException; /** * Extract state definitions (variable values). * Calls {@code storeStateDefn(s, i, o)} for each state s, variable (index) i and variable value o. * @param storeStateDefn Function to be called for each variable value of each state */ - void extractStates(IOUtils.StateDefnConsumer storeStateDefn) throws PrismException; + public abstract void extractStates(IOUtils.StateDefnConsumer storeStateDefn) throws PrismException; /** * Compute the maximum number of choices (in a nondeterministic model). */ - int computeMaxNumChoices() throws PrismException; + public abstract int computeMaxNumChoices() throws PrismException; /** * Extract the (Markov chain) transitions from a .tra file. * The transition probabilities/rates are assumed to be of type double. * @param storeTransition Function to be called for each transition */ - default void extractMCTransitions(IOUtils.MCTransitionConsumer storeTransition) throws PrismException + public void extractMCTransitions(IOUtils.MCTransitionConsumer storeTransition) throws PrismException { extractMCTransitions(storeTransition, Evaluator.forDouble()); } @@ -117,14 +121,14 @@ default void extractMCTransitions(IOUtils.MCTransitionConsumer storeTran * @param storeTransition Function to be called for each transition * @param eval Evaluator for Value objects */ - void extractMCTransitions(IOUtils.MCTransitionConsumer storeTransition, Evaluator eval) throws PrismException; + public abstract void extractMCTransitions(IOUtils.MCTransitionConsumer storeTransition, Evaluator eval) throws PrismException; /** * Extract the (Markov decision process) transitions from a .tra file. * The transition probabilities/rates are assumed to be of type double. * @param storeTransition Function to be called for each transition */ - default void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTransition) throws PrismException + public void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTransition) throws PrismException { extractMDPTransitions(storeTransition, Evaluator.forDouble()); } @@ -135,13 +139,13 @@ default void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTr * @param storeTransition Function to be called for each transition * @param eval Evaluator for Value objects */ - void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTransition, Evaluator eval) throws PrismException; + public abstract void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTransition, Evaluator eval) throws PrismException; /** * Extract the (labelled transition system) transitions from a .tra file. * @param storeTransition Function to be called for each transition */ - void extractLTSTransitions(IOUtils.LTSTransitionConsumer storeTransition) throws PrismException; + public abstract void extractLTSTransitions(IOUtils.LTSTransitionConsumer storeTransition) throws PrismException; /** * Extract info about state labellings and initial states. @@ -151,7 +155,7 @@ default void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTr * @param storeLabel Function to be called for each state satisfying a label * @param storeInit Function to be called for each initial stat */ - void extractLabelsAndInitialStates(BiConsumer storeLabel, Consumer storeInit) throws PrismException; + public abstract void extractLabelsAndInitialStates(BiConsumer storeLabel, Consumer storeInit) throws PrismException; /** * Extract the state rewards for a given reward structure index. @@ -159,7 +163,7 @@ default void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTr * @param rewardIndex Index of reward structure to extract (0-indexed) * @param storeReward Function to be called for each reward */ - default void extractStateRewards(int rewardIndex, BiConsumer storeReward) throws PrismException + public void extractStateRewards(int rewardIndex, BiConsumer storeReward) throws PrismException { extractStateRewards(rewardIndex, storeReward, Evaluator.forDouble()); } @@ -171,7 +175,7 @@ default void extractStateRewards(int rewardIndex, BiConsumer st * @param storeReward Function to be called for each reward * @param eval Evaluator for Value objects */ - void extractStateRewards(int rewardIndex, BiConsumer storeReward, Evaluator eval) throws PrismException; + public abstract void extractStateRewards(int rewardIndex, BiConsumer storeReward, Evaluator eval) throws PrismException; /** * Extract the (Markov chain) transition rewards for a given reward structure index. @@ -179,7 +183,7 @@ default void extractStateRewards(int rewardIndex, BiConsumer st * @param rewardIndex Index of reward structure to extract (0-indexed) * @param storeReward Function to be called for each reward */ - default void extractMCTransitionRewards(int rewardIndex, IOUtils.TransitionRewardConsumer storeReward) throws PrismException + public void extractMCTransitionRewards(int rewardIndex, IOUtils.TransitionRewardConsumer storeReward) throws PrismException { extractMCTransitionRewards(rewardIndex, storeReward, Evaluator.forDouble()); } @@ -191,7 +195,7 @@ default void extractMCTransitionRewards(int rewardIndex, IOUtils.TransitionRewar * @param storeReward Function to be called for each reward * @param eval Evaluator for Value objects */ - void extractMCTransitionRewards(int rewardIndex, IOUtils.TransitionRewardConsumer storeReward, Evaluator eval) throws PrismException; + public abstract void extractMCTransitionRewards(int rewardIndex, IOUtils.TransitionRewardConsumer storeReward, Evaluator eval) throws PrismException; /** * Extract the (Markov decision process) transition rewards for a given reward structure index. @@ -199,7 +203,7 @@ default void extractMCTransitionRewards(int rewardIndex, IOUtils.TransitionRewar * @param rewardIndex Index of reward structure to extract (0-indexed) * @param storeReward Function to be called for each reward */ - default void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionStateRewardConsumer storeReward) throws PrismException + public void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionStateRewardConsumer storeReward) throws PrismException { extractMDPTransitionRewards(rewardIndex, storeReward, Evaluator.forDouble()); } @@ -211,7 +215,7 @@ default void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionStat * @param storeReward Function to be called for each reward * @param eval Evaluator for Value objects */ - void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionStateRewardConsumer storeReward, Evaluator eval) throws PrismException; + public abstract void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionStateRewardConsumer storeReward, Evaluator eval) throws PrismException; // Defaults for a single variable name when none is specified @@ -219,7 +223,7 @@ default void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionStat * Get the default name for the (single) variable when none is specified. * By default, this is "x". */ - default String defaultVariableName() + public String defaultVariableName() { return "x"; } @@ -228,7 +232,7 @@ default String defaultVariableName() * Get the default type for the (single) variable when none is specified. * By default, this is {@code int}. */ - default Type defaultVariableType() + public Type defaultVariableType() { return TypeInt.getInstance(); } @@ -237,7 +241,7 @@ default Type defaultVariableType() * Get the default type declaration for the (single) variable when none is specified. * By default, this is {@code [0..(numStates-1)]}. */ - default DeclarationType defaultVariableDeclarationType() throws PrismException + public DeclarationType defaultVariableDeclarationType() throws PrismException { return new DeclarationInt(Expression.Int(0), Expression.Int(getNumStates() - 1)); } diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index ba5b2839a2..23623356e8 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -71,7 +71,7 @@ /** * Class to manage importing models from PRISM explicit files (.tra, .sta, etc.) */ -public class PrismExplicitImporter implements ExplicitModelImporter +public class PrismExplicitImporter extends ExplicitModelImporter { // What to import: files and type override private File statesFile; From e411f16c1153c2e8679e31206370cfcf59a5dbb5 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Thu, 13 Feb 2025 07:51:55 +0000 Subject: [PATCH 34/44] Refactor handling of imported transition reward indexing. For Markov chains, the importer is now responsible for providing transition rewards indexed either by the transition's offset within the state or by the successor state. The former is used by the explicit engine, the latter by the symbolic engine. The user calls setTransitionRewardIndexing to specify which indexing is required. There is also a new method setModel in ExplicitModelImporter which can be used to provide access to the model in case needed during transition reward processing. Notably this is used when extracting rewards from a .trew file for use in the explicit engine (which requires conversion of state->offset indexing). This logic is now moved into PrismExplicitImporter. --- prism/src/explicit/ExplicitFiles2Rewards.java | 14 +--- prism/src/io/ExplicitModelImporter.java | 62 ++++++++++++--- prism/src/io/IOUtils.java | 10 +++ prism/src/io/PrismExplicitImporter.java | 77 +++++++++++++------ .../symbolic/build/ExplicitFiles2MTBDD.java | 11 ++- 5 files changed, 125 insertions(+), 49 deletions(-) diff --git a/prism/src/explicit/ExplicitFiles2Rewards.java b/prism/src/explicit/ExplicitFiles2Rewards.java index 6710f3f24a..b98d08fb88 100644 --- a/prism/src/explicit/ExplicitFiles2Rewards.java +++ b/prism/src/explicit/ExplicitFiles2Rewards.java @@ -71,6 +71,8 @@ public ExplicitFiles2Rewards(PrismComponent parent, ExplicitModelImporter import this.importer = importer; this.model = (Model) model; this.eval = eval; + // Pass model to importer + this.importer.setModel(this.model); // Initialise storage rewards = new RewardsSimple[importer.getRewardInfo().getNumRewardStructs()]; } @@ -160,17 +162,7 @@ protected void storeStateReward(int r, int s, Value v) */ protected void storeMCTransitionReward(int r, int s, int s2, Value v) { - // Find successor index for state s2 (from state s) - SuccessorsIterator it = model.getSuccessors(s); - int i = 0; - while (it.hasNext()) { - if (it.nextInt() == s2) { - rewards[r].setTransitionReward(s, i, v); - return; - } - i++; - } - // No matching transition found + rewards[r].setTransitionReward(s, s2, v); } /** diff --git a/prism/src/io/ExplicitModelImporter.java b/prism/src/io/ExplicitModelImporter.java index fee44c75d3..3bcd074aec 100644 --- a/prism/src/io/ExplicitModelImporter.java +++ b/prism/src/io/ExplicitModelImporter.java @@ -44,6 +44,40 @@ */ public abstract class ExplicitModelImporter { + // Importer config or cached information + + /** How transition rewards are indexed */ + public enum TransitionRewardIndexing { + OFFSET, // Offset within rewards for state/choice + STATE // Index of successor state + }; + protected TransitionRewardIndexing transitionRewardIndexing = TransitionRewardIndexing.OFFSET; + + /** + * Specify how transition rewards should be supplied when extracted. + */ + public void setTransitionRewardIndexing(TransitionRewardIndexing transitionRewardIndexing) + { + this.transitionRewardIndexing = transitionRewardIndexing; + } + + /** + * Access provide to a model (usually constructed earlier using this importer) + * which can be looked up to identify transition info (usually for extracting rewards). + */ + protected explicit.Model modelLookup; + + /** + * Provide access to a model (usually constructed earlier using this importer) + * which can be looked up to identify transition info (usually for extracting rewards). + */ + public void setModel(explicit.Model modelLookup) + { + this.modelLookup = modelLookup; + } + + // Methods to be implemented by an importer + /** * Does this importer provide info about state definitions? */ @@ -106,7 +140,7 @@ public String getModelTypeString() throws PrismException public abstract int computeMaxNumChoices() throws PrismException; /** - * Extract the (Markov chain) transitions from a .tra file. + * Extract the (Markov chain) transitions. * The transition probabilities/rates are assumed to be of type double. * @param storeTransition Function to be called for each transition */ @@ -116,7 +150,7 @@ public void extractMCTransitions(IOUtils.MCTransitionConsumer storeTrans } /** - * Extract the (Markov chain) transitions from a .tra file. + * Extract the (Markov chain) transitions. * The transition probabilities/rates are assumed to be of type Value. * @param storeTransition Function to be called for each transition * @param eval Evaluator for Value objects @@ -124,7 +158,7 @@ public void extractMCTransitions(IOUtils.MCTransitionConsumer storeTrans public abstract void extractMCTransitions(IOUtils.MCTransitionConsumer storeTransition, Evaluator eval) throws PrismException; /** - * Extract the (Markov decision process) transitions from a .tra file. + * Extract the (Markov decision process) transitions. * The transition probabilities/rates are assumed to be of type double. * @param storeTransition Function to be called for each transition */ @@ -134,7 +168,7 @@ public void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTra } /** - * Extract the (Markov decision process) transitions from a .tra file. + * Extract the (Markov decision process) transitions. * The transition probabilities/rates are assumed to be of type Value. * @param storeTransition Function to be called for each transition * @param eval Evaluator for Value objects @@ -142,7 +176,7 @@ public void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTra public abstract void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTransition, Evaluator eval) throws PrismException; /** - * Extract the (labelled transition system) transitions from a .tra file. + * Extract the (labelled transition system) transitions. * @param storeTransition Function to be called for each transition */ public abstract void extractLTSTransitions(IOUtils.LTSTransitionConsumer storeTransition) throws PrismException; @@ -179,7 +213,10 @@ public void extractStateRewards(int rewardIndex, BiConsumer sto /** * Extract the (Markov chain) transition rewards for a given reward structure index. - * The transition probabilities/rates are assumed to be of type double. + * These are supplied as tuples (s,i,v) where s is the (source) state and v is the reward value. + * The index i is either the offset within the state or the index of the successor state, + * depending on what has been specified with {@link #setTransitionRewardIndexing(TransitionRewardIndexing)}. + * The reward values are assumed to be of type double. * @param rewardIndex Index of reward structure to extract (0-indexed) * @param storeReward Function to be called for each reward */ @@ -190,7 +227,10 @@ public void extractMCTransitionRewards(int rewardIndex, IOUtils.TransitionReward /** * Extract the (Markov chain) transition rewards for a given reward structure index. - * The transition probabilities/rates are assumed to be of type Value. + * These are supplied as tuples (s,i,v) where s is the (source) state and v is the reward value. + * The index i is either the offset within the state or the index of the successor state, + * depending on what has been specified with {@link #setTransitionRewardIndexing(TransitionRewardIndexing)}. + * The reward values are assumed to be of type Value. * @param rewardIndex Index of reward structure to extract (0-indexed) * @param storeReward Function to be called for each reward * @param eval Evaluator for Value objects @@ -199,7 +239,9 @@ public void extractMCTransitionRewards(int rewardIndex, IOUtils.TransitionReward /** * Extract the (Markov decision process) transition rewards for a given reward structure index. - * The transition probabilities/rates are assumed to be of type double. + * These are supplied as tuples (s,i,s2,v) where s is the (source) state, + * i is the choice index and v is the reward value. + * The reward values are assumed to be of type double. * @param rewardIndex Index of reward structure to extract (0-indexed) * @param storeReward Function to be called for each reward */ @@ -210,7 +252,9 @@ public void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionState /** * Extract the (Markov decision process) transition rewards for a given reward structure index. - * The transition probabilities/rates are assumed to be of type Value. + * These are supplied as tuples (s,i,s2,v) where s is the (source) state, + * i is the choice index and v is the reward value. + * The reward values are assumed to be of type Value. * @param rewardIndex Index of reward structure to extract (0-indexed) * @param storeReward Function to be called for each reward * @param eval Evaluator for Value objects diff --git a/prism/src/io/IOUtils.java b/prism/src/io/IOUtils.java index 8be09b660c..cc2b0801cb 100644 --- a/prism/src/io/IOUtils.java +++ b/prism/src/io/IOUtils.java @@ -100,8 +100,18 @@ public interface TransitionRewardConsumer { * Functional interface for a consumer accepting transition-successor-state rewards (s,i,s2,v), * i.e., source state s, index i, target state s2, value v. */ + // TODO REMOVE @FunctionalInterface public interface TransitionStateRewardConsumer { void accept(int s, int i, int s2, V v) throws PrismException; } + + /** + * Functional interface for a consumer accepting transition-successor-state rewards (s,i,j,v), + * i.e., source state s, index one i, index two j, target state s2, value v. + */ + @FunctionalInterface + public interface TransitionSuccRewardConsumer { + void accept(int s, int i, int j, V v) throws PrismException; + } } diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index 23623356e8..684d4a1756 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -47,6 +47,9 @@ import csv.BasicReader; import csv.CsvFormatException; import csv.CsvReader; +import explicit.DTMCSimple; +import explicit.ModelExplicit; +import explicit.SuccessorsIterator; import param.BigRational; import parser.State; import parser.ast.DeclarationBool; @@ -963,7 +966,7 @@ public void extractStateRewards(int rewardIndex, BiConsumer void extractMCTransitionRewards(int rewardIndex, IOUtils.Transiti { if (rewardIndex < transRewardsReaders.size()) { RewardFile file = transRewardsReaders.get(rewardIndex); - file.extractMCTransitionRewards(storeReward, eval, modelStats.numStates); + file.extractMCTransitionRewards(storeReward, eval); } } @@ -981,11 +984,11 @@ public void extractMDPTransitionRewards(int rewardIndex, IOUtils.Transit { if (rewardIndex < transRewardsReaders.size()) { RewardFile file = transRewardsReaders.get(rewardIndex); - file.extractMDPTransitionRewards(storeReward, eval, modelStats.numStates); + file.extractMDPTransitionRewards(storeReward, eval); } } - public static class RewardFile + public class RewardFile { protected final File file; protected final Optional name; @@ -1005,11 +1008,10 @@ public Optional getName() * Extract the state rewards from a .srew file. * The rewards are assumed to be of type double. * @param storeReward Function to be called for each reward - * @param numStates Number of states in the associated model */ - protected void extractStateRewards(BiConsumer storeReward, int numStates) throws PrismException + protected void extractStateRewards(BiConsumer storeReward) throws PrismException { - extractStateRewards(storeReward, Evaluator.forDouble(), numStates); + extractStateRewards(storeReward, Evaluator.forDouble()); } /** @@ -1017,9 +1019,8 @@ protected void extractStateRewards(BiConsumer storeReward, int * The rewards are assumed to be of type Value. * @param storeReward Function to be called for each reward * @param eval Evaluator for Value objects - * @param numStates Number of states in the associated model */ - protected void extractStateRewards(BiConsumer storeReward, Evaluator eval, int numStates) throws PrismException + protected void extractStateRewards(BiConsumer storeReward, Evaluator eval) throws PrismException { int lineNum = 0; try (BufferedReader in = new BufferedReader(new FileReader(file))) { @@ -1033,7 +1034,7 @@ protected void extractStateRewards(BiConsumer storeRewar continue; } checkLineSize(record, 2, 2); - int s = checkStateIndex(Integer.parseInt(record[0]), numStates); + int s = checkStateIndex(Integer.parseInt(record[0]), modelStats.numStates); Value v = checkValue(record[1], eval); storeReward.accept(s, v); } @@ -1049,11 +1050,10 @@ protected void extractStateRewards(BiConsumer storeRewar * Extract the (Markov chain) transition rewards from a .trew file. * The rewards are assumed to be of type double. * @param storeReward Function to be called for each reward - * @param numStates Number of states in the associated model */ - protected void extractMCTransitionRewards(IOUtils.TransitionRewardConsumer storeReward, int numStates) throws PrismException + protected void extractMCTransitionRewards(IOUtils.TransitionRewardConsumer storeReward) throws PrismException { - extractMCTransitionRewards(storeReward, Evaluator.forDouble(), numStates); + extractMCTransitionRewards(storeReward, Evaluator.forDouble()); } /** @@ -1061,10 +1061,17 @@ protected void extractMCTransitionRewards(IOUtils.TransitionRewardConsumer void extractMCTransitionRewards(IOUtils.TransitionRewardConsumer storeReward, Evaluator eval, int numStates) throws PrismException + protected void extractMCTransitionRewards(IOUtils.TransitionRewardConsumer storeReward, Evaluator eval) throws PrismException { + // Check that we have access to a model if needed for transition indexing + // If not, we build one via this importer + if (transitionRewardIndexing == TransitionRewardIndexing.OFFSET && modelLookup == null) { + modelLookup = new DTMCSimple<>(); + ((ModelExplicit) modelLookup).setEvaluator(eval); + ((ModelExplicit) modelLookup).buildFromExplicitImport(PrismExplicitImporter.this); + } + int lineNum = 0; try (BufferedReader in = new BufferedReader(new FileReader(file))) { lineNum += skipCommentAndFirstLine(in); @@ -1077,10 +1084,32 @@ protected void extractMCTransitionRewards(IOUtils.TransitionRewardConsum continue; } checkLineSize(record, 3, 3); - int s = checkStateIndex(Integer.parseInt(record[0]), numStates); - int s2 = checkStateIndex(Integer.parseInt(record[1]), numStates); + int s = checkStateIndex(Integer.parseInt(record[0]), modelStats.numStates); + int s2 = checkStateIndex(Integer.parseInt(record[1]), modelStats.numStates); Value v = checkValue(record[2], eval); - storeReward.accept(s, s2, v); + + switch (transitionRewardIndexing) { + case STATE: + storeReward.accept(s, s2, v); + break; + case OFFSET: + // Need to look up transition offset from successor state + SuccessorsIterator it = modelLookup.getSuccessors(s); + int i = 0; + while (it.hasNext()) { + if (it.nextInt() == s2) { + storeReward.accept(s, i, v); + break; + } + i++; + } + if (i > modelLookup.getNumTransitions(s)) { + throw new PrismException("No matching transition for transition reward " + s + "->" + s2); + } + break; + default: + throw new PrismException("Unknown transition reward indexing " + transitionRewardIndexing); + } } } catch (IOException e) { throw new PrismException("File I/O error reading from \"" + file + "\""); @@ -1094,11 +1123,10 @@ protected void extractMCTransitionRewards(IOUtils.TransitionRewardConsum * Extract the (Markov decision process) transition rewards from a .trew file. * The rewards are assumed to be of type double. * @param storeReward Function to be called for each reward - * @param numStates Number of states in the associated model */ - protected void extractMDPTransitionRewards(IOUtils.TransitionStateRewardConsumer storeReward, int numStates) throws PrismException + protected void extractMDPTransitionRewards(IOUtils.TransitionStateRewardConsumer storeReward) throws PrismException { - extractMDPTransitionRewards(storeReward, Evaluator.forDouble(), numStates); + extractMDPTransitionRewards(storeReward, Evaluator.forDouble()); } /** @@ -1106,9 +1134,8 @@ protected void extractMDPTransitionRewards(IOUtils.TransitionStateRewardConsumer * The rewards are assumed to be of type Value. * @param storeReward Function to be called for each reward * @param eval Evaluator for Value objects - * @param numStates Number of states in the associated model */ - protected void extractMDPTransitionRewards(IOUtils.TransitionStateRewardConsumer storeReward, Evaluator eval, int numStates) throws PrismException + protected void extractMDPTransitionRewards(IOUtils.TransitionStateRewardConsumer storeReward, Evaluator eval) throws PrismException { int lineNum = 0; try (BufferedReader in = new BufferedReader(new FileReader(file))) { @@ -1122,9 +1149,9 @@ protected void extractMDPTransitionRewards(IOUtils.TransitionStateReward continue; } checkLineSize(record, 4, 4); - int s = checkStateIndex(Integer.parseInt(record[0]), numStates); + int s = checkStateIndex(Integer.parseInt(record[0]), modelStats.numStates); int i = checkChoiceIndex(Integer.parseInt(record[1])); - int s2 = checkStateIndex(Integer.parseInt(record[2]), numStates); + int s2 = checkStateIndex(Integer.parseInt(record[2]), modelStats.numStates); Value v = checkValue(record[3], eval); storeReward.accept(s, i, s2, v); } diff --git a/prism/src/symbolic/build/ExplicitFiles2MTBDD.java b/prism/src/symbolic/build/ExplicitFiles2MTBDD.java index 447a67a1a4..ea5d9750cc 100644 --- a/prism/src/symbolic/build/ExplicitFiles2MTBDD.java +++ b/prism/src/symbolic/build/ExplicitFiles2MTBDD.java @@ -38,6 +38,7 @@ import parser.Values; import parser.VarList; import parser.ast.DeclarationType; +import prism.Evaluator; import prism.ModelInfo; import prism.ModelType; import prism.Prism; @@ -121,15 +122,17 @@ public ExplicitFiles2MTBDD(Prism prism) public Model build(ExplicitModelImporter importer) throws PrismException { this.importer = importer; - this.modelInfo = importer.getModelInfo(); + modelInfo = importer.getModelInfo(); modelType = modelInfo.getModelType(); varList = modelInfo.createVarList(); numVars = varList.getNumVars(); - this.numStates = importer.getNumStates(); + numStates = importer.getNumStates(); modelVariables = new ModelVariablesDD(); - rewardInfo = importer.getRewardInfo(); + // Tell importer we need state-indexed transition rewards + importer.setTransitionRewardIndexing(ExplicitModelImporter.TransitionRewardIndexing.STATE); + // Build states list, if info is available // (importer can handle case where it is unavailable, but we bypass this) if (importer.providesStates()) { @@ -498,7 +501,7 @@ private void buildTransitionRewards() throws PrismException transRewards[r] = JDD.Constant(0); int finalR = r; if (!modelType.nondeterministic()) { - importer.extractMCTransitionRewards(r, (s, s2, d) -> storeMCTransitionReward(finalR, s, s2, d)); + importer.extractMCTransitionRewards(r, (s, s2, d) -> storeMCTransitionReward(finalR, s, s2, d), Evaluator.forDouble()); } else { importer.extractMDPTransitionRewards(r, (s, i, s2, d) -> storeMDPTransitionReward(finalR, s, i, s2, d)); } From e646f0980bbb2dc7f88ffa6a41734bb0fb4a5a01 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Thu, 13 Feb 2025 15:33:17 +0000 Subject: [PATCH 35/44] Fix MDP transition reward import from explicit files. We assume (s,i) not (s,i,s') for now, since that is what is currently supported by the underlying storage/engines. So the handling of PRISM .trew files, which do technically support (s,i,s') rewards, is pushed into PrismExplicitImporter. And ExplicitModelImporter is adapted to expect (s,i) rewards. ExplicitFiles2MTBDD is also adapted accordingly. --- prism/src/explicit/ExplicitFiles2Rewards.java | 36 +----------------- prism/src/io/ExplicitModelImporter.java | 8 ++-- prism/src/io/PrismExplicitImporter.java | 37 +++++++++++++++++-- .../symbolic/build/ExplicitFiles2MTBDD.java | 36 ++++++++++++++---- 4 files changed, 67 insertions(+), 50 deletions(-) diff --git a/prism/src/explicit/ExplicitFiles2Rewards.java b/prism/src/explicit/ExplicitFiles2Rewards.java index b98d08fb88..7657ecedad 100644 --- a/prism/src/explicit/ExplicitFiles2Rewards.java +++ b/prism/src/explicit/ExplicitFiles2Rewards.java @@ -105,36 +105,7 @@ protected Rewards getTheRewardObject(int r) throws PrismException if (!model.getModelType().nondeterministic()) { importer.extractMCTransitionRewards(r, (s, s2, v) -> storeMCTransitionReward(r, s, s2, v), eval); } else { - importer.extractMDPTransitionRewards(r, - new IOUtils.TransitionStateRewardConsumer() { - int sLast = -1; - int iLast = -1; - Value vLast = null; - int count = 0; - public void accept(int s, int i, int s2, Value v) throws PrismException - { - count++; - // Check that transition rewards for the same state/choice are the same - // (currently no support for state-choice-state rewards) - if (s == sLast && i == iLast) { - if (!eval.equals(vLast, v)) { - throw new PrismException("mismatching transition rewards " + vLast + " and " + v + " in choice " + i + " of state " + s); - } - } - // And check that were rewards on all successors for each choice - // (for speed, we just check that the right number were present) - else { - if (sLast != -1 && count != ((NondetModel) model).getNumTransitions(sLast, iLast)) { - throw new PrismException("wrong number of transition rewards in choice " + iLast + " of state " + sLast); - } - sLast = s; - iLast = i; - vLast = v; - count = 0; - } - storeMDPTransitionReward(r, s, i, s2, v); - } - }, eval); + importer.extractMDPTransitionRewards(r, (s, i, v) -> storeMDPTransitionReward(r, s, i, v), eval); } } return rewards[r]; @@ -170,13 +141,10 @@ protected void storeMCTransitionReward(int r, int s, int s2, Value v) * @param r Reward structure index * @param s State index (source) * @param i Choice index - * @param s2 State index (destination) * @param v Reward value */ - protected void storeMDPTransitionReward(int r, int s, int i, int s2, Value v) + protected void storeMDPTransitionReward(int r, int s, int i, Value v) { - // For now, don't bother to check that the reward is the same for all s2 - // for a given state s and index i (so the last one in the file will define it) rewards[r].setTransitionReward(s, i, v); } } diff --git a/prism/src/io/ExplicitModelImporter.java b/prism/src/io/ExplicitModelImporter.java index 3bcd074aec..ead3bc1f65 100644 --- a/prism/src/io/ExplicitModelImporter.java +++ b/prism/src/io/ExplicitModelImporter.java @@ -239,27 +239,27 @@ public void extractMCTransitionRewards(int rewardIndex, IOUtils.TransitionReward /** * Extract the (Markov decision process) transition rewards for a given reward structure index. - * These are supplied as tuples (s,i,s2,v) where s is the (source) state, + * These are supplied as tuples (s,i,v) where s is the (source) state, * i is the choice index and v is the reward value. * The reward values are assumed to be of type double. * @param rewardIndex Index of reward structure to extract (0-indexed) * @param storeReward Function to be called for each reward */ - public void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionStateRewardConsumer storeReward) throws PrismException + public void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionRewardConsumer storeReward) throws PrismException { extractMDPTransitionRewards(rewardIndex, storeReward, Evaluator.forDouble()); } /** * Extract the (Markov decision process) transition rewards for a given reward structure index. - * These are supplied as tuples (s,i,s2,v) where s is the (source) state, + * These are supplied as tuples (s,i,v) where s is the (source) state, * i is the choice index and v is the reward value. * The reward values are assumed to be of type Value. * @param rewardIndex Index of reward structure to extract (0-indexed) * @param storeReward Function to be called for each reward * @param eval Evaluator for Value objects */ - public abstract void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionStateRewardConsumer storeReward, Evaluator eval) throws PrismException; + public abstract void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionRewardConsumer storeReward, Evaluator eval) throws PrismException; // Defaults for a single variable name when none is specified diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index 684d4a1756..0c42721b2d 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -49,6 +49,7 @@ import csv.CsvReader; import explicit.DTMCSimple; import explicit.ModelExplicit; +import explicit.NondetModel; import explicit.SuccessorsIterator; import param.BigRational; import parser.State; @@ -980,7 +981,7 @@ public void extractMCTransitionRewards(int rewardIndex, IOUtils.Transiti } @Override - public void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionStateRewardConsumer storeReward, Evaluator eval) throws PrismException + public void extractMDPTransitionRewards(int rewardIndex, IOUtils.TransitionRewardConsumer storeReward, Evaluator eval) throws PrismException { if (rewardIndex < transRewardsReaders.size()) { RewardFile file = transRewardsReaders.get(rewardIndex); @@ -1124,7 +1125,7 @@ protected void extractMCTransitionRewards(IOUtils.TransitionRewardConsum * The rewards are assumed to be of type double. * @param storeReward Function to be called for each reward */ - protected void extractMDPTransitionRewards(IOUtils.TransitionStateRewardConsumer storeReward) throws PrismException + protected void extractMDPTransitionRewards(IOUtils.TransitionRewardConsumer storeReward) throws PrismException { extractMDPTransitionRewards(storeReward, Evaluator.forDouble()); } @@ -1135,13 +1136,17 @@ protected void extractMDPTransitionRewards(IOUtils.TransitionStateRewardConsumer * @param storeReward Function to be called for each reward * @param eval Evaluator for Value objects */ - protected void extractMDPTransitionRewards(IOUtils.TransitionStateRewardConsumer storeReward, Evaluator eval) throws PrismException + protected void extractMDPTransitionRewards(IOUtils.TransitionRewardConsumer storeReward, Evaluator eval) throws PrismException { int lineNum = 0; try (BufferedReader in = new BufferedReader(new FileReader(file))) { lineNum += skipCommentAndFirstLine(in); BasicReader reader = BasicReader.wrap(in).normalizeLineEndings(); CsvReader csv = new CsvReader(reader, false, false, false, ' ', LF); + int count = 0; + int sLast = -1; + int iLast = -1; + Value vLast = null; for (String[] record : csv) { lineNum++; if ("".equals(record[0])) { @@ -1153,7 +1158,31 @@ protected void extractMDPTransitionRewards(IOUtils.TransitionStateReward int i = checkChoiceIndex(Integer.parseInt(record[1])); int s2 = checkStateIndex(Integer.parseInt(record[2]), modelStats.numStates); Value v = checkValue(record[3], eval); - storeReward.accept(s, i, s2, v); + // Check that transition rewards for the same state/choice are the same + // (currently no support for state-choice-state rewards) + if (s == sLast && i == iLast) { + if (!eval.equals(vLast, v)) { + throw new PrismException("mismatching transition rewards " + vLast + " and " + v + " in choice " + i + " of state " + s); + } + } + // If possible, check that were rewards on all successors for each choice + // (for speed, we just check that the right number were present) + // For now, don't bother to check that the reward is the same for all s2 + // for a given state s and index i (so the first one in the file will define it) + else { + if (modelLookup != null && modelLookup instanceof NondetModel && sLast != -1 && count != ((NondetModel) modelLookup).getNumTransitions(sLast, iLast)) { + throw new PrismException("wrong number of transition rewards in choice " + iLast + " of state " + sLast); + } + sLast = s; + iLast = i; + vLast = v; + count = 0; + } + // Only store the reward for the first instance of state-choice (s,i) + if (count == 0) { + storeReward.accept(s, i, v); + } + count++; } } catch (IOException e) { throw new PrismException("File I/O error reading from \"" + file + "\""); diff --git a/prism/src/symbolic/build/ExplicitFiles2MTBDD.java b/prism/src/symbolic/build/ExplicitFiles2MTBDD.java index ea5d9750cc..705ac1c33d 100644 --- a/prism/src/symbolic/build/ExplicitFiles2MTBDD.java +++ b/prism/src/symbolic/build/ExplicitFiles2MTBDD.java @@ -81,6 +81,8 @@ public class ExplicitFiles2MTBDD // mtbdd stuff + private ModelSymbolic model = null; + // dds/dd vars - whole system private JDDNode trans; // transition matrix dd private JDDNode start; // dd for start state @@ -155,7 +157,6 @@ private void readStatesFromFile() throws PrismException /** build model */ private Model buildModel() throws PrismException { - ModelSymbolic model = null; JDDNode tmp, tmp2; JDDVars ddv; int i; @@ -204,10 +205,6 @@ private Model buildModel() throws PrismException // construct labels and init state info buildLabelsAndInitialStates(); - // compute rewards - buildStateRewards(); - buildTransitionRewards(); - Values constantValues = new Values(); // no constants // create new Model object to be returned @@ -226,8 +223,13 @@ private Model buildModel() throws PrismException model = new StochModel(trans, start, allDDRowVars, allDDColVars, modelVariables, varList, varDDRowVars, varDDColVars); } - model.setRewards(stateRewards, transRewards, rewardStructNames); model.setConstantValues(constantValues); + + // compute/set rewards + buildStateRewards(); + buildTransitionRewards(); + model.setRewards(stateRewards, transRewards, rewardStructNames); + // set action info // TODO: disable if not required? model.setSynchs(synchs); @@ -503,7 +505,7 @@ private void buildTransitionRewards() throws PrismException if (!modelType.nondeterministic()) { importer.extractMCTransitionRewards(r, (s, s2, d) -> storeMCTransitionReward(finalR, s, s2, d), Evaluator.forDouble()); } else { - importer.extractMDPTransitionRewards(r, (s, i, s2, d) -> storeMDPTransitionReward(finalR, s, i, s2, d)); + importer.extractMDPTransitionRewards(r, (s, i, d) -> storeMDPTransitionReward(finalR, s, i, d)); } } } @@ -539,6 +541,24 @@ protected void storeMCTransitionReward(int rewardStructIndex, int s, int s2, dou transRewards[rewardStructIndex] = JDD.Plus(transRewards[rewardStructIndex], JDD.Times(JDD.Constant(d), tmp)); } + /** + * Stores transRewards in the required format for mtbdd. + * + * @param rewardStructIndex reward structure index + * @param s source state index + * @param i choice index + * @param d reward value + */ + protected void storeMDPTransitionReward(int rewardStructIndex, int s, int i, double d) + { + // Construct element of matrix MTBDD + JDDNode tmp = encodeState(s); + tmp = JDD.Apply(JDD.TIMES, tmp, JDD.SetVectorElement(JDD.Constant(0), allDDNondetVars, i, 1)); + tmp = JDD.And(tmp, model.getTrans01().copy()); + // Add it into MTBDD for transition rewards + transRewards[rewardStructIndex] = JDD.Plus(transRewards[rewardStructIndex], JDD.Times(JDD.Constant(d), tmp)); + } + /** * Stores transRewards in the required format for mtbdd. * @@ -553,7 +573,7 @@ protected void storeMDPTransitionReward(int rewardStructIndex, int s, int i, int // Construct element of matrix MTBDD JDDNode tmp = encodeStatePair(s, s2); tmp = JDD.Apply(JDD.TIMES, tmp, JDD.SetVectorElement(JDD.Constant(0), allDDNondetVars, i, 1)); - // Add it into MTBDD for state rewards + // Add it into MTBDD for transition rewards transRewards[rewardStructIndex] = JDD.Plus(transRewards[rewardStructIndex], JDD.Times(JDD.Constant(d), tmp)); } From 197b60e818df7bb5b619577d5e4ca11b05c26201 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Wed, 9 Apr 2025 12:06:19 +0100 Subject: [PATCH 36/44] File extension for -importmodel can be missing/non-standard. For now, the format is just assumed to be "explicit" (the only option). --- prism/src/prism/PrismCL.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 943ed923f2..daa3f500c6 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -1885,10 +1885,8 @@ private void processImportModelSwitch(String filesOptionsString) throws PrismExc String optionsString = halves[1]; // Split files into basename/extensions int i = filesString.lastIndexOf('.'); - if (i == -1) - throw new PrismException("No file name extension(s) in file(s) \"" + filesString + "\" for -importmodel"); - String basename = filesString.substring(0, i); - String extList = filesString.substring(i + 1); + String basename = i == -1 ? filesString : filesString.substring(0, i); + String extList = i == -1 ? "" : filesString.substring(i + 1); String exts[] = extList.split(","); // Process file extensions importModelWarning = null; @@ -1913,9 +1911,10 @@ private void processImportModelSwitch(String filesOptionsString) throws PrismExc } else if (ext.equals("trew")) { addTransitionRewardImports(basename, true); } - // Unknown extension + // For any other extension (including none/unknown), default to explicit (.tra) else { - throw new PrismException("Unknown extension \"" + ext + "\" for -importmodel switch"); + modelFilename = basename + (ext.isEmpty() ? "" : "." + ext); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.MODEL, ModelExportFormat.EXPLICIT, new File(modelFilename))); } } // No options supported currently From f803e2fcaddc724cf270afed6f3cb7a5843f68ae Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Thu, 24 Apr 2025 12:55:28 +0100 Subject: [PATCH 37/44] Add deadlock detection + fixing to explicit model import process. Because we now sometimes import to immutable models. This is disabled for import to symbolic models, where this is not an issue. --- .../import/dtmc-deadlocks-fixed.lab | 5 + .../import/dtmc-deadlocks-fixed.tra | 9 ++ .../functionality/import/dtmc-deadlocks.tra | 6 + .../dtmc-deadlocks.tra.importexport.auto | 1 + .../import/mdp-deadlocks-fixed.lab | 5 + .../import/mdp-deadlocks-fixed.tra | 9 ++ .../functionality/import/mdp-deadlocks.tra | 6 + .../mdp-deadlocks.tra.importexport.auto | 1 + prism/src/explicit/ExplicitFiles2Model.java | 7 +- prism/src/io/ExplicitModelImporter.java | 22 +++ prism/src/io/PrismExplicitImporter.java | 141 +++++++++++++++++- .../symbolic/build/ExplicitFiles2MTBDD.java | 3 + 12 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 prism-tests/functionality/import/dtmc-deadlocks-fixed.lab create mode 100644 prism-tests/functionality/import/dtmc-deadlocks-fixed.tra create mode 100644 prism-tests/functionality/import/dtmc-deadlocks.tra create mode 100644 prism-tests/functionality/import/dtmc-deadlocks.tra.importexport.auto create mode 100644 prism-tests/functionality/import/mdp-deadlocks-fixed.lab create mode 100644 prism-tests/functionality/import/mdp-deadlocks-fixed.tra create mode 100644 prism-tests/functionality/import/mdp-deadlocks.tra create mode 100644 prism-tests/functionality/import/mdp-deadlocks.tra.importexport.auto diff --git a/prism-tests/functionality/import/dtmc-deadlocks-fixed.lab b/prism-tests/functionality/import/dtmc-deadlocks-fixed.lab new file mode 100644 index 0000000000..3f267a7c4f --- /dev/null +++ b/prism-tests/functionality/import/dtmc-deadlocks-fixed.lab @@ -0,0 +1,5 @@ +0="init" 1="deadlock" +0: 0 +2: 1 +3: 1 +5: 1 diff --git a/prism-tests/functionality/import/dtmc-deadlocks-fixed.tra b/prism-tests/functionality/import/dtmc-deadlocks-fixed.tra new file mode 100644 index 0000000000..9d959e2e1b --- /dev/null +++ b/prism-tests/functionality/import/dtmc-deadlocks-fixed.tra @@ -0,0 +1,9 @@ +6 8 +0 0 0.4 east +0 1 0.6 east +1 2 0.5 south +1 4 0.5 south +2 2 1 +3 3 1 +4 5 1 east +5 5 1 diff --git a/prism-tests/functionality/import/dtmc-deadlocks.tra b/prism-tests/functionality/import/dtmc-deadlocks.tra new file mode 100644 index 0000000000..665d2c33b6 --- /dev/null +++ b/prism-tests/functionality/import/dtmc-deadlocks.tra @@ -0,0 +1,6 @@ +6 5 +0 0 0.4 east +0 1 0.6 east +1 2 0.5 south +1 4 0.5 south +4 5 1 east diff --git a/prism-tests/functionality/import/dtmc-deadlocks.tra.importexport.auto b/prism-tests/functionality/import/dtmc-deadlocks.tra.importexport.auto new file mode 100644 index 0000000000..35a6e154e8 --- /dev/null +++ b/prism-tests/functionality/import/dtmc-deadlocks.tra.importexport.auto @@ -0,0 +1 @@ +-importmodel dtmc-deadlocks.tra -exportmodel dtmc-deadlocks-fixed.tra,lab -ex diff --git a/prism-tests/functionality/import/mdp-deadlocks-fixed.lab b/prism-tests/functionality/import/mdp-deadlocks-fixed.lab new file mode 100644 index 0000000000..3f267a7c4f --- /dev/null +++ b/prism-tests/functionality/import/mdp-deadlocks-fixed.lab @@ -0,0 +1,5 @@ +0="init" 1="deadlock" +0: 0 +2: 1 +3: 1 +5: 1 diff --git a/prism-tests/functionality/import/mdp-deadlocks-fixed.tra b/prism-tests/functionality/import/mdp-deadlocks-fixed.tra new file mode 100644 index 0000000000..ab13c6a650 --- /dev/null +++ b/prism-tests/functionality/import/mdp-deadlocks-fixed.tra @@ -0,0 +1,9 @@ +6 6 8 +0 0 0 0.4 east +0 0 1 0.6 east +1 0 2 0.5 south +1 0 4 0.5 south +2 0 2 1 +3 0 3 1 +4 0 5 1 east +5 0 5 1 diff --git a/prism-tests/functionality/import/mdp-deadlocks.tra b/prism-tests/functionality/import/mdp-deadlocks.tra new file mode 100644 index 0000000000..d1baa2b4bd --- /dev/null +++ b/prism-tests/functionality/import/mdp-deadlocks.tra @@ -0,0 +1,6 @@ +6 3 5 +0 0 0 0.4 east +0 0 1 0.6 east +1 0 2 0.5 south +1 0 4 0.5 south +4 0 5 1 east diff --git a/prism-tests/functionality/import/mdp-deadlocks.tra.importexport.auto b/prism-tests/functionality/import/mdp-deadlocks.tra.importexport.auto new file mode 100644 index 0000000000..3b9f0d6739 --- /dev/null +++ b/prism-tests/functionality/import/mdp-deadlocks.tra.importexport.auto @@ -0,0 +1 @@ +-importmodel mdp-deadlocks.tra -exportmodel mdp-deadlocks-fixed.tra,lab -ex diff --git a/prism/src/explicit/ExplicitFiles2Model.java b/prism/src/explicit/ExplicitFiles2Model.java index d899eaf1a1..ed5aed8a07 100644 --- a/prism/src/explicit/ExplicitFiles2Model.java +++ b/prism/src/explicit/ExplicitFiles2Model.java @@ -95,6 +95,7 @@ public Model build(ExplicitModelImporter modelImporter) throws PrismExce */ public Model build(ExplicitModelImporter modelImporter, Evaluator eval) throws PrismException { + modelImporter.setFixDeadlocks(fixdl); ModelExplicit model = null; ModelInfo modelInfo = modelImporter.getModelInfo(); boolean isDbl = eval.one() instanceof Double; @@ -148,8 +149,10 @@ public Model build(ExplicitModelImporter modelImporter, Evaluator if (!model.getInitialStates().iterator().hasNext()) { throw new PrismException("Imported model has no initial states"); } - - model.findDeadlocks(fixdl); + BitSet deadlocks = modelImporter.getDeadlockStates(); + for (int s = deadlocks.nextSetBit(0); s >= 0; s = deadlocks.nextSetBit(s + 1)) { + model.addDeadlockState(s); + } loadStates(modelImporter, model); diff --git a/prism/src/io/ExplicitModelImporter.java b/prism/src/io/ExplicitModelImporter.java index ead3bc1f65..d18ef40df3 100644 --- a/prism/src/io/ExplicitModelImporter.java +++ b/prism/src/io/ExplicitModelImporter.java @@ -36,6 +36,7 @@ import prism.PrismException; import prism.RewardInfo; +import java.util.BitSet; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -53,6 +54,17 @@ public enum TransitionRewardIndexing { }; protected TransitionRewardIndexing transitionRewardIndexing = TransitionRewardIndexing.OFFSET; + /** Should deadlocks be detected and fixed (by adding a self-loop) on import? */ + protected boolean fixdl; + + /** + * Specify whether should deadlocks be detected and fixed (by adding a self-loop) on import + */ + public void setFixDeadlocks(boolean fixdl) + { + this.fixdl = fixdl; + } + /** * Specify how transition rewards should be supplied when extracted. */ @@ -113,6 +125,16 @@ public void setModel(explicit.Model modelLookup) */ public abstract int getNumTransitions() throws PrismException; + /** + * Get the indices of the states which are/were deadlocks. + */ + public abstract BitSet getDeadlockStates() throws PrismException; + + /** + * Get the number of states which are/were deadlocks. + */ + public abstract int getNumDeadlockStates() throws PrismException; + /** * Get a string stating the model type and how it was obtained. */ diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index 0c42721b2d..02cd962ecb 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -100,6 +100,14 @@ private class ModelStats } private ModelStats modelStats; + // Info about deadlocks + private class DeadlockInfo + { + BitSet deadlocks = new BitSet(); + int numDeadlocks = 0; + } + private DeadlockInfo deadlockInfo; + // Mapping from label indices in file to (non-built-in) label indices (also: -1=init, -2=deadlock) private List labelMap; @@ -307,7 +315,12 @@ public int getNumChoices() throws PrismException if (modelStats == null) { buildModelStats(); } - return modelStats.numChoices; + int numChoices = modelStats.numChoices; + // Add extras if deadlocks are being fixed + if (fixdl) { + numChoices += getNumDeadlockStates(); + } + return numChoices; } @Override @@ -318,7 +331,32 @@ public int getNumTransitions() throws PrismException if (modelStats == null) { buildModelStats(); } - return modelStats.numTransitions; + int numTransitions = modelStats.numTransitions; + // Add extras if deadlocks are being fixed + if (fixdl) { + numTransitions += getNumDeadlockStates(); + } + return numTransitions; + } + + @Override + public BitSet getDeadlockStates() throws PrismException + { + // Do deadlock state detection lazily, as needed + if (deadlockInfo == null) { + findDeadlocks(); + } + return deadlockInfo.deadlocks; + } + + @Override + public int getNumDeadlockStates() throws PrismException + { + // Do deadlock state detection lazily, as needed + if (deadlockInfo == null) { + findDeadlocks(); + } + return deadlockInfo.numDeadlocks; } @Override @@ -653,6 +691,47 @@ private ModelType autodetectModelType(File transFile) } } + /** + * Traverse the transitions file to detect any deadlock states + * and then store the details in deadlockInfo. + */ + private void findDeadlocks() throws PrismException + { + // Record which states have transitions + BitSet statesWithTransitions = new BitSet(); + int lineNum = 0; + try (BufferedReader in = new BufferedReader(new FileReader(transFile))) { + lineNum += skipCommentAndFirstLine(in); + BasicReader reader = BasicReader.wrap(in).normalizeLineEndings(); + CsvReader csv = new CsvReader(reader, false, false, false, ' ', LF); + for (String[] record : csv) { + lineNum++; + if ("".equals(record[0])) { + // Skip blank lines + continue; + } + // Lines should be 3-5 long (LTS/MDP with/without actions) + checkLineSize(record, 3, 5); + // Extract/store source state + int s = checkStateIndex(Integer.parseInt(record[0]), modelStats.numStates); + statesWithTransitions.set(s); + } + } catch (IOException e) { + throw new PrismException("File I/O error reading from \"" + transFile + "\": " + e.getMessage()); + } catch (PrismException | NumberFormatException | CsvFormatException e) { + String expl = (e.getMessage() == null || e.getMessage().isEmpty()) ? "" : (" (" + e.getMessage() + ")"); + throw new PrismException("Error detected" + expl + " at line " + lineNum + " of transitions file \"" + transFile + "\""); + } + // Store deadlock info + deadlockInfo = new DeadlockInfo(); + if (statesWithTransitions.cardinality() != modelStats.numStates) { + for (int s = statesWithTransitions.nextClearBit(0); s < modelStats.numStates; s = statesWithTransitions.nextClearBit(s + 1)) { + deadlockInfo.deadlocks.set(s); + deadlockInfo.numDeadlocks++; + } + } + } + @Override public void extractStates(IOUtils.StateDefnConsumer storeStateDefn) throws PrismException { @@ -708,11 +787,11 @@ public void extractStates(IOUtils.StateDefnConsumer storeStateDefn) throws Prism public int computeMaxNumChoices() throws PrismException { int lineNum = 0; + int maxNumChoices = 0; try (BufferedReader in = new BufferedReader(new FileReader(transFile))) { lineNum += skipCommentAndFirstLine(in); BasicReader reader = BasicReader.wrap(in).normalizeLineEndings(); CsvReader csv = new CsvReader(reader, false, false, false, ' ', LF); - int maxNumChoices = 0; for (String[] record : csv) { lineNum++; if ("".equals(record[0])) { @@ -726,18 +805,27 @@ public int computeMaxNumChoices() throws PrismException maxNumChoices = j + 1; } } - return maxNumChoices; } catch (IOException e) { throw new PrismException("File I/O error reading from \"" + transFile + "\": " + e.getMessage()); } catch (PrismException | NumberFormatException | CsvFormatException e) { String expl = (e.getMessage() == null || e.getMessage().isEmpty()) ? "" : (" (" + e.getMessage() + ")"); throw new PrismException("Error detected" + expl + " at line " + lineNum + " of transitions file \"" + transFile + "\""); } + if (fixdl && getNumDeadlockStates() > 0) { + maxNumChoices = Math.max(maxNumChoices, 1); + } + return maxNumChoices; } @Override public void extractMCTransitions(IOUtils.MCTransitionConsumer storeTransition, Evaluator eval) throws PrismException { + BitSet deadlocks = new BitSet(); + int nextDeadlock = -1; + if (fixdl) { + deadlocks = getDeadlockStates(); + nextDeadlock = deadlocks.nextSetBit(0); + } int lineNum = 0; try (BufferedReader in = new BufferedReader(new FileReader(transFile))) { lineNum += skipCommentAndFirstLine(in); @@ -754,8 +842,19 @@ public void extractMCTransitions(IOUtils.MCTransitionConsumer sto int s2 = checkStateIndex(Integer.parseInt(record[1]), modelStats.numStates); Value v = checkValue(record[2], eval); Object a = (record.length > 3) ? checkAction(record[3]) : null; + // Add self-loops for any deadlock states before s + while (nextDeadlock != -1 && nextDeadlock < s) { + storeTransition.accept(nextDeadlock, nextDeadlock, eval.one(), null); + nextDeadlock = deadlocks.nextSetBit(nextDeadlock + 1); + } + // Add transition storeTransition.accept(s, s2, v, a); } + // Add self-loops for any remaining deadlock states + while (nextDeadlock != -1) { + storeTransition.accept(nextDeadlock, nextDeadlock, eval.one(), null); + nextDeadlock = deadlocks.nextSetBit(nextDeadlock + 1); + } } catch (IOException e) { throw new PrismException("File I/O error reading from \"" + transFile + "\""); } catch (PrismException | NumberFormatException | CsvFormatException e) { @@ -767,6 +866,12 @@ public void extractMCTransitions(IOUtils.MCTransitionConsumer sto @Override public void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTransition, Evaluator eval) throws PrismException { + BitSet deadlocks = new BitSet(); + int nextDeadlock = -1; + if (fixdl) { + deadlocks = getDeadlockStates(); + nextDeadlock = deadlocks.nextSetBit(0); + } int lineNum = 0; try (BufferedReader in = new BufferedReader(new FileReader(transFile))) { lineNum += skipCommentAndFirstLine(in); @@ -784,8 +889,19 @@ public void extractMDPTransitions(IOUtils.MDPTransitionConsumer s int s2 = checkStateIndex(Integer.parseInt(record[2]), modelStats.numStates); Value v = checkValue(record[3], eval); Object a = (record.length > 4) ? checkAction(record[4]) : null; + // Add self-loops for any deadlock states before s + while (nextDeadlock != -1 && nextDeadlock < s) { + storeTransition.accept(nextDeadlock, 0, nextDeadlock, eval.one(), null); + nextDeadlock = deadlocks.nextSetBit(nextDeadlock + 1); + } + // Add transition storeTransition.accept(s, i, s2, v, a); } + // Add self-loops for any remaining deadlock states + while (nextDeadlock != -1) { + storeTransition.accept(nextDeadlock, 0, nextDeadlock, eval.one(), null); + nextDeadlock = deadlocks.nextSetBit(nextDeadlock + 1); + } } catch (IOException e) { throw new PrismException("File I/O error reading from \"" + transFile + "\""); } catch (PrismException | NumberFormatException | CsvFormatException e) { @@ -797,6 +913,12 @@ public void extractMDPTransitions(IOUtils.MDPTransitionConsumer s @Override public void extractLTSTransitions(IOUtils.LTSTransitionConsumer storeTransition) throws PrismException { + BitSet deadlocks = new BitSet(); + int nextDeadlock = -1; + if (fixdl) { + deadlocks = getDeadlockStates(); + nextDeadlock = deadlocks.nextSetBit(0); + } int lineNum = 0; try (BufferedReader in = new BufferedReader(new FileReader(transFile))) { lineNum += skipCommentAndFirstLine(in); @@ -813,8 +935,19 @@ public void extractLTSTransitions(IOUtils.LTSTransitionConsumer storeTransition) int i = checkChoiceIndex(Integer.parseInt(record[1])); int s2 = checkStateIndex(Integer.parseInt(record[2]), modelStats.numStates); Object a = (record.length > 3) ? checkAction(record[3]) : null; + // Add self-loops for any deadlock states before s + while (nextDeadlock != -1 && nextDeadlock < s) { + storeTransition.accept(nextDeadlock, 0, nextDeadlock, null); + nextDeadlock = deadlocks.nextSetBit(nextDeadlock + 1); + } + // Add transition storeTransition.accept(s, i, s2, a); } + // Add self-loops for any remaining deadlock states + while (nextDeadlock != -1) { + storeTransition.accept(nextDeadlock, 0, nextDeadlock, null); + nextDeadlock = deadlocks.nextSetBit(nextDeadlock + 1); + } } catch (IOException e) { throw new PrismException("File I/O error reading from \"" + transFile + "\""); } catch (PrismException | NumberFormatException | CsvFormatException e) { diff --git a/prism/src/symbolic/build/ExplicitFiles2MTBDD.java b/prism/src/symbolic/build/ExplicitFiles2MTBDD.java index 705ac1c33d..74bd5baf5e 100644 --- a/prism/src/symbolic/build/ExplicitFiles2MTBDD.java +++ b/prism/src/symbolic/build/ExplicitFiles2MTBDD.java @@ -135,6 +135,9 @@ public Model build(ExplicitModelImporter importer) throws PrismException // Tell importer we need state-indexed transition rewards importer.setTransitionRewardIndexing(ExplicitModelImporter.TransitionRewardIndexing.STATE); + // Tell importer not to fix deadlocks; we do it here + importer.setFixDeadlocks(false); + // Build states list, if info is available // (importer can handle case where it is unavailable, but we bypass this) if (importer.providesStates()) { From 2e1295d2c46b58d36e71cc0d395e6022e9c18bcd Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Wed, 28 May 2025 09:02:37 +0100 Subject: [PATCH 38/44] Default implementation of extractStates in ExplicitModelImporter. Plus commenting of related methods in ExplicitModelImporter. --- prism/src/io/ExplicitModelImporter.java | 16 ++++++++++++++-- prism/src/io/PrismExplicitImporter.java | 6 ++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/prism/src/io/ExplicitModelImporter.java b/prism/src/io/ExplicitModelImporter.java index d18ef40df3..8d2af401b9 100644 --- a/prism/src/io/ExplicitModelImporter.java +++ b/prism/src/io/ExplicitModelImporter.java @@ -106,7 +106,10 @@ public void setModel(explicit.Model modelLookup) public abstract String sourceString(); /** - * Get info about the model. + * Get info about the model (type, variables, labels, etc.). + * If {@link #providesLabels()} returns false, this will report zero labels. + * If {@link #providesStates()} returns false, this will report a single + * integer-valued variable with name {@link #defaultVariableName()}. */ public abstract ModelInfo getModelInfo() throws PrismException; @@ -152,9 +155,18 @@ public String getModelTypeString() throws PrismException /** * Extract state definitions (variable values). * Calls {@code storeStateDefn(s, i, o)} for each state s, variable (index) i and variable value o. + * If {@link #providesStates()} returns false, this should report a single + * integer-valued variable range between 0 and {@link #getNumStates()} - 1. * @param storeStateDefn Function to be called for each variable value of each state */ - public abstract void extractStates(IOUtils.StateDefnConsumer storeStateDefn) throws PrismException; + public void extractStates(IOUtils.StateDefnConsumer storeStateDefn) throws PrismException + { + // Default implementation - assume one integer variable + int numStates = getNumStates(); + for (int s = 0; s < numStates; s++) { + storeStateDefn.accept(s, 0, s); + } + } /** * Compute the maximum number of choices (in a nondeterministic model). diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index 02cd962ecb..c6691c983f 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -735,15 +735,13 @@ private void findDeadlocks() throws PrismException @Override public void extractStates(IOUtils.StateDefnConsumer storeStateDefn) throws PrismException { - int numVars = basicModelInfo.getNumVars(); // If there is no info, just assume that states comprise a single integer value if (getStatesFile() == null) { - for (int s = 0; s < modelStats.numStates; s++) { - storeStateDefn.accept(s, 0, s); - } + super.extractStates(storeStateDefn); return; } // Otherwise extract from .sta file + int numVars = basicModelInfo.getNumVars(); int lineNum = 0; try (BufferedReader in = new BufferedReader(new FileReader(statesFile))) { lineNum += skipCommentAndFirstLine(in); From a407022eb7e6fc395ee424bdbd3f8f9e98769188 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Thu, 29 May 2025 20:41:20 +0100 Subject: [PATCH 39/44] Bugfix: NPE on testing of model checking results after explicit import. --- .../robot.prism.importcheck.auto.disabled | 4 ++-- .../functionality/import/robot.prism.props | 19 +++++++++++++++++-- prism/src/prism/PrismCL.java | 6 +++--- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/prism-tests/functionality/import/robot.prism.importcheck.auto.disabled b/prism-tests/functionality/import/robot.prism.importcheck.auto.disabled index 3a3e214a64..96cba48add 100644 --- a/prism-tests/functionality/import/robot.prism.importcheck.auto.disabled +++ b/prism-tests/functionality/import/robot.prism.importcheck.auto.disabled @@ -1,2 +1,2 @@ --mdp -importmodel robot.all robot.prism.props --mdp -importmodel robot.all robot.prism.props -ex +-importmodel robot.all robot.prism.props +-importmodel robot.all robot.prism.props -ex diff --git a/prism-tests/functionality/import/robot.prism.props b/prism-tests/functionality/import/robot.prism.props index 2abccf287e..af2d121cd0 100644 --- a/prism-tests/functionality/import/robot.prism.props +++ b/prism-tests/functionality/import/robot.prism.props @@ -1,2 +1,17 @@ -// RESULT: 0.1 -Pmax=? [ (G !"hazard")&(G F "goal1") ] +// RESULT: Infinity +Rmin=? [ F "goal1" ] + +// RESULT: Infinity +Rmax=? [ F "goal1" ] + +// RESULT: 19/15 +Rmin=? [ F "goal2" ] + +// RESULT: Infinity +Rmax=? [ F "goal2" ] + +// RESULT: 1.2 +Rmin=? [ F ("goal1"|"goal2") ] + +// RESULT: 3.5 +Rmax=? [ F ("goal1"|"goal2") ] diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index daa3f500c6..f0124287c7 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -438,7 +438,7 @@ public void run(String[] args) if (modelBuildFail) { results[j].setMultipleErrors(definedMFConstants, null, modelBuildException); if (test) { - doResultTest(propertiesToCheck.get(j), new Result(modelBuildException), modulesFile.getConstantValues(), null); + doResultTest(propertiesToCheck.get(j), new Result(modelBuildException), prism.getModelInfo().getConstantValues(), null); } break; } @@ -498,7 +498,7 @@ public void run(String[] args) // if required, check result against expected value if (test) { - doResultTest(propertiesToCheck.get(j), res, modulesFile.getConstantValues(), propertiesFile.getConstantValues()); + doResultTest(propertiesToCheck.get(j), res, prism.getModelInfo().getConstantValues(), propertiesFile.getConstantValues()); } // iterate to next property @@ -511,7 +511,7 @@ public void run(String[] args) for (j++; j < numPropertiesToCheck; j++) { results[j].setMultipleErrors(definedMFConstants, null, modelBuildException); if (test) { - doResultTest(propertiesToCheck.get(j), new Result(modelBuildException), modulesFile.getConstantValues(), propertiesFile.getConstantValues()); + doResultTest(propertiesToCheck.get(j), new Result(modelBuildException), prism.getModelInfo().getConstantValues(), propertiesFile.getConstantValues()); } } break; From a051110aa165f4de16441caec0598041a6f296db Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Thu, 29 May 2025 21:50:51 +0100 Subject: [PATCH 40/44] Import from explicit files preserves "deadlock" label. I.e., it imports info about states that were deadlocks, even if they have now been fixed (replaced with self-loops). --- .../functionality/import/dice-deadlock.lab | 8 ++++++ .../import/dice-deadlock.pm.importexport.auto | 2 ++ .../import/dice-deadlock.pm.orig | 26 +++++++++++++++++++ .../functionality/import/dice-deadlock.tra | 21 +++++++++++++++ prism/src/explicit/ExplicitFiles2Model.java | 2 +- prism/src/io/ExplicitModelImporter.java | 19 +++++++++++++- prism/src/io/PrismExplicitImporter.java | 10 ++++--- .../symbolic/build/ExplicitFiles2MTBDD.java | 5 ++++ prism/src/symbolic/model/ModelSymbolic.java | 17 ++++++++++-- 9 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 prism-tests/functionality/import/dice-deadlock.lab create mode 100644 prism-tests/functionality/import/dice-deadlock.pm.importexport.auto create mode 100644 prism-tests/functionality/import/dice-deadlock.pm.orig create mode 100644 prism-tests/functionality/import/dice-deadlock.tra diff --git a/prism-tests/functionality/import/dice-deadlock.lab b/prism-tests/functionality/import/dice-deadlock.lab new file mode 100644 index 0000000000..2457fd1859 --- /dev/null +++ b/prism-tests/functionality/import/dice-deadlock.lab @@ -0,0 +1,8 @@ +0="init" 1="deadlock" 2="end" 3="six" +0: 0 +7: 1 2 +8: 1 2 +9: 1 2 +10: 1 2 +11: 1 2 +12: 1 2 3 diff --git a/prism-tests/functionality/import/dice-deadlock.pm.importexport.auto b/prism-tests/functionality/import/dice-deadlock.pm.importexport.auto new file mode 100644 index 0000000000..814cf7f290 --- /dev/null +++ b/prism-tests/functionality/import/dice-deadlock.pm.importexport.auto @@ -0,0 +1,2 @@ +-importmodel dice-deadlock.tra,lab -exportmodel dice-deadlock.tra,lab +-importmodel dice-deadlock.tra,lab -exportmodel dice-deadlock.tra,lab -ex diff --git a/prism-tests/functionality/import/dice-deadlock.pm.orig b/prism-tests/functionality/import/dice-deadlock.pm.orig new file mode 100644 index 0000000000..cac96f4b50 --- /dev/null +++ b/prism-tests/functionality/import/dice-deadlock.pm.orig @@ -0,0 +1,26 @@ +dtmc + +module die + + // local state + s : [0..7] init 0; + // value of the die + d : [0..6] init 0; + + [] s=0 -> 1/2 : (s'=1) + 1/2 : (s'=2); + [] s=1 -> 1/2 : (s'=3) + 1/2 : (s'=4); + [] s=2 -> 1/2 : (s'=5) + 1/2 : (s'=6); + [] s=3 -> 1/2 : (s'=1) + 1/2 : (s'=7) & (d'=1); + [] s=4 -> 1/2 : (s'=7) & (d'=2) + 1/2 : (s'=7) & (d'=3); + [] s=5 -> 1/2 : (s'=7) & (d'=4) + 1/2 : (s'=7) & (d'=5); + [] s=6 -> 1/2 : (s'=2) + 1/2 : (s'=7) & (d'=6); +// [] s=7 -> (s'=7); + +endmodule + +label "end" = s=7; +label "six" = d=6; + +rewards "coin_flips" + s<7 : 1; +endrewards diff --git a/prism-tests/functionality/import/dice-deadlock.tra b/prism-tests/functionality/import/dice-deadlock.tra new file mode 100644 index 0000000000..e7c1e3aff4 --- /dev/null +++ b/prism-tests/functionality/import/dice-deadlock.tra @@ -0,0 +1,21 @@ +13 20 +0 1 0.5 +0 2 0.5 +1 3 0.5 +1 4 0.5 +2 5 0.5 +2 6 0.5 +3 1 0.5 +3 7 0.5 +4 8 0.5 +4 9 0.5 +5 10 0.5 +5 11 0.5 +6 2 0.5 +6 12 0.5 +7 7 1 +8 8 1 +9 9 1 +10 10 1 +11 11 1 +12 12 1 diff --git a/prism/src/explicit/ExplicitFiles2Model.java b/prism/src/explicit/ExplicitFiles2Model.java index ed5aed8a07..0ed255c7d8 100644 --- a/prism/src/explicit/ExplicitFiles2Model.java +++ b/prism/src/explicit/ExplicitFiles2Model.java @@ -174,7 +174,7 @@ private void loadLabelsAndInitialStates(ExplicitModelImporter modelImporter, Mod labelBitSets.add(new BitSet()); } // Extract info - modelImporter.extractLabelsAndInitialStates((s, l) -> labelBitSets.get(l).set(s), model::addInitialState); + modelImporter.extractLabelsAndInitialStates((s, l) -> labelBitSets.get(l).set(s), model::addInitialState, model::addDeadlockState); // Attach labels to model for (int l = 0; l < numLabels; l++) { model.addLabel(modelInfo.getLabelName(l), labelBitSets.get(l)); diff --git a/prism/src/io/ExplicitModelImporter.java b/prism/src/io/ExplicitModelImporter.java index 8d2af401b9..b4916f3830 100644 --- a/prism/src/io/ExplicitModelImporter.java +++ b/prism/src/io/ExplicitModelImporter.java @@ -220,10 +220,27 @@ public void extractMDPTransitions(IOUtils.MDPTransitionConsumer storeTra * Calls {@code storeLabel(s, i)} for each state s satisfying label l, * where l is 0-indexed and matches the label list from {@link #getModelInfo()}. * Calls {@code storeInit(s)} for each initial state s. + * Any "deadlock" labels are ignored. * @param storeLabel Function to be called for each state satisfying a label * @param storeInit Function to be called for each initial stat */ - public abstract void extractLabelsAndInitialStates(BiConsumer storeLabel, Consumer storeInit) throws PrismException; + public void extractLabelsAndInitialStates(BiConsumer storeLabel, Consumer storeInit) throws PrismException + { + extractLabelsAndInitialStates(storeLabel, storeInit, null); + } + + /** + * Extract info about state labellings and initial states. + * Calls {@code storeLabel(s, i)} for each state s satisfying label l, + * where l is 0-indexed and matches the label list from {@link #getModelInfo()}. + * Calls {@code storeInit(s)} for each initial state s. + * If {@code storeDeadlock} is non-null, it will be called for each state labelled + * with "deadlock", regardless of whether it has been detected as a deadlock and/or fixed. + * @param storeLabel Function to be called for each state satisfying a label + * @param storeInit Function to be called for each initial state + * @param storeDeadlock Function to be called for each state marked as a deadlock + */ + public abstract void extractLabelsAndInitialStates(BiConsumer storeLabel, Consumer storeInit, Consumer storeDeadlock) throws PrismException; /** * Extract the state rewards for a given reward structure index. diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index c6691c983f..70199fd129 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -554,7 +554,7 @@ private void extractModelStatsFromTransFile(File transFile) throws PrismExceptio /** * Extract names of labels from the labels file. - * These are store in the label name list within basicModelInfo. + * These are stored in the label name list within basicModelInfo. * The "init" and "deadlock" labels are skipped, as they have special * meaning and are implicitly defined for all models. */ @@ -955,7 +955,7 @@ public void extractLTSTransitions(IOUtils.LTSTransitionConsumer storeTransition) } @Override - public void extractLabelsAndInitialStates(BiConsumer storeLabel, Consumer storeInit) throws PrismException + public void extractLabelsAndInitialStates(BiConsumer storeLabel, Consumer storeInit, Consumer storeDeadlock) throws PrismException { // If there is no info, just assume that 0 is the initial state if (getLabelsFile() == null) { @@ -983,7 +983,11 @@ public void extractLabelsAndInitialStates(BiConsumer storeLabe // Store label info int i = checkLabelIndex(ss[j]); int l = labelMap.get(i); - if (l == -1) { + if (l == -2) { + if (storeDeadlock != null) { + storeDeadlock.accept(s); + } + } else if (l == -1) { storeInit.accept(s); } else if (l > -1) { storeLabel.accept(s, l); diff --git a/prism/src/symbolic/build/ExplicitFiles2MTBDD.java b/prism/src/symbolic/build/ExplicitFiles2MTBDD.java index 74bd5baf5e..ac66a99c0e 100644 --- a/prism/src/symbolic/build/ExplicitFiles2MTBDD.java +++ b/prism/src/symbolic/build/ExplicitFiles2MTBDD.java @@ -105,6 +105,7 @@ public class ExplicitFiles2MTBDD private int maxNumChoices = 0; private LinkedHashMap labelsDD; + private JDDNode labelDeadlock; // Progress info private ProgressDisplay progress; @@ -263,6 +264,7 @@ private Model buildModel() throws PrismException // attach labels attachLabels(model); + model.addDeadlocks(labelDeadlock); // deref spare dds if (labelsDD != null) { @@ -449,6 +451,7 @@ private void buildLabelsAndInitialStates() throws PrismException { // Initialise BDDs start = JDD.Constant(0); + labelDeadlock = JDD.Constant(0); int numLabels = modelInfo.getNumLabels(); JDDNode[] labelDDs = new JDDNode[numLabels]; for (int l = 0; l < numLabels; l++) { @@ -459,6 +462,8 @@ private void buildLabelsAndInitialStates() throws PrismException labelDDs[l] = JDD.Or(labelDDs[l], encodeState(s)); }, s -> { start = JDD.Or(start, encodeState(s)); + }, s -> { + labelDeadlock = JDD.Or(labelDeadlock, encodeState(s)); }); if (start == null || start.equals(JDD.ZERO)) { throw new PrismException("No initial states found in labels file"); diff --git a/prism/src/symbolic/model/ModelSymbolic.java b/prism/src/symbolic/model/ModelSymbolic.java index 3db313ef75..3965ae9f2f 100644 --- a/prism/src/symbolic/model/ModelSymbolic.java +++ b/prism/src/symbolic/model/ModelSymbolic.java @@ -416,12 +416,25 @@ public void filterReachableStates() /** * Find all deadlock states and store this information in the model. - * If requested (if fix=true) and if needed (i.e. for DTMCs/CTMCs), - * fix deadlocks by adding self-loops in these states. + * If requested (if fix=true), fix deadlocks by adding self-loops in these states. * The set of deadlocks (before any possible fixing) can be obtained from {@link #getDeadlocks()}. */ public abstract void findDeadlocks(boolean fix); + /** + * Mark additional states as deadlocks. + * + *
[ DEREFS: moreDeadlocks ] + */ + public void addDeadlocks(JDDNode moreDeadlocks) + { + if (deadlocks == null) { + deadlocks = moreDeadlocks; + } else { + deadlocks = JDD.Or(deadlocks, moreDeadlocks); + } + } + // Accessors (for prism.Model & symbolic.Model interface) @Override From 88ab0bd3758b45f85a776013212fead8ca6eb487 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Fri, 30 May 2025 17:07:24 +0100 Subject: [PATCH 41/44] Fix explicit file import type detection for very small files. --- prism/src/io/PrismExplicitImporter.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index 70199fd129..321a265d91 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -680,12 +680,9 @@ private ModelType autodetectModelType(File transFile) if (d > 1) { return ModelType.CTMC; } - // All non-rates so far: guess MDP/DTMC - if (lines == max) { - return nondet ? ModelType.MDP : ModelType.DTMC; - } } - return null; + // All non-rates seen: guess MDP/DTMC + return nondet ? ModelType.MDP : ModelType.DTMC; } catch (NumberFormatException | CsvFormatException | IOException e) { return null; } From 0c37fb7522f308f418a75b1a69ceebe8afb85225 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sat, 31 May 2025 00:26:18 +0100 Subject: [PATCH 42/44] Explicit file import deals with zero-range integer variables. --- prism/src/io/PrismExplicitImporter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prism/src/io/PrismExplicitImporter.java b/prism/src/io/PrismExplicitImporter.java index 321a265d91..8dc1f75b2a 100644 --- a/prism/src/io/PrismExplicitImporter.java +++ b/prism/src/io/PrismExplicitImporter.java @@ -507,6 +507,10 @@ private void extractVarInfoFromStatesFile(File statesFile) throws PrismException if (varTypes.get(i) instanceof TypeBool) { basicModelInfo.getVarList().addVar(varNames.get(i), new DeclarationBool(), -1); } else { + // Note: we do not yet allow 0-range variables + if (varMins[i] == varMaxs[i]) { + varMaxs[i]++; + } basicModelInfo.getVarList().addVar(varNames.get(i), new DeclarationInt(Expression.Int(varMins[i]), Expression.Int(varMaxs[i])), -1); } } From 7aba2abbd17b58e8e7956c184f1a90f6b2b8dd7e Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Fri, 30 May 2025 18:13:41 +0100 Subject: [PATCH 43/44] Fix (expected) Markov chain transition rewards imports. For importing from explicit files with the explicit engine, Markov chain transition rewards often need to be converted to expected state rewards before model checking, as is done when constructing rewards from a PRISM model / generator. Not currently testable with regression tests but: prism -importmodel ../prism-tests/functionality/import/lec3.all -pf 'R{"r"}=?[C<=10]' -ex should return 4.552734375 not 2.6640625. --- prism/src/explicit/DTMCSparse.java | 1 + .../explicit/rewards/ConstructRewards.java | 43 ++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/prism/src/explicit/DTMCSparse.java b/prism/src/explicit/DTMCSparse.java index 1a89df5858..e17d4a6add 100644 --- a/prism/src/explicit/DTMCSparse.java +++ b/prism/src/explicit/DTMCSparse.java @@ -474,6 +474,7 @@ public double mvMultRewSingle(final int state, final double[] vect, final MCRewa for (int i=rows[state], stop=rows[state+1]; i < stop; i++) { final int target = columns[i]; final double probability = probabilities[i]; + //d += probability * (mcRewards.getTransitionReward(state, i-rows[state]) + vect[target]); d += probability * vect[target]; } return d; diff --git a/prism/src/explicit/rewards/ConstructRewards.java b/prism/src/explicit/rewards/ConstructRewards.java index bc606de94a..314566a955 100644 --- a/prism/src/explicit/rewards/ConstructRewards.java +++ b/prism/src/explicit/rewards/ConstructRewards.java @@ -96,7 +96,7 @@ public Rewards buildRewardStructure(Model model, RewardGen // If the RewardGenerator already has the rewards built, use this (after checking) if (rewardGen.isRewardLookupSupported(RewardLookup.BY_REWARD_OBJECT)) { Rewards rewardsObj = rewardGen.getRewardObject(r); - checkRewardObject(rewardsObj, rewardGen.getRewardObjectModel(), rewardGen.getRewardEvaluator()); + rewardsObj = checkRewardObject(rewardsObj, rewardGen.getRewardObjectModel(), rewardGen.getRewardEvaluator()); return rewardsObj; } // Extract some model info @@ -504,12 +504,24 @@ private void checkTransitionReward(Value rew, Evaluator eval, Obj * @param model The model for the rewards * @param eval Evaluator matching the type {@code Value} of the reward value */ - private void checkRewardObject(Rewards rewards, Model model, Evaluator eval) throws PrismException + private Rewards checkRewardObject(Rewards rewards, Model model, Evaluator eval) throws PrismException { int numStates = model.getNumStates(); + // In some cases, we need to create a new Rewards object + // in which (Markov chain) transition rewards are converted to expected rewards + RewardsExplicit rewardsRet = null; + boolean convertToExpected = !model.getModelType().nondeterministic() && rewards.hasTransitionRewards() && expectedRewards; + if (convertToExpected) { + rewardsRet = new RewardsSimple<>(numStates); + rewardsRet.setEvaluator(rewards.getEvaluator()); + } // State rewards for (int s = 0; s < numStates; s++) { - checkStateReward(rewards.getStateReward(s), eval, s, null); + Value rew = rewards.getStateReward(s); + checkStateReward(rew, eval, s, null); + if (convertToExpected) { + rewardsRet.setStateReward(s, rew); + } } // Transition rewards (nondet models) if (model.getModelType().nondeterministic()) { @@ -523,12 +535,31 @@ private void checkRewardObject(Rewards rewards, Model mode // Transition rewards (Markov chain like models) else { for (int s = 0; s < numStates; s++) { - int numTrans = model.getNumTransitions(s); - for (int i = 0; i < numTrans; i++) { - checkTransitionReward(rewards.getTransitionReward(s, i), eval, s, null); + if (!convertToExpected) { + int numTrans = model.getNumTransitions(s); + for (int i = 0; i < numTrans; i++) { + checkTransitionReward(rewards.getTransitionReward(s, i), eval, s, null); + } + } else { + DTMC mcModel = (DTMC) model; + Iterator> iter = mcModel.getTransitionsIterator(s); + int i = 0; + while (iter.hasNext()) { + Map.Entry e = iter.next(); + Value rew = rewards.getTransitionReward(s, i); + checkTransitionReward(rew, eval, s, null); + if (rewards.getEvaluator().isZero(rew)) { + i++; + continue; + } + Value rewWeighted = rewards.getEvaluator().multiply(e.getValue(), rew); + rewardsRet.addToStateReward(s, rewWeighted); + i++; + } } } } + return convertToExpected ? rewardsRet : rewards; } /** From 687d20986f90ebc474baeb55a5a2a60374aeb5bb Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Thu, 5 Jun 2025 18:15:26 +0100 Subject: [PATCH 44/44] Compile fix (enums in switches). --- prism/src/userinterface/model/GUIMultiModel.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/prism/src/userinterface/model/GUIMultiModel.java b/prism/src/userinterface/model/GUIMultiModel.java index 68128509a0..0d0fd8b206 100644 --- a/prism/src/userinterface/model/GUIMultiModel.java +++ b/prism/src/userinterface/model/GUIMultiModel.java @@ -367,16 +367,16 @@ protected void a_exportBuildAs(ModelExportEntity exportEntity, ModelExportFormat break; default: switch (exportEntity) { - case ModelExportEntity.STATES: + case STATES: res = showSaveFileDialog(staFilters.values(), staFilters.get("sta")); break; - case ModelExportEntity.MODEL: + case MODEL: res = showSaveFileDialog(traFilters.values(), traFilters.get("tra")); break; - case ModelExportEntity.OBSERVATIONS: + case OBSERVATIONS: res = showSaveFileDialog(obsFilters.values(), obsFilters.get("obs")); break; - case ModelExportEntity.LABELS: + case LABELS: res = showSaveFileDialog(labFilters.values(), labFilters.get("lab")); break; default: @@ -419,7 +419,7 @@ protected void a_exportSteadyState(ModelExportFormat exportFormat) case MATLAB: res = showSaveFileDialog(matlabFilter); break; - case ModelExportFormat.EXPLICIT: + case EXPLICIT: default: res = showSaveFileDialog(textFilter); break; @@ -450,10 +450,10 @@ protected void a_exportTransient(ModelExportFormat exportFormat) // Pop up dialog to select file int res = JFileChooser.CANCEL_OPTION; switch (exportFormat) { - case ModelExportFormat.MATLAB: + case MATLAB: res = showSaveFileDialog(matlabFilter); break; - case ModelExportFormat.EXPLICIT: + case EXPLICIT: default: res = showSaveFileDialog(textFilter); break;