You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
502 lines
17 KiB
502 lines
17 KiB
//==============================================================================
|
|
//
|
|
// Copyright (c) 2002-
|
|
// Authors:
|
|
// * Dave Parker <david.parker@comlab.ox.ac.uk> (University of Oxford)
|
|
// * Vojtech Forejt <vojtech.forejt@cs.ox.ac.uk> (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 explicit.rewards;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileReader;
|
|
import java.io.IOException;
|
|
import java.util.List;
|
|
|
|
import explicit.DTMC;
|
|
import explicit.MDP;
|
|
import explicit.Model;
|
|
import parser.State;
|
|
import parser.Values;
|
|
import parser.ast.ASTElement;
|
|
import parser.ast.Expression;
|
|
import parser.ast.RewardStruct;
|
|
import prism.PrismComponent;
|
|
import prism.PrismException;
|
|
import prism.PrismLangException;
|
|
import prism.PrismNotSupportedException;
|
|
import prism.RewardGenerator;
|
|
import prism.RewardGenerator.RewardLookup;
|
|
|
|
public class ConstructRewards extends PrismComponent
|
|
{
|
|
public ConstructRewards(PrismComponent parent)
|
|
{
|
|
super(parent);
|
|
}
|
|
|
|
/** Allow negative rewards, i.e., weights. Defaults to false. */
|
|
protected boolean allowNegative = false;
|
|
|
|
/** Set flag that negative rewards are allowed, i.e., weights */
|
|
public void allowNegativeRewards()
|
|
{
|
|
allowNegative = true;
|
|
}
|
|
|
|
/**
|
|
* Construct the rewards for a model from a reward generator.
|
|
* @param model The model
|
|
* @param rewardGen The RewardGenerator defining the rewards
|
|
* @param r The index of the reward structure to build
|
|
*/
|
|
public Rewards buildRewardStructure(Model model, RewardGenerator rewardGen, int r) throws PrismException
|
|
{
|
|
switch (model.getModelType()) {
|
|
case DTMC:
|
|
case CTMC:
|
|
return buildMCRewardStructure((DTMC) model, rewardGen, r);
|
|
case MDP:
|
|
return buildMDPRewardStructure((MDP) model, rewardGen, r);
|
|
default:
|
|
throw new PrismNotSupportedException("Cannot build rewards for " + model.getModelType() + "s");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Construct the rewards for a Markov chain (DTMC or CTMC) from a reward generator.
|
|
* @param mc The DTMC or CTMC
|
|
* @param rewardGen The RewardGenerator defining the rewards
|
|
* @param r The index of the reward structure to build
|
|
*/
|
|
public MCRewards buildMCRewardStructure(DTMC mc, RewardGenerator rewardGen, int r) throws PrismException
|
|
{
|
|
if (rewardGen.rewardStructHasTransitionRewards(r)) {
|
|
throw new PrismNotSupportedException("Explicit engine does not yet handle transition rewards for D/CTMCs");
|
|
}
|
|
int numStates = mc.getNumStates();
|
|
List<State> statesList = mc.getStatesList();
|
|
StateRewardsArray rewSA = new StateRewardsArray(numStates);
|
|
for (int s = 0; s < numStates; s++) {
|
|
if (rewardGen.rewardStructHasStateRewards(r)) {
|
|
double rew = getAndCheckStateReward(s, rewardGen, r, statesList);
|
|
rewSA.addToStateReward(s, rew);
|
|
}
|
|
}
|
|
return rewSA;
|
|
}
|
|
|
|
/**
|
|
* Construct the rewards for an MDP from a reward generator.
|
|
* @param mdp The MDP
|
|
* @param rewardGen The RewardGenerator defining the rewards
|
|
* @param r The index of the reward structure to build
|
|
*/
|
|
public MDPRewards buildMDPRewardStructure(MDP mdp, RewardGenerator rewardGen, int r) throws PrismException
|
|
{
|
|
int numStates = mdp.getNumStates();
|
|
List<State> statesList = mdp.getStatesList();
|
|
MDPRewardsSimple rewSimple = new MDPRewardsSimple(numStates);
|
|
for (int s = 0; s < numStates; s++) {
|
|
if (rewardGen.rewardStructHasStateRewards(r)) {
|
|
double rew = getAndCheckStateReward(s, rewardGen, r, statesList);
|
|
rewSimple.addToStateReward(s, rew);
|
|
}
|
|
if (rewardGen.rewardStructHasTransitionRewards(r)) {
|
|
// Don't add rewards to transitions added to "fix" deadlock states
|
|
if (mdp.isDeadlockState(s)) {
|
|
continue;
|
|
}
|
|
int numChoices = mdp.getNumChoices(s);
|
|
for (int k = 0; k < numChoices; k++) {
|
|
double rew = getAndCheckStateActionReward(s, mdp.getAction(s, k), rewardGen, r, statesList);
|
|
rewSimple.addToTransitionReward(s, k, rew);
|
|
}
|
|
}
|
|
}
|
|
return rewSimple;
|
|
}
|
|
|
|
/**
|
|
* Get a state reward for a specific state and reward structure from a RewardGenerator.
|
|
* Also check that the state reward is legal. Throw an exception if not.
|
|
* @param s The index of the state
|
|
* @param rewardGen The RewardGenerator defining the rewards
|
|
* @param r The index of the reward structure to build
|
|
* @param statesLists List of states (maybe needed for state look up)
|
|
*/
|
|
private double getAndCheckStateReward(int s, RewardGenerator rewardGen, int r, List<State> statesList) throws PrismException
|
|
{
|
|
double rew = 0;
|
|
Object stateIndex = null;
|
|
if (rewardGen.isRewardLookupSupported(RewardLookup.BY_STATE)) {
|
|
State state = statesList.get(s);
|
|
stateIndex = state;
|
|
rew = rewardGen.getStateReward(r, state);
|
|
} else if (rewardGen.isRewardLookupSupported(RewardLookup.BY_STATE_INDEX)) {
|
|
stateIndex = s;
|
|
rew = rewardGen.getStateReward(r, s);
|
|
} else {
|
|
throw new PrismException("Unknown reward lookup mechanism for reward generator");
|
|
}
|
|
checkStateReward(rew, stateIndex, null);
|
|
return rew;
|
|
}
|
|
|
|
/**
|
|
* Get a state reward for a specific state and reward structure from a RewardGenerator.
|
|
* Also check that the state reward is legal. Throw an exception if not.
|
|
* @param s The index of the state
|
|
* @param rewardGen The RewardGenerator defining the rewards
|
|
* @param r The index of the reward structure to build
|
|
* @param statesLists List of states (maybe needed for state look up)
|
|
*/
|
|
private double getAndCheckStateActionReward(int s, Object action, RewardGenerator rewardGen, int r, List<State> statesList) throws PrismException
|
|
{
|
|
double rew = 0;
|
|
Object stateIndex = null;
|
|
if (rewardGen.isRewardLookupSupported(RewardLookup.BY_STATE)) {
|
|
State state = statesList.get(s);
|
|
stateIndex = state;
|
|
rew = rewardGen.getStateActionReward(r, state, action);
|
|
} else if (rewardGen.isRewardLookupSupported(RewardLookup.BY_STATE_INDEX)) {
|
|
stateIndex = s;
|
|
rew = rewardGen.getStateActionReward(r, s, action);
|
|
} else {
|
|
throw new PrismException("Unknown reward lookup mechanism for reward generator");
|
|
}
|
|
checkTransitionReward(rew, stateIndex, null);
|
|
return rew;
|
|
}
|
|
|
|
/**
|
|
* Construct rewards from a model and reward structure.
|
|
* @param model The model
|
|
* @param rewStr The reward structure
|
|
* @param constantValues Values for any undefined constants needed
|
|
*/
|
|
public Rewards buildRewardStructure(Model model, RewardStruct rewStr, Values constantValues) throws PrismException
|
|
{
|
|
switch (model.getModelType()) {
|
|
case DTMC:
|
|
case CTMC:
|
|
return buildMCRewardStructure((DTMC) model, rewStr, constantValues);
|
|
case MDP:
|
|
return buildMDPRewardStructure((MDP) model, rewStr, constantValues);
|
|
default:
|
|
throw new PrismNotSupportedException("Cannot build rewards for " + model.getModelType() + "s");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Construct the rewards for a Markov chain (DTMC or CTMC) from a model and reward structure.
|
|
* @param mc The DTMC or CTMC
|
|
* @param rewStr The reward structure
|
|
* @param constantValues Values for any undefined constants needed
|
|
*/
|
|
public MCRewards buildMCRewardStructure(DTMC mc, RewardStruct rewStr, Values constantValues) throws PrismException
|
|
{
|
|
List<State> statesList;
|
|
Expression guard;
|
|
int i, j, n, numStates;
|
|
|
|
if (rewStr.getNumTransItems() > 0) {
|
|
throw new PrismNotSupportedException("Explicit engine does not yet handle transition rewards for D/CTMCs");
|
|
}
|
|
// Special case: constant rewards
|
|
if (rewStr.getNumStateItems() == 1 && Expression.isTrue(rewStr.getStates(0)) && rewStr.getReward(0).isConstant()) {
|
|
double rew = rewStr.getReward(0).evaluateDouble(constantValues);
|
|
checkStateReward(rew, null, rewStr.getReward(0));
|
|
return new StateRewardsConstant(rew);
|
|
}
|
|
// Normal: state rewards
|
|
else {
|
|
numStates = mc.getNumStates();
|
|
statesList = mc.getStatesList();
|
|
StateRewardsArray rewSA = new StateRewardsArray(numStates);
|
|
n = rewStr.getNumItems();
|
|
for (i = 0; i < n; i++) {
|
|
guard = rewStr.getStates(i);
|
|
for (j = 0; j < numStates; j++) {
|
|
if (guard.evaluateBoolean(constantValues, statesList.get(j))) {
|
|
double rew = rewStr.getReward(i).evaluateDouble(constantValues, statesList.get(j));
|
|
checkStateReward(rew, statesList.get(j), rewStr.getReward(i));
|
|
rewSA.addToStateReward(j, rew);
|
|
}
|
|
}
|
|
}
|
|
return rewSA;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Construct the rewards for an MDP from a model and reward structure.
|
|
* @param mdp The MDP
|
|
* @param rewStr The reward structure
|
|
* @param constantValues Values for any undefined constants needed
|
|
*/
|
|
public MDPRewards buildMDPRewardStructure(MDP mdp, RewardStruct rewStr, Values constantValues) throws PrismException
|
|
{
|
|
List<State> statesList;
|
|
Expression guard;
|
|
String action;
|
|
Object mdpAction;
|
|
int i, j, k, n, numStates, numChoices;
|
|
|
|
// Special case: constant state rewards
|
|
if (rewStr.getNumStateItems() == 1 && Expression.isTrue(rewStr.getStates(0)) && rewStr.getReward(0).isConstant()) {
|
|
double rew = rewStr.getReward(0).evaluateDouble(constantValues);
|
|
checkStateReward(rew, null, rewStr.getReward(0));
|
|
return new StateRewardsConstant(rew);
|
|
}
|
|
// Normal: state and transition rewards
|
|
else {
|
|
numStates = mdp.getNumStates();
|
|
statesList = mdp.getStatesList();
|
|
MDPRewardsSimple rewSimple = new MDPRewardsSimple(numStates);
|
|
n = rewStr.getNumItems();
|
|
for (i = 0; i < n; i++) {
|
|
guard = rewStr.getStates(i);
|
|
action = rewStr.getSynch(i);
|
|
for (j = 0; j < numStates; j++) {
|
|
// Is guard satisfied?
|
|
if (guard.evaluateBoolean(constantValues, statesList.get(j))) {
|
|
// Transition reward
|
|
if (rewStr.getRewardStructItem(i).isTransitionReward()) {
|
|
if (mdp.isDeadlockState(j)) {
|
|
// As state s is a deadlock state, any outgoing transition
|
|
// was added to "fix" the deadlock and thus does not get a reward.
|
|
// Skip to next state
|
|
continue;
|
|
}
|
|
numChoices = mdp.getNumChoices(j);
|
|
for (k = 0; k < numChoices; k++) {
|
|
mdpAction = mdp.getAction(j, k);
|
|
if (mdpAction == null ? (action.isEmpty()) : mdpAction.equals(action)) {
|
|
double rew = rewStr.getReward(i).evaluateDouble(constantValues, statesList.get(j));
|
|
checkTransitionReward(rew, statesList.get(j), rewStr.getReward(i));
|
|
rewSimple.addToTransitionReward(j, k, rew);
|
|
}
|
|
}
|
|
}
|
|
// State reward
|
|
else {
|
|
double rew = rewStr.getReward(i).evaluateDouble(constantValues, statesList.get(j));
|
|
checkStateReward(rew, statesList.get(j), rewStr.getReward(i));
|
|
rewSimple.addToStateReward(j, rew);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return rewSimple;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Construct the rewards for a Markov chain (DTMC or CTMC) from files exported explicitly by PRISM.
|
|
* @param mc The DTMC or CTMC
|
|
* @param rews The file containing state rewards (ignored if null)
|
|
* @param rewt The file containing transition rewards (ignored if null)
|
|
*/
|
|
public MCRewards buildMCRewardsFromPrismExplicit(DTMC mc, File rews, File rewt) throws PrismException
|
|
{
|
|
String s, ss[];
|
|
int i, lineNum = 0;
|
|
double reward;
|
|
StateRewardsArray rewSA = new StateRewardsArray(mc.getNumStates());
|
|
|
|
if (rews != null) {
|
|
// Open state rewards file, automatic close
|
|
try (BufferedReader in = new BufferedReader(new FileReader(rews))) {
|
|
// Ignore first line
|
|
s = in.readLine();
|
|
lineNum = 1;
|
|
if (s == null) {
|
|
throw new PrismException("Missing first line of state rewards file");
|
|
}
|
|
// Go though list of state rewards in file
|
|
s = in.readLine();
|
|
lineNum++;
|
|
while (s != null) {
|
|
s = s.trim();
|
|
if (s.length() > 0) {
|
|
ss = s.split(" ");
|
|
i = Integer.parseInt(ss[0]);
|
|
reward = Double.parseDouble(ss[1]);
|
|
checkStateReward(reward, i, null);
|
|
rewSA.setStateReward(i, reward);
|
|
}
|
|
s = in.readLine();
|
|
lineNum++;
|
|
}
|
|
} catch (IOException e) {
|
|
throw new PrismException("Could not read state rewards from file \"" + rews + "\"" + e);
|
|
} catch (NumberFormatException e) {
|
|
throw new PrismException("Problem in state rewards file (line " + lineNum + ") for MDP");
|
|
}
|
|
}
|
|
|
|
if (rewt != null) {
|
|
throw new PrismNotSupportedException("Explicit engine does not yet handle transition rewards for D/CTMCs");
|
|
}
|
|
|
|
return rewSA;
|
|
}
|
|
|
|
/**
|
|
* Construct the rewards for an MDP from files exported explicitly by PRISM.
|
|
* @param model The MDP
|
|
* @param rews The file containing state rewards (ignored if null)
|
|
* @param rewt The file containing transition rewards (ignored if null)
|
|
*/
|
|
public MDPRewards buildMDPRewardsFromPrismExplicit(MDP mdp, File rews, File rewt) throws PrismException
|
|
{
|
|
String s, ss[];
|
|
int i, j, lineNum = 0;
|
|
double reward;
|
|
MDPRewardsSimple rs = new MDPRewardsSimple(mdp.getNumStates());
|
|
|
|
if (rews != null) {
|
|
// Open state rewards file, automatic close
|
|
try (BufferedReader in = new BufferedReader(new FileReader(rews))) {
|
|
// Ignore first line
|
|
s = in.readLine();
|
|
lineNum = 1;
|
|
if (s == null) {
|
|
throw new PrismException("Missing first line of state rewards file");
|
|
}
|
|
// Go though list of state rewards in file
|
|
s = in.readLine();
|
|
lineNum++;
|
|
while (s != null) {
|
|
s = s.trim();
|
|
if (s.length() > 0) {
|
|
ss = s.split(" ");
|
|
i = Integer.parseInt(ss[0]);
|
|
reward = Double.parseDouble(ss[1]);
|
|
checkStateReward(reward, i, null);
|
|
rs.setStateReward(i, reward);
|
|
}
|
|
s = in.readLine();
|
|
lineNum++;
|
|
}
|
|
} catch (IOException e) {
|
|
throw new PrismException("Could not read state rewards from file \"" + rews + "\"" + e);
|
|
} catch (NumberFormatException e) {
|
|
throw new PrismException("Problem in state rewards file (line " + lineNum + ") for MDP");
|
|
}
|
|
}
|
|
|
|
if (rewt != null) {
|
|
// Open transition rewards file, automatic close
|
|
try (BufferedReader in = new BufferedReader(new FileReader(rewt))) {
|
|
// Ignore first line
|
|
s = in.readLine();
|
|
lineNum = 1;
|
|
if (s == null) {
|
|
throw new PrismException("Missing first line of transition rewards file");
|
|
}
|
|
// Go though list of transition rewards in file
|
|
s = in.readLine();
|
|
lineNum++;
|
|
while (s != null) {
|
|
s = s.trim();
|
|
if (s.length() > 0) {
|
|
ss = s.split(" ");
|
|
i = Integer.parseInt(ss[0]);
|
|
j = Integer.parseInt(ss[1]);
|
|
reward = Double.parseDouble(ss[3]);
|
|
checkTransitionReward(reward, i, null);
|
|
rs.setTransitionReward(i, j, reward);
|
|
}
|
|
s = in.readLine();
|
|
lineNum++;
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
throw new PrismException("Could not read transition rewards from file \"" + rewt + "\"" + e);
|
|
} catch (NumberFormatException e) {
|
|
throw new PrismException("Problem in transition rewards file (line " + lineNum + ") for MDP");
|
|
}
|
|
}
|
|
|
|
return rs;
|
|
}
|
|
|
|
/**
|
|
* Check that a state reward is legal. Throw an exception if not.
|
|
* Optionally, provide a state where the error occurs (as an Object),
|
|
* and/or a pointer to where the error occurs syntactically (as an ASTElement)
|
|
* @param rew The reward value
|
|
* @param stateIndex The index of the state, for error reporting (optional)
|
|
* @param ast Where the error occurred, for error reporting (optional)
|
|
*/
|
|
private void checkStateReward(double rew, Object stateIndex, ASTElement ast) throws PrismException
|
|
{
|
|
String error = null;
|
|
if (!Double.isFinite(rew)) {
|
|
error = "State reward is not finite";
|
|
} else if (!allowNegative && rew < 0) {
|
|
error = "State reward is negative (" + rew + ")";
|
|
}
|
|
if (error != null) {
|
|
if (stateIndex != null) {
|
|
error += " at state " + stateIndex;
|
|
}
|
|
if (ast != null) {
|
|
throw new PrismLangException(error, ast);
|
|
} else {
|
|
throw new PrismException(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that a state reward is legal. Throw an exception if not.
|
|
* @param rew The reward value
|
|
* Optionally, provide a state where the error occurs (as an Object),
|
|
* and/or a pointer to where the error occurs syntactically (as an ASTElement)
|
|
* @param rew The reward value
|
|
* @param stateIndex The index of the state, for error reporting (optional)
|
|
* @param ast Where the error occurred, for error reporting (optional)
|
|
*/
|
|
private void checkTransitionReward(double rew, Object stateIndex, ASTElement ast) throws PrismException
|
|
{
|
|
String error = null;
|
|
if (!Double.isFinite(rew)) {
|
|
error = "Transition reward is not finite";
|
|
} else if (!allowNegative && rew < 0) {
|
|
error = "Transition reward is negative (" + rew + ")";
|
|
}
|
|
if (error != null) {
|
|
if (stateIndex != null) {
|
|
error += " at state " + stateIndex;
|
|
}
|
|
if (ast != null) {
|
|
throw new PrismLangException(error, ast);
|
|
} else {
|
|
throw new PrismException(error);
|
|
}
|
|
}
|
|
}
|
|
}
|