diff --git a/prism/.classpath b/prism/.classpath
index 1bef3aa5a..07828a2a9 100644
--- a/prism/.classpath
+++ b/prism/.classpath
@@ -12,5 +12,6 @@
+
diff --git a/prism/Makefile b/prism/Makefile
index 4a8523861..a2836e8ba 100644
--- a/prism/Makefile
+++ b/prism/Makefile
@@ -298,11 +298,46 @@ MAKE_DIRS = dd jdd odd dv prism mtbdd sparse hybrid parser settings userinterfac
EXT_PACKAGES = lpsolve55 lp_solve_5.5_java
+EXACT_LP_PACKAGES = glpk glpk_java
+
+EXACT_LP_REQUIREMENTS_INSTALLED = 1
+
+# Find glpk_java requirements
+DETECT_SWIG = $(shell which swig 2> /dev/null)
+DETECT_MVN = $(shell which mvn 2> /dev/null)
+DETECT_LIBTOOL = $(shell which libtool 2> /dev/null)
+DETECT_ACLOCAL = $(shell which aclocal 2> /dev/null)
+
+ifeq ($(DETECT_SWIG),)
+ EXACT_LP_REQUIREMENTS_INSTALLED = 0
+endif
+ifeq ($(DETECT_MVN),)
+ EXACT_LP_REQUIREMENTS_INSTALLED = 0
+endif
+ifeq ($(DETECT_LIBTOOL),)
+ EXACT_LP_REQUIREMENTS_INSTALLED = 0
+endif
+ifeq ($(DETECT_ACLOCAL),)
+ EXACT_LP_REQUIREMENTS_INSTALLED = 0
+endif
+
.PHONY: clean javadoc tests
default: all
-all: cuddpackage extpackages prism
+all: cuddpackage extpackages exact_lp_requirements exact_lp prism
+
+exact_lp_requirements: checks
+ cp ext/glpk_java/jar/glpk-java.jar lib
+ @if [ "$(DETECT_SWIG)" != "" ]; then echo "Found SWIG in $(DETECT_SWIG)"; \
+ else echo "Warning: SWIG not found. Required for GLPK java bindings."; fi
+ @if [ "$(DETECT_MVN)" != "" ]; then echo "Found MAVEN in $(DETECT_MVN)"; \
+ else echo "Warning: MAVEN not found. Required for GLPK java bindings."; fi
+ @if [ "$(DETECT_LIBTOOL)" != "" ]; then echo "Found LIBTOOL in $(DETECT_LIBTOOL)"; \
+ else echo "Warning: LIBTOOL not found. Required for GLPK java bindings."; fi
+ @if [ "$(DETECT_ACLOCAL)" != "" ]; then echo "Found ACLOCAL in $(DETECT_ACLOCAL)"; \
+ else echo "Warning: ACLOCAL not found. Required for both GLPK and GLPK java bindings."; fi
+ @sed -i '/EXACT_LP_REQUIREMENTS_INSTALLED/c\ public final int EXACT_LP_REQUIREMENTS_INSTALLED = $(EXACT_LP_REQUIREMENTS_INSTALLED);' src/prism/PrismSettings.java;
cuddpackage: checks
@if [ "$(CUDD_DIR)" = "" ]; then echo "Error: Cannot find CUDD"; exit 1; fi
@@ -360,6 +395,25 @@ extpackages: checks
) || exit 1; \
done
+exact_lp: checks
+ @(if [ "$(EXACT_LP_REQUIREMENTS_INSTALLED)" = 1 ]; then \
+ for ext in $(EXACT_LP_PACKAGES); do \
+ echo Making $$ext; \
+ (cd ext/$$ext && \
+ $(MAKE) \
+ OSTYPE="$(OSTYPE)" \
+ ARCH="$(ARCH)" \
+ LIBPREFIX="$(LIBPREFIX)" \
+ LIBSUFFIX="$(LIBSUFFIX)" \
+ LIBMATH="$(LIBMATH)" \
+ BINDISTSUFFIX="$(BINDISTSUFFIX)" \
+ JAVA_DIR="$(JAVA_DIR)" \
+ JAVA_JNI_H_DIR="$(JAVA_JNI_H_DIR)" \
+ JAVA_JNI_MD_H_DIR="$(JAVA_JNI_MD_H_DIR)" \
+ ) || exit 1; \
+ done \
+ fi)
+
prism: checks make_dirs bin_scripts
make_dirs:
@@ -543,7 +597,7 @@ clean_cudd:
@(cd $(CUDD_DIR) && $(MAKE) distclean)
clean_ext:
- @(for ext in $(EXT_PACKAGES); do \
+ @(for ext in $(EXT_PACKAGES) $(EXACT_LP_PACKAGES); do \
echo Cleaning $$ext ...; \
(cd ext/$$ext && \
$(MAKE) -s LIBPREFIX="$(LIBPREFIX)" LIBSUFFIX="$(LIBSUFFIX)" clean) \
diff --git a/prism/ext/glpk/Makefile b/prism/ext/glpk/Makefile
new file mode 100644
index 000000000..9325fda35
--- /dev/null
+++ b/prism/ext/glpk/Makefile
@@ -0,0 +1,57 @@
+################################################
+# NB: This Makefile is designed to be called #
+# from the main PRISM Makefile. It won't #
+# work on its own because it needs #
+# various options to be passed in #
+################################################
+
+default: all
+
+all: checks ../../lib/glpk
+
+glpkvers = "4.60"
+
+IS_SRC_CREATED = $(shell find . -name "src" 2> /dev/null)
+ALREADY_DOWNLOADED = $(shell ls src | grep glpk-$(glpkvers).tar.gz 2> /dev/null)
+ALREADY_INSTALLED_LIB = $(shell find src/glpk/src/.libs -name $(LIBPREFIX)glpk$(LIBSUFFIX).40 2> /dev/null)
+ALREADY_INSTALLED = 1
+
+ifeq ($(ALREADY_INSTALLED_LIB),)
+ ALREADY_INSTALLED = 0
+endif
+
+# Try and prevent accidental makes (i.e. called manually, not from top-level Makefile)
+checks:
+ @if [ "$(LIBSUFFIX)" = "" ]; then \
+ (echo "Error: This Makefile is designed to be called from the main PRISM Makefile"; exit 1) \
+ fi;
+
+../../lib/glpk:
+ @(if [ "$(OSTYPE)" != "cygwin" ]; then \
+ (if [ "$(IS_SRC_CREATED)" = "" ]; then \
+ mkdir src; fi) && \
+ (cd src/ && \
+ (if [ "$(ALREADY_DOWNLOADED)" = "" ]; then \
+ wget http://ftp.gnu.org/gnu/glpk/glpk-$(glpkvers).tar.gz && \
+ tar -xzf glpk-$(glpkvers).tar.gz && \
+ mv glpk-$(glpkvers) glpk; \
+ fi) && \
+ cd glpk/ && \
+ (if [ $(ALREADY_INSTALLED) = 0 ]; then \
+ patch -p1 < ../../glpk_exact_solutions.patch && \
+ ./autogen.sh > /dev/null && \
+ ./configure --with-gmp > /dev/null && \
+ echo Building GLPK. This may take several minutes... && \
+ make > /dev/null; \
+ fi) \
+ ) && cp --remove-destination src/glpk/src/.libs/$(LIBPREFIX)glpk$(LIBSUFFIX).40 ../../lib/; \
+ fi)
+
+clean: checks
+ rm -f ../../lib/$(LIBPREFIX)glpk$(LIBSUFFIX)
+ rm -f ../../lib/$(LIBPREFIX)glpk$(LIBSUFFIX).40
+ rm -rf src/*
+
+celan: clean
+
+#################################################
diff --git a/prism/ext/glpk/glpk_exact_solutions.patch b/prism/ext/glpk/glpk_exact_solutions.patch
new file mode 100644
index 000000000..7da40ddd3
--- /dev/null
+++ b/prism/ext/glpk/glpk_exact_solutions.patch
@@ -0,0 +1,320 @@
+diff --git a/src/glpapi06.c b/src/glpapi06.c
+index f526e72..e67f5e1 100644
+--- a/src/glpapi06.c
++++ b/src/glpapi06.c
+@@ -209,7 +209,8 @@ up: col->stat = GLP_NU, col->prim = col->ub;
+ }
+ if (parm->msg_lev >= GLP_MSG_ALL && parm->out_dly == 0)
+ { if (P->pbs_stat == GLP_FEAS && P->dbs_stat == GLP_FEAS)
+- xprintf("OPTIMAL SOLUTION FOUND\n");
++ return;
++ //xprintf("OPTIMAL SOLUTION FOUND\n");
+ else if (P->pbs_stat == GLP_NOFEAS)
+ xprintf("PROBLEM HAS NO FEASIBLE SOLUTION\n");
+ else if (parm->meth == GLP_PRIMAL)
+@@ -501,6 +502,8 @@ void glp_init_smcp(glp_smcp *parm)
+ parm->out_frq = 500;
+ parm->out_dly = 0;
+ parm->presolve = GLP_OFF;
++ parm->precision = 64;
++ parm->num_states = 10;
+ return;
+ }
+
+@@ -783,6 +786,90 @@ double glp_get_col_dual(glp_prob *lp, int j)
+ /***********************************************************************
+ * NAME
+ *
++* glp_get_probs - retrieve fixed precision solutions array
++*
++* SYNOPSIS
++*
++* unsigned char **glp_get_probs(glp_prob *lp);
++*
++* RETURNS
++*
++* The routine glp_get_probs returns the array of fixed precision
++* solutions. */
++
++unsigned char **glp_get_probs(glp_prob *lp)
++{
++ unsigned char **probs;
++ probs = lp->probs;
++ return probs;
++}
++
++/***********************************************************************
++* NAME
++*
++* glp_get_exps - retrieve exponent positions for each solution
++*
++* SYNOPSIS
++*
++* int *glp_get_exps(glp_prob *lp);
++*
++* RETURNS
++*
++* The routine glp_get_exps returns the positions of the exponents
++* for each solution in probs array. */
++
++int *glp_get_exps(glp_prob *lp)
++{
++ int *exps;
++ exps = lp->exps;
++ return exps;
++}
++
++/***********************************************************************
++* NAME
++*
++* glp_get_nums - retrieve numerators of exact rational solutions
++*
++* SYNOPSIS
++*
++* unsigned char *glp_get_nums(glp_prob *lp);
++*
++* RETURNS
++*
++* The routine glp_get_nums returns the numerators for
++* each exact rational arithmetic solution. */
++
++unsigned char **glp_get_nums(glp_prob *lp)
++{
++ unsigned char **nums;
++ nums = lp->nums;
++ return nums;
++}
++
++/***********************************************************************
++* NAME
++*
++* glp_get_dens - retrieve denominators of exact rational solutions
++*
++* SYNOPSIS
++*
++* unsigned char **glp_get_dens(glp_prob *lp);
++*
++* RETURNS
++*
++* The routine glp_get_dens returns the denominators for
++* each exact rational arithmetic solution. */
++
++unsigned char **glp_get_dens(glp_prob *lp)
++{
++ unsigned char **dens;
++ dens = lp->dens;
++ return dens;
++}
++
++/***********************************************************************
++* NAME
++*
+ * glp_get_unbnd_ray - determine variable causing unboundedness
+ *
+ * SYNOPSIS
+diff --git a/src/glpapi07.c b/src/glpapi07.c
+index 89a06bf..3fe33a2 100644
+--- a/src/glpapi07.c
++++ b/src/glpapi07.c
+@@ -125,7 +125,8 @@ static void set_d_eps(mpq_t x, double val)
+ if (s < 0) mpq_neg(x, x);
+ /* check that the desired tolerance has been attained */
+ xassert(fabs(val - mpq_get_d(x)) <= eps * (1.0 + fabs(val)));
+-done: return;
++done:
++ return;
+ }
+
+ static void load_data(SSX *ssx, glp_prob *lp)
+@@ -159,8 +160,10 @@ static void load_data(SSX *ssx, glp_prob *lp)
+ default: xassert(type != type);
+ }
+ ssx->type[k] = type;
+- set_d_eps(ssx->lb[k], lb);
+- set_d_eps(ssx->ub[k], ub);
++ //set_d_eps(ssx->lb[k], lb);
++ //set_d_eps(ssx->ub[k], ub);
++ mpq_set_d(ssx->lb[k], lb);
++ mpq_set_d(ssx->ub[k], ub);
+ }
+ /* optimization direction */
+ switch (lp->dir)
+@@ -176,7 +179,8 @@ static void load_data(SSX *ssx, glp_prob *lp)
+ coef = 0.0;
+ else
+ coef = lp->col[k-m]->coef;
+- set_d_eps(ssx->coef[k], coef);
++ //set_d_eps(ssx->coef[k], coef);
++ mpq_set_d(ssx->coef[k], coef);
+ }
+ /* constraint coefficients */
+ ind = xcalloc(1+m, sizeof(int));
+@@ -188,7 +192,8 @@ static void load_data(SSX *ssx, glp_prob *lp)
+ for (k = 1; k <= len; k++)
+ { loc++;
+ ssx->A_ind[loc] = ind[k];
+- set_d_eps(ssx->A_val[loc], val[k]);
++ //set_d_eps(ssx->A_val[loc], val[k]);
++ mpq_set_d(ssx->A_val[loc], val[k]);
+ }
+ }
+ xassert(loc == nnz);
+@@ -262,8 +267,14 @@ int glp_exact(glp_prob *lp, const glp_smcp *parm)
+ int m = lp->m;
+ int n = lp->n;
+ int nnz = lp->nnz;
+- int i, j, k, type, pst, dst, ret, stat;
++ int i, j, k, type, pst, dst, ret, stat, actual_state, index;
+ double lb, ub, prim, dual, sum;
++ char *exact_prob;
++ /* fixed precision initial parameters */
++ mpf_t fix_prob;
++ mpf_init(fix_prob);
++ mpf_set_prec(fix_prob, parm->precision);
++ mp_exp_t exponent;
+ if (parm == NULL)
+ parm = &_parm, glp_init_smcp((glp_smcp *)parm);
+ /* check control parameters */
+@@ -273,11 +284,26 @@ int glp_exact(glp_prob *lp, const glp_smcp *parm)
+ if (parm->tm_lim < 0)
+ xerror("glp_exact: tm_lim = %d; invalid parameter\n",
+ parm->tm_lim);
++ if (parm->num_states <= 0)
++ xerror("glp_exact: num_states = %d; invalid parameter\n",
++ parm->num_states);
++ if (parm->precision <= 0)
++ xerror("glp_exact: precision = %d; invalid parameter\n",
++ parm->precision);
+ /* the problem must have at least one row and one column */
+ if (!(m > 0 && n > 0))
+ { xprintf("glp_exact: problem has no rows/columns\n");
+ return GLP_EFAIL;
+ }
++ actual_state = 0;
++ /* alloc memory for storing exact probabilities */
++ lp->nums = (unsigned char **) malloc(parm->num_states * sizeof(unsigned char*));
++ lp->dens = (unsigned char **) malloc(parm->num_states * sizeof(unsigned char*));
++ /* alloc memory for storing fixed probabilities */
++ lp->exps = (int *) malloc(parm->num_states * sizeof(int));
++ lp->probs = (unsigned char **) malloc(parm->num_states * sizeof(unsigned char*));
++ for (index = 0; index < parm->num_states; index++)
++ lp->probs[index] = (unsigned char *) malloc(mpf_get_prec(fix_prob) * sizeof(unsigned char));
+ #if 1
+ /* basic solution is currently undefined */
+ lp->pbs_stat = lp->dbs_stat = GLP_UNDEF;
+@@ -303,14 +329,14 @@ int glp_exact(glp_prob *lp, const glp_smcp *parm)
+ }
+ }
+ /* create the simplex solver workspace */
+- xprintf("glp_exact: %d rows, %d columns, %d non-zeros\n",
+- m, n, nnz);
++ //xprintf("glp_exact: %d rows, %d columns, %d non-zeros\n",
++ // m, n, nnz);
+ #ifdef HAVE_GMP
+- xprintf("GNU MP bignum library is being used\n");
++ //xprintf("GNU MP bignum library is being used\n");
+ #else
+- xprintf("GLPK bignum module is being used\n");
+- xprintf("(Consider installing GNU MP to attain a much better perf"
+- "ormance.)\n");
++ //xprintf("GLPK bignum module is being used\n");
++ //xprintf("(Consider installing GNU MP to attain a much better perf"
++ //"ormance.)\n");
+ #endif
+ ssx = ssx_create(m, n, nnz);
+ /* load LP problem data into the workspace */
+@@ -433,6 +459,14 @@ int glp_exact(glp_prob *lp, const glp_smcp *parm)
+ xassert(ssx != ssx);
+ }
+ dual = mpq_get_d(ssx->cbar[j]);
++ /* exact rational results */
++ lp->nums[actual_state] = mpz_get_str(NULL, 10, mpq_numref(ssx->cbar[j]));
++ lp->dens[actual_state] = mpz_get_str(NULL, 10, mpq_denref(ssx->cbar[j]));
++ /* fixed results */
++ mpf_set_q(fix_prob, ssx->cbar[j]);
++ lp->probs[actual_state] = mpf_get_str(NULL, &exponent, 10, 0, fix_prob);
++ lp->exps[actual_state] = exponent;
++ actual_state++;
+ }
+ if (k <= m)
+ { glp_set_row_stat(lp, k, stat);
+@@ -456,5 +490,3 @@ done: /* delete the simplex solver workspace */
+ /* return to the application program */
+ return ret;
+ }
+-
+-/* eof */
+diff --git a/src/glpk.h b/src/glpk.h
+index 137801c..654a232 100644
+--- a/src/glpk.h
++++ b/src/glpk.h
+@@ -137,6 +137,8 @@ typedef struct
+ int out_frq; /* spx.out_frq */
+ int out_dly; /* spx.out_dly (milliseconds) */
+ int presolve; /* enable/disable using LP presolver */
++ int precision; /* current precision */
++ int num_states; /* number of states */
+ double foo_bar[36]; /* (reserved) */
+ } glp_smcp;
+
+@@ -496,6 +498,18 @@ double glp_get_col_prim(glp_prob *P, int j);
+ double glp_get_col_dual(glp_prob *P, int j);
+ /* retrieve column dual value (basic solution) */
+
++unsigned char **glp_get_probs(glp_prob *lp);
++/* retrieve fixed precision solutions array */
++
++int *glp_get_exps(glp_prob *lp);
++/* retrieve exponent positions for each solution */
++
++unsigned char **glp_get_nums(glp_prob *lp);
++/* retrieve the numerators for each exact rational solution */
++
++unsigned char **glp_get_dens(glp_prob *lp);
++/* retrieve the denominators for each exact rational solution */
++
+ int glp_get_unbnd_ray(glp_prob *P);
+ /* determine variable causing unboundedness */
+
+diff --git a/src/glpssx02.c b/src/glpssx02.c
+index 4b3ea97..4a810a4 100644
+--- a/src/glpssx02.c
++++ b/src/glpssx02.c
+@@ -31,9 +31,9 @@ static void show_progress(SSX *ssx, int phase)
+ int i, def = 0;
+ for (i = 1; i <= ssx->m; i++)
+ if (ssx->type[ssx->Q_col[i]] == SSX_FX) def++;
+- xprintf("%s%6d: %s = %22.15g (%d)\n", phase == 1 ? " " : "*",
+- ssx->it_cnt, phase == 1 ? "infsum" : "objval",
+- mpq_get_d(ssx->bbar[0]), def);
++ //xprintf("%s%6d: %s = %22.15g (%d)\n", phase == 1 ? " " : "*",
++ //ssx->it_cnt, phase == 1 ? "infsum" : "objval",
++ //mpq_get_d(ssx->bbar[0]), def);
+ #if 0
+ ssx->tm_lag = utime();
+ #else
+@@ -446,7 +446,7 @@ skip: /* compute simplex multipliers */
+ ret = ssx_phase_II(ssx);
+ switch (ret)
+ { case 0:
+- xprintf("OPTIMAL SOLUTION FOUND\n");
++ //xprintf("OPTIMAL SOLUTION FOUND\n");
+ ret = 0;
+ break;
+ case 1:
+diff --git a/src/prob.h b/src/prob.h
+index 4c1d34e..fc675d9 100644
+--- a/src/prob.h
++++ b/src/prob.h
+@@ -143,6 +143,11 @@ struct glp_prob
+ GLP_NOFEAS - no integer solution exists */
+ double mip_obj;
+ /* objective function value */
++ /* for exact solver values storage */
++ unsigned char **probs; /* values of the probabilities */
++ int *exps; /* exponents of the probabilities */
++ unsigned char **nums; /* rational numerators */
++ unsigned char **dens; /* rational denominators */
+ };
+
+ struct GLPROW
diff --git a/prism/ext/glpk_java/Makefile b/prism/ext/glpk_java/Makefile
new file mode 100644
index 000000000..cbeac64a5
--- /dev/null
+++ b/prism/ext/glpk_java/Makefile
@@ -0,0 +1,68 @@
+################################################
+# NB: This Makefile is designed to be called #
+# from the main PRISM Makefile. It won't #
+# work on its own because it needs #
+# various options to be passed in #
+################################################
+
+default: all
+
+all: checks ../../lib/glpk_java
+
+LDPATH = $(shell cd ../glpk/src/glpk/src/.libs; pwd)
+CFLAGSPATH = $(shell cd ../glpk/src/glpk/src; pwd)
+SWIGFLAGSPATH = $(shell cd ../glpk/src/glpk/src; pwd)
+
+glpkjavavers = "1.7.0"
+
+IS_SRC_CREATED = $(shell find . -name "src" 2> /dev/null)
+ALREADY_DOWNLOADED = $(shell ls src | grep libglpk-java-$(glpkjavavers).tar.gz 2> /dev/null)
+ALREADY_INSTALLED_JAR = $(shell find src/glpk_java/swig -name glpk-java.jar 2> /dev/null)
+ALREADY_INSTALLED_LIB = $(shell find src/glpk_java/swig/.libs -name $(LIBPREFIX)glpk_java$(LIBSUFFIX) 2> /dev/null)
+ALREADY_INSTALLED = 1
+
+ifeq ($(ALREADY_INSTALLED_JAR),)
+ ALREADY_INSTALLED = 0
+endif
+ifeq ($(ALREADY_INSTALLED_LIB),)
+ ALREADY_INSTALLED = 0
+endif
+
+# Try and prevent accidental makes (i.e. called manually, not from top-level Makefile)
+checks:
+ @if [ "$(LIBSUFFIX)" = "" ]; then \
+ (echo "Error: This Makefile is designed to be called from the main PRISM Makefile"; exit 1) \
+ fi;
+
+../../lib/glpk_java:
+ @(if [ "$(OSTYPE)" != "cygwin" ]; then \
+ (if [ "$(IS_SRC_CREATED)" = "" ]; then \
+ mkdir src ;fi) && \
+ export JAVA_HOME=$(JAVA_DIR) && \
+ (cd src/ && \
+ (if [ "$(ALREADY_DOWNLOADED)" = "" ]; then \
+ wget http://download.sourceforge.net/project/glpk-java/glpk-java/glpk-java-$(glpkjavavers)/libglpk-java-$(glpkjavavers).tar.gz && \
+ tar -xzf libglpk-java-$(glpkjavavers).tar.gz && \
+ mv libglpk-java-$(glpkjavavers) glpk_java; \
+ fi) && \
+ cd glpk_java/ && \
+ (if [ $(ALREADY_INSTALLED) = 0 ]; then \
+ patch -p1 < ../../libglpk_java_exact.patch && \
+ export LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LDPATH) && \
+ export CFLAGS=-I$(CFLAGSPATH) && \
+ export SWIGFLAGS=-I$(SWIGFLAGSPATH) && \
+ ./autogen.sh > /dev/null && \
+ ./configure > /dev/null && \
+ make > /dev/null; \
+ fi) \
+ ) && cp --remove-destination src/glpk_java/swig/glpk-java.jar ../../lib/; \
+ cp --remove-destination src/glpk_java/swig/.libs/$(LIBPREFIX)glpk_java$(LIBSUFFIX) ../../lib/; \
+ fi)
+
+clean: checks
+ rm -f ../../lib/$(LIBPREFIX)glpk_java$(LIBSUFFIX)
+ rm -rf src/*
+
+celan: clean
+
+#################################################
diff --git a/prism/ext/glpk_java/jar/glpk-java.jar b/prism/ext/glpk_java/jar/glpk-java.jar
new file mode 100644
index 000000000..1dbb9e97b
Binary files /dev/null and b/prism/ext/glpk_java/jar/glpk-java.jar differ
diff --git a/prism/ext/glpk_java/libglpk_java_exact.patch b/prism/ext/glpk_java/libglpk_java_exact.patch
new file mode 100644
index 000000000..eace2c298
--- /dev/null
+++ b/prism/ext/glpk_java/libglpk_java_exact.patch
@@ -0,0 +1,54 @@
+diff --git a/swig/glpk.i b/swig/glpk.i
+index bf7d91f..e5ba65f 100644
+--- a/swig/glpk.i
++++ b/swig/glpk.i
+@@ -334,6 +334,8 @@ glp_java_vertex_data *glp_java_vertex_get_data(
+ %include "glpk_java_arrays.i"
+ %glp_array_functions(int, intArray)
+ %glp_array_functions(double, doubleArray)
++%glp_array_functions(unsigned char, uint8Array)
++%glp_array_functions(unsigned char *, uint8ArrayArray)
+
+ // Add handling for String arrays in glp_main
+ %include "various.i"
+diff --git a/swig/glpk_java_arrays.i b/swig/glpk_java_arrays.i
+index 214cec7..e4ecaaa 100644
+--- a/swig/glpk_java_arrays.i
++++ b/swig/glpk_java_arrays.i
+@@ -29,11 +29,19 @@ static TYPE NAME##_getitem(TYPE *ary, int index) {
+ return (TYPE) 0;
+ }
+ }
++
+ static void NAME##_setitem(TYPE *ary, int index, TYPE value) {
+ if (ary != NULL) {
+ ary[index] = value;
+ }
+ }
++
++static char *NAME##_toString(TYPE *ary) {
++ char *str;
++ str = (char *)ary;
++ return str;
++}
++
+ %}
+
+ %javamethodmodifiers new_##NAME(int nelements) "
+@@ -85,6 +93,16 @@ TYPE NAME##_getitem(TYPE *ary, int index);
+ public";
+ void NAME##_setitem(TYPE *ary, int index, TYPE value);
+
++%javamethodmodifiers NAME##_toString(TYPE* ary) "
++/**
++ * Sets the value of an element of an array of TYPE.
++ *
BEWARE: The validity of the index is not checked.
++ *
++ * @param ary array
++ */
++public";
++char *NAME##_toString(TYPE *ary);
++
+ %enddef
+ /* Old Swig versions require a LF after enddef */
+
diff --git a/prism/src/explicit/DTMCModelChecker.java b/prism/src/explicit/DTMCModelChecker.java
index e6c5b3059..6dc532708 100644
--- a/prism/src/explicit/DTMCModelChecker.java
+++ b/prism/src/explicit/DTMCModelChecker.java
@@ -29,9 +29,18 @@
import java.io.File;
import java.util.Arrays;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import org.gnu.glpk.GLPK;
+import org.gnu.glpk.GLPKConstants;
+import org.gnu.glpk.SWIGTYPE_p_double;
+import org.gnu.glpk.SWIGTYPE_p_int;
+import org.gnu.glpk.glp_prob;
+import org.gnu.glpk.glp_smcp;
+
import parser.VarList;
import parser.ast.Declaration;
import parser.ast.DeclarationIntUnbounded;
@@ -519,7 +528,8 @@ public ModelCheckerResult computeReachProbs(DTMC dtmc, BitSet remain, BitSet tar
LinEqMethod linEqMethod = this.linEqMethod;
// Switch to a supported method, if necessary
- if (!(linEqMethod == LinEqMethod.POWER || linEqMethod == LinEqMethod.GAUSS_SEIDEL)) {
+ if (!(linEqMethod == LinEqMethod.POWER || linEqMethod == LinEqMethod.GAUSS_SEIDEL
+ || linEqMethod == LinEqMethod.EXACT_LINEAR_PROGRAMMING || settings.EXACT_LP_REQUIREMENTS_INSTALLED == 0)) {
linEqMethod = LinEqMethod.GAUSS_SEIDEL;
mainLog.printWarning("Switching to linear equation solution method \"" + linEqMethod.fullName() + "\"");
}
@@ -598,6 +608,9 @@ public ModelCheckerResult computeReachProbs(DTMC dtmc, BitSet remain, BitSet tar
case GAUSS_SEIDEL:
res = computeReachProbsGaussSeidel(dtmc, no, yes, init, known);
break;
+ case EXACT_LINEAR_PROGRAMMING:
+ res = computeReachProbsExactLinearProg(dtmc, no, yes, init, known);
+ break;
default:
throw new PrismException("Unknown linear equation solution method " + linEqMethod.fullName());
}
@@ -1040,6 +1053,265 @@ protected ModelCheckerResult computeReachProbsGaussSeidel(DTMC dtmc, BitSet no,
return res;
}
+ /**
+ * Compute arbitrary precision reachability probabilities using linear programming.
+ * @param dtmc The DTMC
+ * @param no Probability 0 states
+ * @param yes Probability 1 states
+ * @param init Optionally, an initial solution vector (will be overwritten)
+ * @param known Optionally, a set of states for which the exact answer is known
+ * Note: if 'known' is specified (i.e. is non-null, 'init' must also be given and is used for the exact values.
+ */
+ protected ModelCheckerResult computeReachProbsExactLinearProg(DTMC dtmc, BitSet no, BitSet yes, double init[], BitSet known) throws PrismException
+ {
+ ModelCheckerResult res;
+ BitSet unknown;
+ int i, nStates, maybeSize, countMaybeTrans, ret;
+ double sol, soln[], initVal, matAEntry, bEntry;
+ long timer, matATimer, exactSolverTimer;
+ SWIGTYPE_p_int ind;
+ SWIGTYPE_p_double val;
+ glp_smcp parm;
+ glp_prob lp;
+
+ // Start value iteration
+ timer = System.currentTimeMillis();
+ mainLog.println("Starting exact linear programming...");
+
+ // Store num states
+ nStates = dtmc.getNumStates();
+
+ // Determine the set of maybe states (S?)
+ unknown = new BitSet();
+ unknown.set(0, nStates);
+ unknown.andNot(yes);
+ unknown.andNot(no);
+ if (known != null)
+ unknown.andNot(known);
+
+ // Create solution vector
+ soln = (init == null) ? new double[nStates] : init;
+
+ // Initialise solution vector. Use (where available) the following in order of preference:
+ // (1) exact answer, if already known; (2) 1.0/0.0 if in yes/no; (3) passed in initial value; (4) initVal
+ // where initVal is 0.0 or 1.0, depending on whether we converge from below/above.
+ initVal = (valIterDir == ValIterDir.BELOW) ? 0.0 : 1.0;
+ if (init != null) {
+ if (known != null) {
+ for (i = 0; i < nStates; i++)
+ soln[i] = known.get(i) ? init[i] : yes.get(i) ? 1.0 : no.get(i) ? 0.0 : init[i];
+ } else {
+ for (i = 0; i < nStates; i++)
+ soln[i] = yes.get(i) ? 1.0 : no.get(i) ? 0.0 : init[i];
+ }
+ } else {
+ for (i = 0; i < nStates; i++)
+ soln[i] = yes.get(i) ? 1.0 : no.get(i) ? 0.0 : initVal;
+ }
+
+ // Store the cardinality of the maybe state
+ maybeSize = unknown.cardinality();
+
+ // If we have states to compute values for
+ if (maybeSize > 0) {
+ // A mapping between maybe states and its positions in the maybe bitset.
+ // This is useful for calculating the `A` matrix later on.
+ // Also, calculate the number of the transitions of all maybe states `countMaybeTrans`
+ countMaybeTrans = 0;
+ HashMap maybeToState = new HashMap();
+ HashMap stateToMaybe = new HashMap();
+ int maybeStateCount = 1;
+ for(int state = unknown.nextSetBit(0); state >= 0; state = unknown.nextSetBit(state + 1)) {
+ maybeToState.put(maybeStateCount, state);
+ stateToMaybe.put(state, maybeStateCount);
+ countMaybeTrans++;
+ maybeStateCount++;
+ }
+
+ // Memory allocation for index and value arrays.
+ // For each transition, GLPK stores the values for each state in the maybe set
+ // and uses these arrays as temporal storage
+ ind = GLPK.new_intArray(maybeSize + 1);
+ val = GLPK.new_doubleArray(maybeSize + 1);
+
+ // Construct max (min) linear programming problem.
+ // First, we put the problem in the canonical form
+ // and then apply the duality definition
+ lp = GLPK.glp_create_prob();
+ mainLog.println("Linear programming problem created");
+
+ // Set the lp constants for the dual simplex method.
+ // columns -> one for each transition
+ // rows -> maybe states
+ GLPK.glp_add_cols(lp, countMaybeTrans);
+ GLPK.glp_add_rows(lp, maybeSize);
+ GLPK.intArray_setitem(ind, 0, 0);
+ GLPK.doubleArray_setitem(val, 0, 0);
+
+ // Constraints for each equation expressed as inequalities.
+ // For the min (max) problem, each equation has -1 as a upper (lower) bound and we compute the max (min).
+ // This was obtained rewriting the problems (6) (7) in their canonical form, and then construct the dual.
+ // The `-1` bounds correspond to the values of the cost vector (remember that we put the problems
+ // in canonical form first).
+ // Also, we set each row (representing a maybe state) as part of the basis defined in (10)
+ for (int row = 1; row <= maybeSize; row++) {
+ // The name of the state
+ GLPK.glp_set_row_name(lp, row, "s" + unknown.nextSetBit(row));
+ GLPK.glp_set_row_bnds(lp, row, GLPKConstants.GLP_UP, 0, -1);
+ GLPK.glp_set_obj_dir(lp, GLPKConstants.GLP_MAX);
+ GLPK.glp_set_row_stat(lp, row, GLPKConstants.GLP_BS);
+ GLPK.intArray_setitem(ind, row, 0);
+ GLPK.doubleArray_setitem(val, row, 0);
+ }
+
+ // The current column of the matrix (transition)
+ int currentTransition = 1;
+ matATimer = System.currentTimeMillis();
+ // Fill out the (A | I) matrix (lp problem equations) of the reachability problem.
+ // In this step, we calculate the values for each entry of the A matrix,
+ // going through all maybe states and their transitions for each choice
+ for (int state = unknown.nextSetBit(0); state >= 0; state = unknown.nextSetBit(state + 1)) {
+ // Index of the i-th equation in the lp's matrix
+ int eqIndex = 1;
+ Iterator> iter = dtmc.getTransitionsIterator(state);
+ bEntry = 0.;
+ boolean pointsToItself = false;
+ // Iterate over the transitions between st and choice
+ while (iter.hasNext()) {
+ matAEntry = 0.;
+ Map.Entry entry = iter.next();
+ int actualKey = entry.getKey();
+ // Skip if the probability is 0
+ if (entry.getValue() == 0)
+ continue;
+ // Calculate the entries (summation) for the b vector (constraint vector)
+ if (yes.get(actualKey))
+ bEntry += entry.getValue();
+ // If it is a maybe state, we calculate the matrix entry as defined on
+ // page 8 of the paper
+ if (unknown.get(actualKey)) {
+ if (actualKey == state) {
+ pointsToItself = true;
+ matAEntry = entry.getValue() - 1;
+ } else {
+ matAEntry = entry.getValue();
+ }
+ // Write the entry of each equation in the array
+ GLPK.intArray_setitem(ind, eqIndex, stateToMaybe.get(actualKey));
+ GLPK.doubleArray_setitem(val, eqIndex, matAEntry);
+ eqIndex++;
+ }
+ }
+ // If the state does not point to itself, then fill out the missing entry
+ if (!pointsToItself) {
+ GLPK.intArray_setitem(ind, eqIndex, stateToMaybe.get(state));
+ GLPK.doubleArray_setitem(val, eqIndex, -1);
+ eqIndex++;
+ }
+
+ // Set the values of the arrays in the full linear programming equation matrix.
+ // Negate the summation calculated for the constraint vector entries
+ GLPK.glp_set_col_bnds(lp, currentTransition, GLPKConstants.GLP_LO, 0, 0);
+ GLPK.glp_set_mat_col(lp, currentTransition, eqIndex - 1, ind, val);
+ GLPK.glp_set_obj_coef(lp, currentTransition, -bEntry);
+
+ currentTransition++;
+ }
+ mainLog.println("Linear programming equations construction time: " +
+ ((double)System.currentTimeMillis() - matATimer)/1000 + " seconds");
+
+ // Solving the linear problem in the linear programming library GLPK.
+ // probs[]: an array for storing the exact probabilities as string in the form `p/q`
+ // `num` and `den` store the temporal `p` and `q` respectively (this could be avoided)
+ String probs[] = new String[nStates];
+
+ // Set up exact parameters
+ parm = new glp_smcp();
+ GLPK.glp_init_smcp(parm);
+ parm.setPrecision(this.getPrecision());
+ parm.setNum_states(nStates);
+
+ // Solve lp problem
+ exactSolverTimer = System.currentTimeMillis();
+ ret = GLPK.glp_exact(lp, parm);
+
+ // Check return value: if `ret` is not zero there has been an error
+ if (ret != 0) {
+ String msg;
+ if (ret == GLPKConstants.GLP_EBADB) {
+ msg = "The initial basis specified in the problem object is invalid: wrong number of basic variables";
+ } else if (ret == GLPKConstants.GLP_ESING) {
+ msg = "The basis matrix corresponding to the initial basis is singular";
+ } else if (ret == GLPKConstants.GLP_EBOUND) {
+ msg = "Some double-bounded variables have incorrect bound";
+ } else {
+ msg = "The linear programming problem could not be solved";
+ }
+ mainLog.println(msg);
+ throw new PrismException(msg);
+ }
+
+ // Calculate and store exact probabilities.
+ // For each maybe state, we extract the fixed precision results from GLPK and store them in `probs[]`.
+ // `sol` stores the exact values as doubles. That implies that the precision is at most 64-bit
+ // However, the exact value is expressed as a string in `probs[]`.
+ sol = GLPK.glp_get_obj_val(lp);
+ String currentResult;
+ int exp;
+ mainLog.println("\n" + this.getPrecision() + "-bit results (non-zero only) for each state of the DTMC:");
+ for (int dtmcState = 0; dtmcState < nStates; dtmcState++) {
+ if (unknown.get(dtmcState)) {
+ sol = GLPK.glp_get_row_dual(lp, stateToMaybe.get(dtmcState));
+ soln[dtmcState] = sol;
+ currentResult = GLPK.uint8Array_toString(GLPK.uint8ArrayArray_getitem(GLPK.glp_get_probs(lp), stateToMaybe.get(dtmcState) - 1));
+ exp = GLPK.intArray_getitem(GLPK.glp_get_exps(lp), stateToMaybe.get(dtmcState) - 1);
+ // We need to format the output since we get the exponent as a position in the string
+ String zeroes = "";
+ for (int nZero = 0; nZero < Math.abs(exp); nZero++)
+ zeroes += "0";
+ currentResult = exp > 0 ? new StringBuilder(currentResult).insert(exp, ".").toString() : "0." + zeroes + currentResult;
+ probs[dtmcState] = currentResult;
+ }
+ // If it is a `yes` state, its probability is 1
+ else if (yes.get(dtmcState)) {
+ soln[dtmcState] = 1.;
+ probs[dtmcState] = "1";
+ }
+ // If it is a `no` state, its probability is 0
+ else if (no.get(dtmcState)) {
+ soln[dtmcState] = 0.;
+ probs[dtmcState] = "0";
+ }
+ // Avoid zero entries
+ if (probs[dtmcState] != "0")
+ mainLog.println("[" + dtmcState + "]: " + probs[dtmcState]);
+ }
+ mainLog.println("\nExact probabilities calculation time: " +
+ ((double)System.currentTimeMillis() - exactSolverTimer)/1000 + " seconds");
+
+ // Free memory
+ GLPK.delete_intArray(ind);
+ GLPK.delete_doubleArray(val);
+ GLPK.delete_intArray(GLPK.glp_get_exps(lp));
+ GLPK.delete_uint8ArrayArray(GLPK.glp_get_probs(lp));
+ GLPK.delete_uint8ArrayArray(GLPK.glp_get_nums(lp));
+ GLPK.delete_uint8ArrayArray(GLPK.glp_get_dens(lp));
+ GLPK.glp_delete_prob(lp);
+
+ }
+
+ // Finished linear programming
+ timer = System.currentTimeMillis() - timer;
+ mainLog.print("Exact linear programming");
+ mainLog.println(" took " + timer / 1000.0 + " seconds.");
+
+ // Return results
+ res = new ModelCheckerResult();
+ res.soln = soln;
+ res.timeTaken = timer / 1000.0;
+ return res;
+ }
+
/**
* Compute bounded reachability probabilities.
* i.e. compute the probability of reaching a state in {@code target} within k steps.
diff --git a/prism/src/explicit/MDPModelChecker.java b/prism/src/explicit/MDPModelChecker.java
index 30879e735..48b1bd354 100644
--- a/prism/src/explicit/MDPModelChecker.java
+++ b/prism/src/explicit/MDPModelChecker.java
@@ -28,9 +28,12 @@
import java.util.Arrays;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import parser.VarList;
import parser.ast.Declaration;
@@ -42,6 +45,7 @@
import prism.PrismException;
import prism.PrismFileLog;
import prism.PrismLog;
+import prism.PrismSettings;
import prism.PrismUtils;
import strat.MDStrategyArray;
import acceptance.AcceptanceReach;
@@ -52,6 +56,13 @@
import explicit.rewards.MDPRewards;
import explicit.rewards.Rewards;
+import org.gnu.glpk.GLPK;
+import org.gnu.glpk.GLPKConstants;
+import org.gnu.glpk.SWIGTYPE_p_double;
+import org.gnu.glpk.SWIGTYPE_p_int;
+import org.gnu.glpk.glp_prob;
+import org.gnu.glpk.glp_smcp;
+
/**
* Explicit-state model checker for Markov decision processes (MDPs).
*/
@@ -335,8 +346,9 @@ public ModelCheckerResult computeReachProbs(MDP mdp, BitSet remain, BitSet targe
MDPSolnMethod mdpSolnMethod = this.mdpSolnMethod;
// Switch to a supported method, if necessary
- if (mdpSolnMethod == MDPSolnMethod.LINEAR_PROGRAMMING) {
+ if (settings.EXACT_LP_REQUIREMENTS_INSTALLED == 0) {
mdpSolnMethod = MDPSolnMethod.GAUSS_SEIDEL;
+ mainLog.printWarning("Cannot compute exact linear programming. GLPK libraries are not properly installed");
mainLog.printWarning("Switching to MDP solution method \"" + mdpSolnMethod.fullName() + "\"");
}
@@ -447,6 +459,9 @@ public ModelCheckerResult computeReachProbs(MDP mdp, BitSet remain, BitSet targe
case MODIFIED_POLICY_ITERATION:
res = computeReachProbsModPolIter(mdp, no, yes, min, strat);
break;
+ case EXACT_LINEAR_PROGRAMMING:
+ res = computeReachProbsExactLinearProg(mdp, no, yes, target, min, strat);
+ break;
default:
throw new PrismException("Unknown MDP solution method " + mdpSolnMethod.fullName());
}
@@ -1049,6 +1064,277 @@ protected ModelCheckerResult computeReachProbsModPolIter(MDP mdp, BitSet no, Bit
res.timeTaken = timer / 1000.0;
return res;
}
+
+ /**
+ * Compute arbitrary precision reachability probabilities using linear programming.
+ * @param mdp: The MDP
+ * @param no: Probability 0 states
+ * @param yes: Probability 1 states
+ * @param target: Target states
+ * @param min: Min or max probabilities (true=min, false=max)
+ * @param strat Storage for (memoryless) strategy choice indices (ignored if null)
+ */
+ protected ModelCheckerResult computeReachProbsExactLinearProg(MDP mdp, BitSet no, BitSet yes, BitSet target, boolean min, int strat[]) throws PrismException
+ {
+ ModelCheckerResult res;
+ int nStates, maybeSize, countMaybeTrans, ret;
+ double sol, soln[], matAEntry, bEntry;
+ long timer, basisTimer, matATimer, exactSolverTimer;
+ BitSet maybe;
+ SWIGTYPE_p_int ind;
+ SWIGTYPE_p_double val;
+ glp_smcp parm;
+ glp_prob lp;
+
+ // Start linear programming
+ timer = System.currentTimeMillis();
+ mainLog.println("Starting exact linear programming (" + (min ? "min" : "max") + ")...");
+
+ // Store num states
+ nStates = mdp.getNumStates();
+
+ // Solution vector where we store the exact
+ // probabilities as doubles
+ soln = new double[nStates];
+ BitSet states = new BitSet();
+ states.set(0, nStates);
+
+ // Determine the set of maybe states (S?)
+ maybe = new BitSet();
+ maybe.set(0, nStates);
+ maybe.andNot(yes);
+ maybe.andNot(no);
+
+ // Store the cardinality of the maybe state
+ maybeSize = maybe.cardinality();
+
+ // The set of basic columns.
+ // This set will be filled as defined in (10)
+ Set basicCols = new HashSet();
+
+ // A mapping between maybe states and its positions in the maybe bitset.
+ // This is useful for calculating the `A` matrix later on.
+ // Also, it is calculated the number of the transitions of all maybe states `countMaybeTrans`
+ countMaybeTrans = 0;
+ HashMap maybeToState = new HashMap();
+ HashMap stateToMaybe = new HashMap();
+ int maybeStateCount = 1;
+ for(int state = maybe.nextSetBit(0); state >= 0; state = maybe.nextSetBit(state + 1)) {
+ maybeToState.put(maybeStateCount, state);
+ stateToMaybe.put(state, maybeStateCount);
+ countMaybeTrans += mdp.getNumChoices(state);
+ maybeStateCount++;
+ }
+
+ // Memory allocation for index and value arrays.
+ // For each transition, GLPK stores the values for each state in the maybe set
+ // and uses these arrays as temporal storage
+ ind = GLPK.new_intArray(maybeSize + 1);
+ val = GLPK.new_doubleArray(maybeSize + 1);
+
+ // Construct max (min) linear programming problem.
+ // First, we put the problem in the canonical form
+ // and then apply the duality definition
+ lp = GLPK.glp_create_prob();
+ mainLog.println("Linear programming problem created");
+
+ // Set the lp constants for the dual simplex method.
+ // columns -> one for each transition
+ // rows -> maybe states
+ GLPK.glp_add_cols(lp, countMaybeTrans);
+ GLPK.glp_add_rows(lp, maybeSize);
+ GLPK.intArray_setitem(ind, 0, 0);
+ GLPK.doubleArray_setitem(val, 0, 0);
+
+ // Constraints for each equation expressed as inequalities.
+ // For the min (max) problem, each equation has -1 as a upper (lower) bound and we compute the max (min).
+ // This was obtained rewriting the problems (6) (7) in their canonical form, and then construct the dual.
+ // The `-1` bounds correspond to the values of the cost vector (remember that we put the problems
+ // in canonical form first).
+ // Also, we set each row (representing a maybe state) as part of the basis defined in (10)
+ for (int row = 1; row <= maybeSize; row++) {
+ // The name of the state
+ GLPK.glp_set_row_name(lp, row, "s" + maybe.nextSetBit(row));
+ if (min) {
+ GLPK.glp_set_row_bnds(lp, row, GLPKConstants.GLP_UP, 0, -1);
+ GLPK.glp_set_obj_dir(lp, GLPKConstants.GLP_MAX);
+ } else {
+ GLPK.glp_set_row_bnds(lp, row, GLPKConstants.GLP_LO, -1, 0);
+ GLPK.glp_set_obj_dir(lp, GLPKConstants.GLP_MIN);
+ }
+ GLPK.glp_set_row_stat(lp, row, GLPKConstants.GLP_BS);
+ GLPK.intArray_setitem(ind, row, 0);
+ GLPK.doubleArray_setitem(val, row, 0);
+ }
+
+ // The current column of the matrix (transition)
+ int currentTransition = 1;
+ matATimer = System.currentTimeMillis();
+ // Fill out the (A | I) matrix (lp problem equations) of the reachability problem.
+ // In this step, we calculate the values for each entry of the A matrix,
+ // going through all maybe states and their transitions for each choice
+ for (int state = maybe.nextSetBit(0); state >= 0; state = maybe.nextSetBit(state + 1)) {
+ for (int choice = 0; choice < mdp.getNumChoices(state); choice++) {
+ // Index of the i-th equation in the lp's matrix
+ int eqIndex = 1;
+ Iterator> iter = mdp.getTransitionsIterator(state, choice);
+ bEntry = 0.;
+ boolean pointsToItself = false;
+ // Iterate over the transitions between st and choice
+ while (iter.hasNext()) {
+ matAEntry = 0.;
+ Map.Entry entry = iter.next();
+ int actualKey = entry.getKey();
+ // Skip if the probability is 0
+ if (entry.getValue() == 0)
+ continue;
+ // Calculate the entries (summation) for the b vector (constraint vector)
+ if (yes.get(actualKey))
+ bEntry += entry.getValue();
+ // If it is a maybe state, we calculate the matrix entry as defined on
+ // page 8 of the paper
+ if (maybe.get(actualKey)) {
+ if (actualKey == state) {
+ pointsToItself = true;
+ matAEntry = entry.getValue() - 1;
+ } else {
+ matAEntry = entry.getValue();
+ }
+ // Write the entry of each equation in the array
+ GLPK.intArray_setitem(ind, eqIndex, stateToMaybe.get(actualKey));
+ GLPK.doubleArray_setitem(val, eqIndex, matAEntry);
+ eqIndex++;
+ }
+ }
+ // If the state does not point to itself, then fill out the missing entry
+ if (!pointsToItself) {
+ GLPK.intArray_setitem(ind, eqIndex, stateToMaybe.get(state));
+ GLPK.doubleArray_setitem(val, eqIndex, -1);
+ eqIndex++;
+ }
+ // This is described in (10) on the paper
+ // If strat is null, it would mean that the strategy is not apt, so we should ignore
+ // the basis construction for this case
+ if (strat != null && choice == strat[state])
+ basicCols.add(currentTransition);
+
+ // Set the values of the arrays in the full linear programming equation matrix.
+ // Negate the summation calculated for the constraint vector entries
+ GLPK.glp_set_col_bnds(lp, currentTransition, GLPKConstants.GLP_LO, 0, 0);
+ GLPK.glp_set_mat_col(lp, currentTransition, eqIndex - 1, ind, val);
+ GLPK.glp_set_obj_coef(lp, currentTransition, -bEntry);
+
+ currentTransition++;
+ }
+ }
+ mainLog.println("Linear programming equations construction time: " +
+ ((double)System.currentTimeMillis() - matATimer)/1000 + " seconds");
+
+ // Determine the valid initial basis defined in (10).
+ // Set every basic column as basic (GLP_BS constant), otherwise
+ // it is non-basic having an active lower bound (GLP_NL)
+ basisTimer = System.currentTimeMillis();
+ for (int col = 1; col < currentTransition; col++) {
+ if (basicCols.contains(col)) {
+ GLPK.glp_set_col_stat(lp, col, GLPKConstants.GLP_BS);
+ } else {
+ GLPK.glp_set_col_stat(lp, col, GLPKConstants.GLP_NL);
+ }
+ }
+ mainLog.println("Basis construction time: " +
+ ((double)System.currentTimeMillis() - basisTimer)/1000 + " seconds");
+
+ // Solving the linear problem in the linear programming library GLPK.
+ // probs[]: an array for storing the exact probabilities as string in the form `p/q`
+ // `num` and `den` store the temporal `p` and `q` respectively (this could be avoided)
+ String probs[] = new String[nStates];
+
+ // Set up fixed parameters
+ parm = new glp_smcp();
+ GLPK.glp_init_smcp(parm);
+ parm.setPrecision(this.getPrecision());
+ parm.setNum_states(nStates);
+
+ // Solve lp problem
+ exactSolverTimer = System.currentTimeMillis();
+ ret = GLPK.glp_exact(lp, parm);
+
+ // Check return value: if `ret` is not zero there has been an error
+ if (ret != 0) {
+ String msg;
+ if (ret == GLPKConstants.GLP_EBADB) {
+ msg = "The initial basis specified in the problem object is invalid: wrong number of basic variables";
+ } else if (ret == GLPKConstants.GLP_ESING) {
+ msg = "The basis matrix corresponding to the initial basis is singular";
+ } else if (ret == GLPKConstants.GLP_EBOUND) {
+ msg = "Some double-bounded variables have incorrect bound";
+ } else {
+ msg = "The linear programming problem could not be solved";
+ }
+ mainLog.println(msg);
+ throw new PrismException(msg);
+ }
+
+ // Calculate and store exact probabilities.
+ // For each maybe state, we extract the fixed precision results from GLPK and store them in `probs[]`.
+ // `sol` stores the exact values as doubles. That implies that the precision is at most 64-bit
+ // However, the exact value is expressed as a string in `probs[]`.
+ sol = GLPK.glp_get_obj_val(lp);
+ String currentResult;
+ int exp;
+ mainLog.println("\n" + this.getPrecision() + "-bit results (non-zero only) for each state of the MDP:");
+ for (int mdpState = 0; mdpState < nStates; mdpState++) {
+ if (maybe.get(mdpState)) {
+ sol = GLPK.glp_get_row_dual(lp, stateToMaybe.get(mdpState));
+ soln[mdpState] = sol;
+ currentResult = GLPK.uint8Array_toString(GLPK.uint8ArrayArray_getitem(GLPK.glp_get_probs(lp), stateToMaybe.get(mdpState) - 1));
+ exp = GLPK.intArray_getitem(GLPK.glp_get_exps(lp), stateToMaybe.get(mdpState) - 1);
+ // We need to format the output since we get the exponent as a position in the string
+ String zeroes = "";
+ for (int nZero = 0; nZero < Math.abs(exp); nZero++)
+ zeroes += "0";
+ currentResult = exp > 0 ? new StringBuilder(currentResult).insert(exp, ".").toString() : "0." + zeroes + currentResult;
+ probs[mdpState] = currentResult;
+ }
+ // If it is a `yes` state, its probability is 1
+ else if (yes.get(mdpState)) {
+ soln[mdpState] = 1.;
+ probs[mdpState] = "1";
+ }
+ // If it is a `no` state, its probability is 0
+ else if (no.get(mdpState)) {
+ soln[mdpState] = 0.;
+ probs[mdpState] = "0";
+ }
+ // Avoid zero entries
+ if (probs[mdpState] != "0")
+ mainLog.println("[" + mdpState + "]: " + probs[mdpState]);
+ }
+ mainLog.println("\nExact probabilities calculation time: " +
+ ((double)System.currentTimeMillis() - exactSolverTimer)/1000 + " seconds");
+
+ // Free memory
+ GLPK.delete_intArray(ind);
+ GLPK.delete_doubleArray(val);
+ GLPK.delete_intArray(GLPK.glp_get_exps(lp));
+ GLPK.delete_uint8ArrayArray(GLPK.glp_get_probs(lp));
+ GLPK.delete_uint8ArrayArray(GLPK.glp_get_nums(lp));
+ GLPK.delete_uint8ArrayArray(GLPK.glp_get_dens(lp));
+ GLPK.glp_delete_prob(lp);
+
+ // Finished linear programming
+ timer = System.currentTimeMillis() - timer;
+ mainLog.print("Exact linear programming");
+ mainLog.println(" took " + timer / 1000.0 + " seconds.");
+
+ // Return results
+ // (Note we don't add the strategy - the one passed in is already there
+ // and might have some existing choices stored for other states).
+ res = new ModelCheckerResult();
+ res.timeTaken = timer / 1000.0;
+ res.soln = soln;
+ return res;
+ }
/**
* Construct strategy information for min/max reachability probabilities.
diff --git a/prism/src/explicit/ProbModelChecker.java b/prism/src/explicit/ProbModelChecker.java
index 40f9d1a57..13a0d9ef3 100644
--- a/prism/src/explicit/ProbModelChecker.java
+++ b/prism/src/explicit/ProbModelChecker.java
@@ -73,6 +73,8 @@ public class ProbModelChecker extends NonProbModelChecker
protected double termCritParam = 1e-8;
// Max iterations for numerical solution
protected int maxIters = 100000;
+ // Floating point precision
+ protected int precision = 64;
// Use precomputation algorithms in model checking?
protected boolean precomp = true;
protected boolean prob0 = true;
@@ -93,7 +95,7 @@ public class ProbModelChecker extends NonProbModelChecker
// Method used for numerical solution
public enum LinEqMethod {
- POWER, JACOBI, GAUSS_SEIDEL, BACKWARDS_GAUSS_SEIDEL, JOR, SOR, BACKWARDS_SOR;
+ POWER, JACOBI, GAUSS_SEIDEL, BACKWARDS_GAUSS_SEIDEL, EXACT_LINEAR_PROGRAMMING, JOR, SOR, BACKWARDS_SOR;
public String fullName()
{
switch (this) {
@@ -105,6 +107,8 @@ public String fullName()
return "Gauss-Seidel";
case BACKWARDS_GAUSS_SEIDEL:
return "Backwards Gauss-Seidel";
+ case EXACT_LINEAR_PROGRAMMING:
+ return "Exact linear programming";
case JOR:
return "JOR";
case SOR:
@@ -119,7 +123,7 @@ public String fullName()
// Method used for solving MDPs
public enum MDPSolnMethod {
- VALUE_ITERATION, GAUSS_SEIDEL, POLICY_ITERATION, MODIFIED_POLICY_ITERATION, LINEAR_PROGRAMMING;
+ VALUE_ITERATION, GAUSS_SEIDEL, POLICY_ITERATION, MODIFIED_POLICY_ITERATION, LINEAR_PROGRAMMING, EXACT_LINEAR_PROGRAMMING;
public String fullName()
{
switch (this) {
@@ -133,6 +137,8 @@ public String fullName()
return "Modified policy iteration";
case LINEAR_PROGRAMMING:
return "Linear programming";
+ case EXACT_LINEAR_PROGRAMMING:
+ return "Exact linear programming";
default:
return this.toString();
}
@@ -151,7 +157,7 @@ public enum ValIterDir {
// Method used for numerical solution
public enum SolnMethod {
- VALUE_ITERATION, GAUSS_SEIDEL, POLICY_ITERATION, MODIFIED_POLICY_ITERATION, LINEAR_PROGRAMMING
+ VALUE_ITERATION, GAUSS_SEIDEL, POLICY_ITERATION, MODIFIED_POLICY_ITERATION, LINEAR_PROGRAMMING, EXACT_LINEAR_PROGRAMMING
};
/**
@@ -180,6 +186,8 @@ public ProbModelChecker(PrismComponent parent) throws PrismException
setLinEqMethod(LinEqMethod.SOR);
} else if (s.equals("Backwards SOR")) {
setLinEqMethod(LinEqMethod.BACKWARDS_SOR);
+ } else if (s.equals("Exact linear programming")) {
+ setLinEqMethod(LinEqMethod.EXACT_LINEAR_PROGRAMMING);
} else {
throw new PrismNotSupportedException("Explicit engine does not support linear equation solution method \"" + s + "\"");
}
@@ -195,6 +203,8 @@ public ProbModelChecker(PrismComponent parent) throws PrismException
setMDPSolnMethod(MDPSolnMethod.MODIFIED_POLICY_ITERATION);
} else if (s.equals("Linear programming")) {
setMDPSolnMethod(MDPSolnMethod.LINEAR_PROGRAMMING);
+ } else if (s.equals("Exact linear programming")) {
+ setMDPSolnMethod(MDPSolnMethod.EXACT_LINEAR_PROGRAMMING);
} else {
throw new PrismNotSupportedException("Explicit engine does not support MDP solution method \"" + s + "\"");
}
@@ -211,6 +221,8 @@ public ProbModelChecker(PrismComponent parent) throws PrismException
setTermCritParam(settings.getDouble(PrismSettings.PRISM_TERM_CRIT_PARAM));
// PRISM_MAX_ITERS
setMaxIters(settings.getInteger(PrismSettings.PRISM_MAX_ITERS));
+ // PRISM_PRECISION
+ setPrecision(settings.getInteger(PrismSettings.PRISM_PRECISION));
// PRISM_PRECOMPUTATION
setPrecomp(settings.getBoolean(PrismSettings.PRISM_PRECOMPUTATION));
// PRISM_PROB0
@@ -248,6 +260,7 @@ public void inheritSettings(ProbModelChecker other)
setTermCrit(other.getTermCrit());
setTermCritParam(other.getTermCritParam());
setMaxIters(other.getMaxIters());
+ setPrecision(other.getPrecision());
setPrecomp(other.getPrecomp());
setProb0(other.getProb0());
setProb1(other.getProb1());
@@ -267,6 +280,7 @@ public void printSettings()
mainLog.print("termCrit = " + termCrit + " ");
mainLog.print("termCritParam = " + termCritParam + " ");
mainLog.print("maxIters = " + maxIters + " ");
+ mainLog.print("precision = " + precision + " ");
mainLog.print("precomp = " + precomp + " ");
mainLog.print("prob0 = " + prob0 + " ");
mainLog.print("prob1 = " + prob1 + " ");
@@ -325,6 +339,14 @@ public void setMaxIters(int maxIters)
this.maxIters = maxIters;
}
+ /**
+ * Set accuracy in bits for arbitrary precision results.
+ */
+ public void setPrecision(int precision)
+ {
+ this.precision = precision;
+ }
+
/**
* Set whether or not to use precomputation (Prob0, Prob1, etc.).
*/
@@ -423,6 +445,11 @@ public int getMaxIters()
return maxIters;
}
+ public int getPrecision()
+ {
+ return precision;
+ }
+
public boolean getPrecomp()
{
return precomp;
diff --git a/prism/src/prism/Makefile b/prism/src/prism/Makefile
index 62e786b97..1c9e087ca 100644
--- a/prism/src/prism/Makefile
+++ b/prism/src/prism/Makefile
@@ -64,6 +64,7 @@ $(PRISM_DIR_REL)/$(OBJ_DIR)/$(THIS_DIR)/ngprism$(EXE): ngprism.c
clean: checks
@rm -f $(CLASS_FILES) $(PRISM_DIR_REL)/$(LIB_DIR)/$(LIBPREFIX)prism$(LIBSUFFIX) $(O_FILES) $(PRISM_DIR_REL)/$(OBJ_DIR)/$(THIS_DIR)/ngprism$(EXE)
+ @sed -i '/EXACT_LP_REQUIREMENTS_INSTALLED/c\ public final int EXACT_LP_REQUIREMENTS_INSTALLED = 1;' PrismSettings.java
celan: clean
diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java
index 82d2b5e03..574eee7df 100644
--- a/prism/src/prism/Prism.java
+++ b/prism/src/prism/Prism.java
@@ -2787,7 +2787,7 @@ public Result modelCheck(PropertiesFile propertiesFile, Property prop) throws Pr
}
// For exact model checking
- if (settings.getBoolean(PrismSettings.PRISM_EXACT_ENABLED)) {
+ if (settings.getBoolean(PrismSettings.PRISM_EXACT_ENABLED) && !settings.getBoolean(PrismSettings.PRISM_EXACT_LP_ENABLED)) {
return modelCheckExact(propertiesFile, prop);
}
// For fast adaptive uniformisation
diff --git a/prism/src/prism/PrismSettings.java b/prism/src/prism/PrismSettings.java
index a3e9f4004..3a91ddf1d 100644
--- a/prism/src/prism/PrismSettings.java
+++ b/prism/src/prism/PrismSettings.java
@@ -89,7 +89,8 @@ public class PrismSettings implements Observer
public static final String PRISM_TERM_CRIT = "prism.termCrit";//"prism.termination";
public static final String PRISM_TERM_CRIT_PARAM = "prism.termCritParam";//"prism.terminationEpsilon";
public static final String PRISM_MAX_ITERS = "prism.maxIters";//"prism.maxIterations";
-
+ public static final String PRISM_PRECISION = "prism.precision";//"prism.precision";
+
public static final String PRISM_CUDD_MAX_MEM = "prism.cuddMaxMem";
public static final String PRISM_CUDD_EPSILON = "prism.cuddEpsilon";
public static final String PRISM_NUM_SB_LEVELS = "prism.numSBLevels";//"prism.hybridNumLevels";
@@ -102,6 +103,7 @@ public class PrismSettings implements Observer
public static final String PRISM_SCC_METHOD = "prism.sccMethod";
public static final String PRISM_SYMM_RED_PARAMS = "prism.symmRedParams";
public static final String PRISM_EXACT_ENABLED = "prism.exact.enabled";
+ public static final String PRISM_EXACT_LP_ENABLED = "prism.exact.enabled";
public static final String PRISM_PTA_METHOD = "prism.ptaMethod";
public static final String PRISM_TRANSIENT_METHOD = "prism.transientMethod";
public static final String PRISM_AR_OPTIONS = "prism.arOptions";
@@ -185,8 +187,8 @@ public class PrismSettings implements Observer
public static final String LOG_SELECTION_COLOUR = "log.selectionColour";
public static final String LOG_BG_COLOUR = "log.bgColour";
public static final String LOG_BUFFER_LENGTH = "log.bufferLength";
-
-
+
+
//Defaults, types and constaints
public static final String[] propertyOwnerNames =
@@ -228,17 +230,19 @@ public class PrismSettings implements Observer
"Which engine (hybrid, sparse, MTBDD, explicit) should be used for model checking." },
{ BOOLEAN_TYPE, PRISM_EXACT_ENABLED, "Do exact model checking", "4.2.1", new Boolean(false), "",
"Perform exact model checking." },
+ { BOOLEAN_TYPE, PRISM_EXACT_LP_ENABLED, "Do exact model checking using linear programming", "4.2.1", new Boolean(false), "",
+ "Perform exact model checking using linear programming." },
{ CHOICE_TYPE, PRISM_PTA_METHOD, "PTA model checking method", "3.3", "Stochastic games", "Digital clocks,Stochastic games,Backwards reachability",
"Which method to use for model checking of PTAs." },
{ CHOICE_TYPE, PRISM_TRANSIENT_METHOD, "Transient probability computation method", "3.3", "Uniformisation", "Uniformisation,Fast adaptive uniformisation",
"Which method to use for computing transient probabilities in CTMCs." },
// NUMERICAL SOLUTION OPTIONS:
- { CHOICE_TYPE, PRISM_LIN_EQ_METHOD, "Linear equations method", "2.1", "Jacobi", "Power,Jacobi,Gauss-Seidel,Backwards Gauss-Seidel,Pseudo-Gauss-Seidel,Backwards Pseudo-Gauss-Seidel,JOR,SOR,Backwards SOR,Pseudo-SOR,Backwards Pseudo-SOR",
+ { CHOICE_TYPE, PRISM_LIN_EQ_METHOD, "Linear equations method", "2.1", "Jacobi", "Power,Jacobi,Gauss-Seidel,Backwards Gauss-Seidel,Pseudo-Gauss-Seidel,Backwards Pseudo-Gauss-Seidel,JOR,SOR,Backwards SOR,Pseudo-SOR,Backwards Pseudo-SOR,Exact linear programming",
"Which iterative method to use when solving linear equation systems." },
{ DOUBLE_TYPE, PRISM_LIN_EQ_METHOD_PARAM, "Over-relaxation parameter", "2.1", new Double(0.9), "",
"Over-relaxation parameter for iterative numerical methods such as JOR/SOR." },
- { CHOICE_TYPE, PRISM_MDP_SOLN_METHOD, "MDP solution method", "4.0", "Value iteration", "Value iteration,Gauss-Seidel,Policy iteration,Modified policy iteration,Linear programming",
+ { CHOICE_TYPE, PRISM_MDP_SOLN_METHOD, "MDP solution method", "4.0", "Value iteration", "Value iteration,Gauss-Seidel,Policy iteration,Modified policy iteration,Linear programming,Exact linear programming",
"Which method to use when solving Markov decision processes." },
{ CHOICE_TYPE, PRISM_MDP_MULTI_SOLN_METHOD, "MDP multi-objective solution method", "4.0.3", "Value iteration", "Value iteration,Gauss-Seidel,Linear programming",
"Which method to use when solving multi-objective queries on Markov decision processes." },
@@ -248,6 +252,8 @@ public class PrismSettings implements Observer
"Epsilon value to use for checking termination of iterative numerical methods." },
{ INTEGER_TYPE, PRISM_MAX_ITERS, "Termination max. iterations", "2.1", new Integer(10000), "0,",
"Maximum number of iterations to perform if iterative methods do not converge." },
+ { INTEGER_TYPE, PRISM_PRECISION, "Floating point precision", "2.1", new Integer(64), "1,",
+ "The accuracy of the mantissa (in bits) is determined by this variable. This user-selectable precision is a minimum value: it is rounded up to a whole limb." },
// MODEL CHECKING OPTIONS:
{ BOOLEAN_TYPE, PRISM_PRECOMPUTATION, "Use precomputation", "2.1", new Boolean(true), "",
"Whether to use model checking precomputation algorithms (Prob0, Prob1, etc.), where optional." },
@@ -416,13 +422,16 @@ public class PrismSettings implements Observer
{ INTEGER_TYPE, LOG_BUFFER_LENGTH, "Buffer length", "2.1", new Integer(10000), "1,", "Length of the buffer for the log display." }
}
};
-
+
public static final String[] oldPropertyNames = {"simulator.apmcStrategy", "simulator.engine", "simulator.newPathAskDefault"};
-
+
+ //Exact LP requirements
+ public final int EXACT_LP_REQUIREMENTS_INSTALLED = 1;
+
public DefaultSettingOwner[] optionOwners;
private Hashtable data;
private boolean modified;
-
+
private ArrayList settingsListeners;
/**
@@ -906,6 +915,12 @@ else if (sw.equals("explicit") || sw.equals("ex")) {
else if (sw.equals("exact")) {
set(PRISM_EXACT_ENABLED, true);
}
+ // Exact linear programming model checking
+ else if (sw.equals("exact-lp")) {
+ set(PRISM_EXACT_LP_ENABLED, true);
+ set(PRISM_MDP_SOLN_METHOD, "Exact linear programming");
+ set(PRISM_LIN_EQ_METHOD, "Exact linear programming");
+ }
// PTA model checking methods
else if (sw.equals("ptamethod")) {
if (i < args.length - 1) {
@@ -1025,7 +1040,22 @@ else if (sw.equals("maxiters")) {
throw new PrismException("No value specified for -" + sw + " switch");
}
}
-
+ // Precision
+ else if (sw.equals("precision")) {
+ if (i < args.length - 1) {
+ try {
+ j = Integer.parseInt(args[++i]);
+ if (j <= 0)
+ throw new NumberFormatException("");
+ set(PRISM_PRECISION, j);
+ } catch (NumberFormatException e) {
+ throw new PrismException("Invalid value for -" + sw + " switch");
+ }
+ } else {
+ throw new PrismException("No value specified for -" + sw + " switch");
+ }
+ }
+
// MODEL CHECKING OPTIONS:
// Precomputation algs off
@@ -1585,6 +1615,7 @@ public static void printHelp(PrismLog mainLog)
mainLog.println("-hybrid (or -h) ................ Use the Hybrid engine [default]");
mainLog.println("-explicit (or -ex) ............. Use the explicit engine");
mainLog.println("-exact ......................... Perform exact (arbitrary precision) model checking");
+ mainLog.println("-exact-lp ...................... Perform exact (arbitrary precision) model checking using linear programming");
mainLog.println("-ptamethod .............. Specify PTA engine (games, digital, backwards) [default: games]");
mainLog.println("-transientmethod ........ CTMC transient analysis methof (unif, fau) [default: unif]");
mainLog.println();