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.
 
 
 
 
 
 

595 lines
17 KiB

//==============================================================================
//
// Copyright (c) 2013-
// Authors:
// * Ernst Moritz Hahn <emhahn@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 param;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Random;
/**
* Implements a region representation by a box (hyper-rectangle).
* This means that for each parameter we have a lower and upper bound, and
* the region contains all points so that the corresponding dimensions are
* (not strictly) between these lower and upper bounds.
*
* @author Ernst Moritz Hahn <emhahn@cs.ox.ac.uk> (University of Oxford)
* @see BoxRegionFactory
*/
final class BoxRegion extends Region {
/** used to produce random inner points of box regions */
static Random random = new Random();
/** regions are only to be split at longest side */
static final int SPLIT_LONGEST = 1;
/** regions are to be split at all sides on each split */
static final int SPLIT_ALL = 2;
/** lower bound for each parameter */
private BigRational[] lower;
/** upper bound for each parameter */
private BigRational[] upper;
// constructors
/**
* Constructs a new box region.
* Does not assign lower and upper bounds, that is the regions is
* not direcly usable after construction.
*
* @param factory factory this box region belongs to
*/
private BoxRegion(BoxRegionFactory factory)
{
this.factory = factory;
lower = new BigRational[factory.numVariables()];
upper = new BigRational[factory.numVariables()];
}
/**
* Constructs a new box region.
*
* @param factory factory this box region belongs to
* @param lower lower bounds for each parameter
* @param upper upper bounds for each parameter
*/
BoxRegion(BoxRegionFactory factory, BigRational[] lower, BigRational[] upper)
{
this.factory = factory;
this.lower = new BigRational[lower.length];
this.upper = new BigRational[upper.length];
System.arraycopy(lower, 0, this.lower, 0, lower.length);
System.arraycopy(upper, 0, this.upper, 0, upper.length);
}
/**
* Copy constructor for box regions.
* For internal use, because regions are generally immutable, so that
* there is no need to have external copy constructors.
*
* @param other region to copy from
*/
private BoxRegion(BoxRegion other)
{
this.factory = other.factory;
this.lower = new BigRational[other.lower.length];
this.upper = new BigRational[other.upper.length];
System.arraycopy(other.lower, 0, this.lower, 0, other.lower.length);
System.arraycopy(other.upper, 0, this.upper, 0, other.upper.length);
}
/**
* Sets lower and upper bound of given parameter.
*
* @param dim parameter to set bounds of
* @param lower new lower bound of parameter
* @param upper new upper bound of parameter
*/
private void setDimension(int dim, BigRational lower, BigRational upper)
{
this.lower[dim] = lower;
this.upper[dim] = upper;
}
@Override
int getDimensions()
{
return lower.length;
}
/**
* Get lower bound of given parameter in region.
*
* @param dim parameter to get lower bound of
* @return lower bound of given parameter in region
*/
BigRational getDimensionLower(int dim)
{
return lower[dim];
}
/**
* Get upper bound of given parameter in region.
*
* @param dim parameter to get lower bound of
* @return upper bound of given parameter in region
*/
BigRational getDimensionUpper(int dim)
{
return upper[dim];
}
@Override
public boolean equals(Object object) {
if (!(object instanceof BoxRegion)) {
return false;
}
BoxRegion other = (BoxRegion) object;
if (this.getDimensions() != other.getDimensions()) {
return false;
}
for (int i = 0; i < getDimensions(); i++) {
if (!this.lower[i].equals(other.lower[i])) {
return false;
}
if (!this.upper[i].equals(other.upper[i])) {
return false;
}
}
return true;
}
@Override
public int hashCode()
{
int hash = 0;
for (int i = 0; i < getDimensions(); i++) {
hash = lower[i].hashCode() + (hash << 6) + (hash << 16) - hash;
hash = upper[i].hashCode() + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
/**
* Gets the central points of the region.
*
* @return point in the centre of the region
*/
Point getMidPoint()
{
BigRational[] point = new BigRational[lower.length];
for (int dim = 0; dim < lower.length; dim++) {
BigRational mid = lower[dim].add(upper[dim]).divide(2);
point[dim] = mid;
}
return new Point(point);
}
/**
* Get volume of the region.
* The volumes of disjoint region which cover the whole parameter
* space sum up to 1. This implies that the volumes are normalised
* according to the upper and lower bounds of the parameters.
*
* @return volume of this region
*/
@Override
BigRational volume()
{
BigRational volume = BigRational.ONE;
for (int dim = 0; dim < lower.length; dim++) {
volume = volume.multiply(upper[dim].subtract(lower[dim]));
volume = volume.divide(factory.sideWidth(dim));
}
return volume;
}
@Override
boolean contains(Point point)
{
for (int dim = 0; dim < getDimensions(); dim++) {
if (point.getDimension(dim).compareTo(lower[dim]) < 0) {
return false;
}
if (point.getDimension(dim).compareTo(upper[dim]) > 0) {
return false;
}
}
return true;
}
@Override
boolean contains(Region region)
{
BoxRegion other = (BoxRegion) region;
for (int dim = 0; dim < getDimensions(); dim++) {
if (other.lower[dim].compareTo(this.lower[dim]) == -1) {
return false;
}
if (other.upper[dim].compareTo(this.upper[dim]) == +1) {
return false;
}
}
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("(");
for (int dim = 0; dim < getDimensions(); dim++) {
builder.append("[");
builder.append(lower[dim].doubleValue());
builder.append(",");
builder.append(upper[dim].doubleValue());
builder.append("]");
if (dim < getDimensions() - 1) {
builder.append(",");
}
}
builder.append(")");
return builder.toString();
}
@Override
RegionValues binaryOp(int op, StateValues values1, StateValues values2) {
RegionValues result;
if ((op == Region.EQ || op == Region.NE || op == Region.GT || op == Region.GE || op == Region.LT || op == Region.LE)) {
result = cmpOp(op, values1, values2);
} else {
result = new RegionValues(factory);
int numStates = values1.getNumStates();
StateValues values = new StateValues(numStates, factory.getInitialState());
for (int state = 0; state < numStates; state++) {
if (op == Region.IMPLIES || op == Region.IFF || op == Region.OR || op == Region.AND) {
boolean stateVal = booleanOp(op, values1.getStateValueAsBoolean(state), values2.getStateValueAsBoolean(state));
values.setStateValue(state, stateVal);
} else if (op == Region.PLUS || op == Region.MINUS || op == Region.TIMES || op == Region.DIVIDE) {
Function stateVal = arithOp(op, values1.getStateValueAsFunction(state), values2.getStateValueAsFunction(state));
values.setStateValue(state, stateVal);
} else {
throw new UnsupportedOperationException("operator not yet implemented for parametric analyses");
}
}
result.add(this, values);
}
return result;
}
private RegionValues cmpOp(int op, StateValues op1, StateValues op2) {
ConstraintChecker checker = factory.getConstraintChecker();
RegionsTODO remaining = new RegionsTODO();
remaining.add(this);
BigRational requiredVolume = this.volume().multiply(BigRational.ONE.subtract(factory.getPrecision()));
BigRational doneVolume = BigRational.ZERO;
RegionValues result = new RegionValues(factory);
Function lastFunction = null;
while (doneVolume.compareTo(requiredVolume) == -1) {
BoxRegion region = (BoxRegion) remaining.poll();
StateValues newValues = new StateValues(op1.getNumStates(), factory.getInitialState());
boolean allDecided = true;
for (int state = 0; state < op1.getNumStates(); state++) {
StateValue op1Val = op1.getStateValue(state);
StateValue op2Val = op2.getStateValue(state);
Function op1ValFn = op1Val instanceof Function ? (Function) op1Val : null;
Function op2ValFn = op2Val instanceof Function ? (Function) op2Val : null;
if (op == Region.EQ) {
if (op1Val instanceof StateBoolean) {
newValues.setStateValue(state, op1Val.equals(op2Val));
}
else if (op1Val.equals(op2Val)) {
newValues.setStateValue(state, true);
} else if (checker.check(region, op1ValFn.subtract(op2ValFn), true)) {
newValues.setStateValue(state, false);
} else if (checker.check(region, op2ValFn.subtract(op1ValFn), true)) {
newValues.setStateValue(state, false);
} else {
allDecided = false;
break;
}
} else if (op == Region.NE) {
if (op1Val instanceof StateBoolean) {
newValues.setStateValue(state, !op1Val.equals(op2Val));
} else if (op1Val.equals(op2Val)) {
newValues.setStateValue(state, false);
} else if (checker.check(region, op1ValFn.subtract(op2ValFn), true)) {
newValues.setStateValue(state, true);
} else if (checker.check(region, op2ValFn.subtract(op1ValFn), true)) {
newValues.setStateValue(state, true);
} else {
allDecided = false;
break;
}
} else {
boolean strict = op == Region.GT || op == Region.LT;
Function cmpTrue = (op == Region.LT || op == Region.LE) ? op2ValFn.subtract(op1ValFn) : op1ValFn.subtract(op2ValFn);
if (checker.check(region, cmpTrue, strict)) {
newValues.setStateValue(state, true);
} else {
Function cmpFalse = (op == Region.LT || op == Region.LE) ? op1ValFn.subtract(op2ValFn) : op2ValFn.subtract(op1ValFn);
if (checker.check(region, cmpFalse, !strict)) {
newValues.setStateValue(state, false);
} else {
allDecided = false;
lastFunction = op2ValFn.subtract(op1ValFn);
break;
}
}
}
}
if (allDecided) {
result.add(region, newValues);
doneVolume = doneVolume.add(region.volume());
} else {
remaining.addAll(region.split(lastFunction));
}
}
return result;
}
/**
* Performs given operation on two rational functions.
*
* @param op operation to perform, see values in {@code Region}
* @param op1 first operand
* @param op2 second operand
* @return value of operation
*/
private Function arithOp(int op, Function op1, Function op2) {
Function result = null;
switch (op) {
case Region.PLUS:
result = op1.add(op2);
break;
case Region.MINUS:
result = op1.subtract(op2);
break;
case Region.TIMES:
result = op1.multiply(op2);
break;
case Region.DIVIDE:
result = op1.divide(op2);
break;
default:
throw new IllegalArgumentException("unsupported arithmetic operator number " + op);
}
return result;
}
/**
* Performs given operation on two booleans.
*
* @param op operation to perform, see values in {@code Region}
* @param op1 first operand
* @param op2 second operand
* @return value of operation
*/
private boolean booleanOp(int op, boolean op1, boolean op2) {
boolean result = false;
switch (op) {
case Region.IMPLIES:
result = !op1 || op2;
break;
case Region.IFF:
result = op1 == op2;
break;
case Region.OR:
result = op1 || op2;
break;
case Region.AND:
result = op1 && op2;
break;
default:
throw new IllegalArgumentException("unsupported boolean operator number " + op);
}
return result;
}
/**
* Split region in longest dimension.
*
* @return set of new regions covering same area
*/
private ArrayList<Region> splitLongest()
{
int longestSide = -1;
BigRational longestLength = BigRational.ZERO;
for (int side = 0; side < lower.length; side++) {
BigRational sideLength = upper[side].subtract(lower[side]);
if (sideLength.compareTo(longestLength) == 1) {
longestSide = side;
longestLength = sideLength;
}
}
ArrayList<Region> result = new ArrayList<Region>();
BoxRegion region1 = new BoxRegion(this);
BoxRegion region2 = new BoxRegion(this);
BigRational mid = (lower[longestSide].add(upper[longestSide])).divide(2);
region1.upper[longestSide] = mid;
region2.lower[longestSide] = mid;
result.add(region1);
result.add(region2);
return result;
}
/**
* Split region in all dimensions.
*
* @return set of new regions covering same area
*/
private ArrayList<Region> splitAll()
{
ArrayList<Region> result = new ArrayList<Region>();
final int numParts = 2;
final int numNewRegions = (int) Math.pow(numParts, lower.length);
BigRational[] newLower = new BigRational[lower.length];
BigRational[] newUpper = new BigRational[upper.length];
for (int newRegionNr = 0; newRegionNr < numNewRegions; newRegionNr++) {
int regionRest = newRegionNr;
for (int var = 0; var < lower.length; var++) {
int lowerPart = regionRest % numParts;
regionRest /= numParts;
BigRational partLength = upper[var].subtract(lower[var]).divide(numParts);
newLower[var] = lower[var].add(partLength.multiply(lowerPart));
newUpper[var] = lower[var].add(partLength.multiply(lowerPart + 1));
}
result.add(new BoxRegion((BoxRegionFactory) factory, newLower, newUpper));
}
return result;
}
@Override
ArrayList<Region> split(Function constraint)
{
// TODO could implement more clever splitting using constraints
if (((BoxRegionFactory) factory).getSplitMethod() == SPLIT_LONGEST) {
return splitLongest();
} else if (((BoxRegionFactory) factory).getSplitMethod() == SPLIT_ALL) {
return splitAll();
} else {
throw new RuntimeException();
}
}
@Override
ArrayList<Point> specialPoints()
{
ArrayList<Point> result = new ArrayList<Point>();
int numEdges = (int) Math.pow(2, lower.length);
for (int edgeNr = 0; edgeNr < numEdges; edgeNr++) {
int regionRest = edgeNr;
BigRational[] point = new BigRational[lower.length];
for (int dim = 0; dim < lower.length; dim++) {
boolean useLower = ( 0 == (regionRest % 2));
regionRest /= 2;
if (useLower) {
point[dim] = lower[dim];
} else {
point[dim] = upper[dim];
}
}
result.add(new Point(point));
}
return result;
}
@Override
Point randomPoint()
{
BigRational[] point = new BigRational[lower.length];
BigInteger maxInt = new BigInteger(Long.toString((long) Math.pow(2, 60)));
for (int dim = 0; dim < lower.length; dim++) {
BigInteger rndInt = new BigInteger(60, random);
BigRational rndRat = new BigRational(rndInt, maxInt);
rndRat = lower[dim].add(upper[dim].subtract(lower[dim]).multiply(rndRat));
point[dim] = rndRat;
}
return new Point(point);
}
@Override
BoxRegion conjunct(Region region) {
BoxRegion other = (BoxRegion) region;
BoxRegion result = new BoxRegion((BoxRegionFactory) factory);
for (int dim = 0; dim < lower.length; dim++) {
if (this.upper[dim].compareTo(other.lower[dim]) <= 0) {
return null;
}
if (this.lower[dim].compareTo(other.upper[dim]) >= 0) {
return null;
}
BigRational newLower = this.lower[dim].max(other.lower[dim]);
BigRational newUpper = this.upper[dim].min(other.upper[dim]);
if (newLower.equals(newUpper)) {
return null;
}
result.setDimension(dim, newLower, newUpper);
}
return result;
}
/**
* Checks whether this region is adjacent to another region in given dimension.
* In this case, both can be glued by @{code glue}.
*
* @param other region to check whether adjacent in given dimension
* @param adjDim dimension where to check adjacency
* @return true iff regions are adjacent in given dimension
*/
private boolean adjacent(Region region, int adjDim)
{
BoxRegion other = (BoxRegion) region;
for (int dim = 0; dim < this.getDimensions(); dim++) {
if (dim != adjDim) {
if (!this.getDimensionLower(dim).equals(other.getDimensionLower(dim))) {
return false;
}
if (!this.getDimensionUpper(dim).equals(other.getDimensionUpper(dim))) {
return false;
}
}
}
return this.getDimensionUpper(adjDim).equals(other.getDimensionLower(adjDim));
}
@Override
boolean adjacent(Region region)
{
BoxRegion other = (BoxRegion) region;
for (int dim = 0; dim < getDimensions(); dim++) {
if (adjacent(other, dim)) {
return true;
}
}
return false;
}
@Override
Region glue(Region region) {
BoxRegion other = (BoxRegion) region;
BoxRegion result = new BoxRegion((BoxRegionFactory) this.getFactory());
for (int dim = 0; dim < result.getDimensions(); dim++) {
result.setDimension(dim, this.getDimensionLower(dim), other.getDimensionUpper(dim));
}
return result;
}
}