diff --git a/prism/src/META-INF/MANIFEST.MF b/prism/src/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..439ef63
--- /dev/null
+++ b/prism/src/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: explicit.nxt.NxtCLI
+
diff --git a/prism/src/automata/DA.java b/prism/src/automata/DA.java
index ee02a2f..b3511b1 100644
--- a/prism/src/automata/DA.java
+++ b/prism/src/automata/DA.java
@@ -63,10 +63,10 @@ public class DA<Symbol, Acceptance extends AcceptanceOmega>
 	/** Local class to represent DRA edge */
 	class Edge
 	{
-		private Symbol label;
-		private int dest;
+		final Symbol label;
+		final int dest;
 
-		public Edge(Symbol label, int dest)
+		Edge(Symbol label, int dest)
 		{
 			this.label = label;
 			this.dest = dest;
@@ -314,16 +314,14 @@ public class DA<Symbol, Acceptance extends AcceptanceOmega>
 	public void print(PrismLog out, String type) throws PrismException
 	{
 		switch (type) {
-		case "txt":
+			// Default to txt
+			default:
+			case "txt":
 			out.println(toString());
 			break;
 		case "dot":
 			printDot(out);
 			break;
-		// Default to txt
-		default:
-			out.println(toString());
-			break;
 		}
 	}
 
diff --git a/prism/src/automata/LTL2DA.java b/prism/src/automata/LTL2DA.java
index e06882f..d123682 100644
--- a/prism/src/automata/LTL2DA.java
+++ b/prism/src/automata/LTL2DA.java
@@ -211,7 +211,7 @@ public class LTL2DA extends PrismComponent
 
 			// if we are running under the Nailgun environment, setup the
 			// environment to include the environment variables of the Nailgun client
-			prism.PrismNG.setupChildProcessEnvironment(builder);
+			//prism.PrismNG.setupChildProcessEnvironment(builder);
 
 			Process p = builder.start();
 			p.getInputStream().close();
diff --git a/prism/src/automata/LTSFromDA.java b/prism/src/automata/LTSFromDA.java
index 5da6fcb..e8ebc91 100644
--- a/prism/src/automata/LTSFromDA.java
+++ b/prism/src/automata/LTSFromDA.java
@@ -26,11 +26,12 @@
 
 package automata;
 
-import java.util.BitSet;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
+import java.util.*;
 
+import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntIterators;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
 import prism.ModelType;
 import prism.PrismException;
 import prism.PrismLog;
@@ -57,13 +58,15 @@ public class LTSFromDA extends ModelExplicit implements LTS
 	// Methods to implement Model
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(int s)
+	public IntIterator getSuccessorsIterator(int s)
 	{
-		Set<Integer> succs = new HashSet<Integer>();
+		IntSet succs = new IntOpenHashSet();
+
 		int n = da.getNumEdges(s);
 		for (int i = 0; i < n; i++) {
 			succs.add(da.getEdgeDest(s, i));
 		}
+
 		return succs.iterator();
 	}
 
@@ -212,11 +215,9 @@ public class LTSFromDA extends ModelExplicit implements LTS
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(int s, int i)
+	public IntIterator getSuccessorsIterator(int s, int i)
 	{
-		Set<Integer> succs = new HashSet<Integer>();
-		succs.add(da.getEdgeDest(s, i));
-		return succs.iterator();
+		return IntIterators.singleton(da.getEdgeDest(s, i));
 	}
 
 	@Override
diff --git a/prism/src/common/IterableBitSet.java b/prism/src/common/IterableBitSet.java
index 9d2ec87..ef9a401 100644
--- a/prism/src/common/IterableBitSet.java
+++ b/prism/src/common/IterableBitSet.java
@@ -30,6 +30,7 @@ package common;
 import java.util.BitSet;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.PrimitiveIterator;
 
 /**
  * Convenience class to loop easily over the set/clear bits of a BitSet.
@@ -39,9 +40,9 @@ import java.util.NoSuchElementException;
  */
 public class IterableBitSet implements Iterable<Integer>
 {
-	private BitSet set;
-	private boolean clearBits = false;
-	private Integer maxIndex = null;
+	private final BitSet set;
+	private final boolean clearBits;
+	private final int maxIndex;
 
 	/**
 	 * Constructor for an Iterable that iterates over the set bits of {@code set}
@@ -50,6 +51,7 @@ public class IterableBitSet implements Iterable<Integer>
 	{
 		this.set = set;
 		this.clearBits = false;
+		this.maxIndex = Integer.MAX_VALUE;
 	}
 
 	/**
@@ -68,25 +70,22 @@ public class IterableBitSet implements Iterable<Integer>
 	}
 
 	/** Implementation of the iterator over the set bits */
-	private class SetBitsIterator implements Iterator<Integer> {
+	private class SetBitsIterator implements PrimitiveIterator.OfInt {
 		private int index = set.nextSetBit(0);
 
 		@Override
 		public boolean hasNext() {
-			if (maxIndex != null && index > maxIndex) {
-				// limit to 0 ... maxIndex
-				return false;
-			}
-			return index >= 0;
+			return 0 <= index && index <= maxIndex;
 		}
 
 		@Override
-		public Integer next() {
+		public int nextInt() {
 			if (hasNext()) {
-				Integer next = index;
+				int next = index;
 				index = set.nextSetBit(index + 1);
 				return next;
 			}
+
 			throw new NoSuchElementException();
 		}
 
@@ -97,25 +96,22 @@ public class IterableBitSet implements Iterable<Integer>
 	}
 
 	/** Implementation of the iterator over the cleared bits, requires that {@code maxIndex != null} */
-	private class ClearBitsIterator implements Iterator<Integer> {
+	private class ClearBitsIterator implements PrimitiveIterator.OfInt {
 		private int index = set.nextClearBit(0);
 
 		@Override
 		public boolean hasNext() {
-			if (index > maxIndex) {
-				// limit to 0 ... maxIndex
-				return false;
-			}
-			return true;
+			return index <= maxIndex;
 		}
 
 		@Override
-		public Integer next() {
+		public int nextInt() {
 			if (hasNext()) {
-				Integer next = index;
+				int next = index;
 				index = set.nextClearBit(index + 1);
 				return next;
 			}
+
 			throw new NoSuchElementException();
 		}
 
@@ -126,12 +122,12 @@ public class IterableBitSet implements Iterable<Integer>
 	}
 
 	@Override
-	public Iterator<Integer> iterator() {
-		if (clearBits == false) {
+	public PrimitiveIterator.OfInt iterator() {
+		if (!clearBits) {
 			return new SetBitsIterator();
-		} else {
-			return new ClearBitsIterator();
 		}
+
+		return new ClearBitsIterator();
 	}
 
 	/**
diff --git a/prism/src/common/IterableStateSet.java b/prism/src/common/IterableStateSet.java
index eb8b8d9..6c05363 100644
--- a/prism/src/common/IterableStateSet.java
+++ b/prism/src/common/IterableStateSet.java
@@ -30,7 +30,9 @@ package common;
 import java.util.BitSet;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.PrimitiveIterator;
 
+// Primitive Iterator?
 /**
  * A convenience wrapper around IterableBitSet that handles the three cases of
  * iterating over the set or cleared bits of a BitSet representing a set of states
@@ -79,26 +81,23 @@ public class IterableStateSet implements Iterable<Integer> {
 	}
 
 	/** Implementation of an Iterator that iterates over all state indizes 0..numStates-1 */
-	private class AllStatesIterator implements Iterator<Integer>
+	private class AllStatesIterator implements PrimitiveIterator.OfInt
 	{
 		private int current = 0;
 
 		@Override
 		public boolean hasNext()
 		{
-			if (current < numStates) {
-				return true;
-			} else {
-				return false;
-			}
+			return current < numStates;
 		}
 
 		@Override
-		public Integer next()
+		public int nextInt()
 		{
 			if (hasNext()) {
 				return current++;
 			}
+
 			throw new NoSuchElementException();
 		}
 
@@ -107,10 +106,10 @@ public class IterableStateSet implements Iterable<Integer> {
 		{
 			throw new UnsupportedOperationException();
 		}
-	};
+	}
 
 	/** Implementation of an Iterator that iterates over the empty state set */
-	private class NoStatesIterator implements Iterator<Integer>
+	private static class NoStatesIterator implements PrimitiveIterator.OfInt
 	{
 		@Override
 		public boolean hasNext()
@@ -119,7 +118,7 @@ public class IterableStateSet implements Iterable<Integer> {
 		}
 
 		@Override
-		public Integer next()
+		public int nextInt()
 		{
 			throw new NoSuchElementException();
 		}
@@ -129,10 +128,10 @@ public class IterableStateSet implements Iterable<Integer> {
 		{
 			throw new UnsupportedOperationException();
 		}
-	};
+	}
 
 	@Override
-	public Iterator<Integer> iterator()
+	public PrimitiveIterator.OfInt iterator()
 	{
 		if (setOfStates == null) {
 			if (!complement) {
diff --git a/prism/src/explicit/Bisimulation.java b/prism/src/explicit/Bisimulation.java
index 6126c46..9ebd604 100644
--- a/prism/src/explicit/Bisimulation.java
+++ b/prism/src/explicit/Bisimulation.java
@@ -32,6 +32,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
 import parser.State;
 import prism.PrismComponent;
 import prism.PrismException;
@@ -199,7 +200,7 @@ public class Bisimulation extends PrismComponent
 		mdp = new MDPSimple(numBlocks);
 		for (s = 0; s < numStates; s++) {
 			// Build lifted distribution
-			Iterator<Map.Entry<Integer, Double>> iter = dtmc.getTransitionsIterator(s);
+			Iterator<Int2DoubleMap.Entry> iter = dtmc.getTransitionsIterator(s);
 			distrNew = new Distribution();
 			while (iter.hasNext()) {
 				Map.Entry<Integer, Double> e = iter.next();
diff --git a/prism/src/explicit/CTMDPSimple.java b/prism/src/explicit/CTMDPSimple.java
index 9ecf978..800fd71 100644
--- a/prism/src/explicit/CTMDPSimple.java
+++ b/prism/src/explicit/CTMDPSimple.java
@@ -104,12 +104,12 @@ public class CTMDPSimple extends MDPSimple implements CTMDP
 		int i, j, n;
 		double d;
 		for (i = 0; i < numStates; i++) {
-			n = trans.get(i).size();
+			n = trans.get(i).length;
 			if (n < 2)
 				continue;
-			d = trans.get(i).get(0).sum();
+			d = trans.get(i)[0].sum();
 			for (j = 1; j < n; j++) {
-				if (!PrismUtils.doublesAreCloseAbs(trans.get(i).get(j).sum(), d, 1e-12))
+				if (!PrismUtils.doublesAreCloseAbs(trans.get(i)[j].sum(), d, 1e-12))
 					return false;
 			}
 		}
diff --git a/prism/src/explicit/ConstructModel.java b/prism/src/explicit/ConstructModel.java
index bd1c8e8..fbd1dd9 100644
--- a/prism/src/explicit/ConstructModel.java
+++ b/prism/src/explicit/ConstructModel.java
@@ -29,9 +29,7 @@ package explicit;
 
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.util.BitSet;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.*;
 
 import parser.State;
 import parser.Values;
@@ -152,7 +150,7 @@ public class ConstructModel extends PrismComponent
 		ModelType modelType;
 		// State storage
 		StateStorage<State> states;
-		LinkedList<State> explore;
+		Deque<State> explore;
 		State state, stateNew;
 		// Explicit model storage
 		ModelSimple modelSimple = null;
@@ -209,8 +207,8 @@ public class ConstructModel extends PrismComponent
 		}
 
 		// Initialise states storage
-		states = new IndexedSet<State>(true);
-		explore = new LinkedList<State>();
+		states = new IndexedSet<>(true);
+		explore = new ArrayDeque<>();
 		// Add initial state(s) to 'explore', 'states' and to the model
 		for (State initState : modelGen.getInitialStates()) {
 			explore.add(initState);
@@ -306,7 +304,7 @@ public class ConstructModel extends PrismComponent
 			modelSimple.findDeadlocks(fixDeadlocks);
 		}
 
-		boolean sort = true;
+		boolean sort = false;
 		int permut[] = null;
 
 		if (sort) {
diff --git a/prism/src/explicit/DTMC.java b/prism/src/explicit/DTMC.java
index 012d143..e301dff 100644
--- a/prism/src/explicit/DTMC.java
+++ b/prism/src/explicit/DTMC.java
@@ -29,6 +29,7 @@ package explicit;
 import java.util.*;
 import java.util.Map.Entry;
 
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
 import prism.Pair;
 import explicit.rewards.MCRewards;
 
@@ -45,7 +46,14 @@ public interface DTMC extends Model
 	/**
 	 * Get an iterator over the transitions from state s.
 	 */
-	public Iterator<Entry<Integer, Double>> getTransitionsIterator(int s);
+	public Iterator<Int2DoubleMap.Entry> getTransitionsIterator(int s);
+
+	/**
+	 * Get an iterator over the transitions from state s. - The second component is always ignored
+	 */
+	public default Iterator<Int2DoubleMap.Entry> getTransitionsIterator(int s, int i) {
+		return getTransitionsIterator(s);
+	}
 
 	/**
 	 * Get an iterator over the transitions from state s, with their attached actions if present.
diff --git a/prism/src/explicit/DTMCEmbeddedSimple.java b/prism/src/explicit/DTMCEmbeddedSimple.java
index ec16e71..965d20e 100644
--- a/prism/src/explicit/DTMCEmbeddedSimple.java
+++ b/prism/src/explicit/DTMCEmbeddedSimple.java
@@ -27,11 +27,14 @@
 package explicit;
 
 import java.util.*;
-import java.util.Map.Entry;
 
 import common.IterableStateSet;
 
 import explicit.rewards.MCRewards;
+import it.unimi.dsi.fastutil.ints.Int2DoubleAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
+import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntIterators;
 import parser.State;
 import parser.Values;
 import prism.ModelType;
@@ -129,12 +132,10 @@ public class DTMCEmbeddedSimple extends DTMCExplicit
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(final int s)
+	public IntIterator getSuccessorsIterator(final int s)
 	{
 		if (exitRates[s] == 0) {
-			List<Integer> list = new ArrayList<Integer>(1);
-			list.add(s);
-			return list.iterator();
+			return IntIterators.singleton(s);
 		} else {
 			return ctmc.getSuccessorsIterator(s);
 		}
@@ -229,19 +230,19 @@ public class DTMCEmbeddedSimple extends DTMCExplicit
 		}
 	}
 
-	public Iterator<Entry<Integer,Double>> getTransitionsIterator(int s)
+	public Iterator<Int2DoubleMap.Entry> getTransitionsIterator(int s)
 	{
 		if (exitRates[s] == 0) {
 			// return prob-1 self-loop
-			Map<Integer,Double> m = new TreeMap<Integer,Double>();
+			Int2DoubleMap m = new Int2DoubleAVLTreeMap();
 			m.put(s, 1.0);
-			return m.entrySet().iterator();
+			return m.int2DoubleEntrySet().iterator();
 		} else {
-			final Iterator<Entry<Integer,Double>> ctmcIterator = ctmc.getTransitionsIterator(s);
+			final Iterator<Int2DoubleMap.Entry> ctmcIterator = ctmc.getTransitionsIterator(s);
 			
 			// return iterator over entries, with probabilities divided by exitRates[s]
 			final double er = exitRates[s];
-			return new Iterator<Entry<Integer,Double>>() {
+			return new Iterator<Int2DoubleMap.Entry>() {
 				@Override
 				public boolean hasNext()
 				{
@@ -249,14 +250,19 @@ public class DTMCEmbeddedSimple extends DTMCExplicit
 				}
 
 				@Override
-				public Entry<Integer, Double> next()
+				public Int2DoubleMap.Entry next()
 				{
-					final Entry<Integer, Double> ctmcEntry = ctmcIterator.next();
+					final Int2DoubleMap.Entry ctmcEntry = ctmcIterator.next();
 					
-					return new Entry<Integer, Double>() {
+					return new Int2DoubleMap.Entry() {
 						@Override
 						public Integer getKey()
 						{
+							return ctmcEntry.getIntKey();
+						}
+
+						@Override
+						public int getIntKey() {
 							return ctmcEntry.getKey();
 						}
 
@@ -267,6 +273,16 @@ public class DTMCEmbeddedSimple extends DTMCExplicit
 						}
 
 						@Override
+						public double setValue(double value) {
+							throw new UnsupportedOperationException();
+						}
+
+						@Override
+						public double getDoubleValue() {
+							return ctmcEntry.getValue() / er;
+						}
+
+						@Override
 						public Double setValue(Double value)
 						{
 							throw new UnsupportedOperationException();
diff --git a/prism/src/explicit/DTMCExplicit.java b/prism/src/explicit/DTMCExplicit.java
index 3982e5c..cd9e2ef 100644
--- a/prism/src/explicit/DTMCExplicit.java
+++ b/prism/src/explicit/DTMCExplicit.java
@@ -37,6 +37,7 @@ import java.util.Map.Entry;
 
 import common.IterableStateSet;
 import explicit.rewards.MCRewards;
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
 import prism.ModelType;
 import prism.Pair;
 import prism.PrismException;
@@ -87,7 +88,7 @@ public abstract class DTMCExplicit extends ModelExplicit implements DTMC
 	@Override
 	public void exportTransitionsToDotFile(int i, PrismLog out)
 	{
-		Iterator<Map.Entry<Integer, Double>> iter = getTransitionsIterator(i);
+		Iterator<Int2DoubleMap.Entry> iter = getTransitionsIterator(i);
 		while (iter.hasNext()) {
 			Map.Entry<Integer, Double> e = iter.next();
 			out.print(i + " -> " + e.getKey() + " [ label=\"");
@@ -110,7 +111,7 @@ public abstract class DTMCExplicit extends ModelExplicit implements DTMC
 			sorted = new TreeMap<Integer, Double>();
 			for (i = 0; i < numStates; i++) {
 				// Extract transitions and sort by destination state index (to match PRISM-exported files)
-				Iterator<Map.Entry<Integer, Double>> iter = getTransitionsIterator(i);
+				Iterator<Int2DoubleMap.Entry> iter = getTransitionsIterator(i);
 				while (iter.hasNext()) {
 					Map.Entry<Integer, Double> e = iter.next();
 					sorted.put(e.getKey(), e.getValue());
@@ -185,11 +186,11 @@ public abstract class DTMCExplicit extends ModelExplicit implements DTMC
 
 	public class AddDefaultActionToTransitionsIterator implements Iterator<Map.Entry<Integer, Pair<Double, Object>>>
 	{
-		private Iterator<Entry<Integer, Double>> transIter;
+		private Iterator<Int2DoubleMap.Entry> transIter;
 		private Object defaultAction;
 		private Entry<Integer, Double> next;
 
-		public AddDefaultActionToTransitionsIterator(Iterator<Entry<Integer, Double>> transIter, Object defaultAction)
+		public AddDefaultActionToTransitionsIterator(Iterator<Int2DoubleMap.Entry> transIter, Object defaultAction)
 		{
 			this.transIter = transIter;
 			this.defaultAction = defaultAction;
diff --git a/prism/src/explicit/DTMCFromMDPAndMDStrategy.java b/prism/src/explicit/DTMCFromMDPAndMDStrategy.java
index 8c083ef..97e5895 100644
--- a/prism/src/explicit/DTMCFromMDPAndMDStrategy.java
+++ b/prism/src/explicit/DTMCFromMDPAndMDStrategy.java
@@ -27,9 +27,10 @@
 package explicit;
 
 import java.util.*;
-import java.util.Map.Entry;
 
 import explicit.rewards.MCRewards;
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
+import it.unimi.dsi.fastutil.ints.IntIterator;
 import parser.State;
 import parser.Values;
 import prism.ModelType;
@@ -121,7 +122,7 @@ public class DTMCFromMDPAndMDStrategy extends DTMCExplicit
 		return numTransitions;
 	}
 
-	public Iterator<Integer> getSuccessorsIterator(final int s)
+	public IntIterator getSuccessorsIterator(final int s)
 	{
 		throw new RuntimeException("Not implemented yet");
 	}
@@ -181,13 +182,13 @@ public class DTMCFromMDPAndMDStrategy extends DTMCExplicit
 		return strat.isChoiceDefined(s) ? mdp.getNumTransitions(s, strat.getChoiceIndex(s)) : 0;
 	}
 
-	public Iterator<Entry<Integer, Double>> getTransitionsIterator(int s)
+	public Iterator<Int2DoubleMap.Entry> getTransitionsIterator(int s)
 	{
 		if (strat.isChoiceDefined(s)) {
-			return mdp.getTransitionsIterator(s, strat.getChoiceIndex(s));
+			return mdp.getTransitions(s, strat.getChoiceIndex(s)).iterator();
 		} else {
 			// Empty iterator
-			return new Iterator<Entry<Integer, Double>>()
+			return new Iterator<Int2DoubleMap.Entry>()
 			{
 				@Override
 				public boolean hasNext()
@@ -196,7 +197,7 @@ public class DTMCFromMDPAndMDStrategy extends DTMCExplicit
 				}
 
 				@Override
-				public Entry<Integer, Double> next()
+				public Int2DoubleMap.Entry next()
 				{
 					return null;
 				}
diff --git a/prism/src/explicit/DTMCFromMDPMemorylessAdversary.java b/prism/src/explicit/DTMCFromMDPMemorylessAdversary.java
index d1bd2b2..1ea38c9 100644
--- a/prism/src/explicit/DTMCFromMDPMemorylessAdversary.java
+++ b/prism/src/explicit/DTMCFromMDPMemorylessAdversary.java
@@ -26,12 +26,11 @@
 
 package explicit;
 
-import java.util.BitSet;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
+import java.util.*;
 import java.util.Map.Entry;
 
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
+import it.unimi.dsi.fastutil.ints.IntIterator;
 import parser.State;
 import parser.Values;
 import prism.ModelType;
@@ -124,7 +123,7 @@ public class DTMCFromMDPMemorylessAdversary extends DTMCExplicit
 		return numTransitions;
 	}
 
-	public Iterator<Integer> getSuccessorsIterator(final int s)
+	public IntIterator getSuccessorsIterator(final int s)
 	{
 		throw new RuntimeException("Not implemented yet");
 	}
@@ -186,13 +185,13 @@ public class DTMCFromMDPMemorylessAdversary extends DTMCExplicit
 	}
 
 	@Override
-	public Iterator<Entry<Integer, Double>> getTransitionsIterator(int s)
+	public Iterator<Int2DoubleMap.Entry> getTransitionsIterator(int s)
 	{
 		if (adv[s] >= 0) {
-			return mdp.getTransitionsIterator(s, adv[s]);
+			return mdp.getTransitions(s, adv[s]).iterator();
 		} else {
 			// Empty iterator
-			return Collections.<Entry<Integer,Double>>emptyIterator(); 
+			return Collections.emptyIterator();
 		}
 	}
 
@@ -200,10 +199,10 @@ public class DTMCFromMDPMemorylessAdversary extends DTMCExplicit
 	public Iterator<Entry<Integer, Pair<Double, Object>>> getTransitionsAndActionsIterator(int s)
 	{
 		if (adv[s] >= 0) {
-			return new DTMCExplicit.AddDefaultActionToTransitionsIterator(mdp.getTransitionsIterator(s, adv[s]), mdp.getAction(s, adv[s]));
+			return new DTMCExplicit.AddDefaultActionToTransitionsIterator(mdp.getTransitions(s, adv[s]).iterator(), mdp.getAction(s, adv[s]));
 		} else {
 			// Empty iterator
-			return Collections.<Entry<Integer,Pair<Double, Object>>>emptyIterator(); 
+			return Collections.emptyIterator();
 		}
 	}
 
diff --git a/prism/src/explicit/DTMCModelChecker.java b/prism/src/explicit/DTMCModelChecker.java
index 8b17bca..2e81a72 100644
--- a/prism/src/explicit/DTMCModelChecker.java
+++ b/prism/src/explicit/DTMCModelChecker.java
@@ -949,7 +949,7 @@ public class DTMCModelChecker extends ProbModelChecker
 	 * @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 computeReachProbsGaussSeidel(DTMC dtmc, BitSet no, BitSet yes, double init[], BitSet known) throws PrismException
+	public ModelCheckerResult computeReachProbsGaussSeidel(DTMC dtmc, BitSet no, BitSet yes, double init[], BitSet known) throws PrismException
 	{
 		ModelCheckerResult res;
 		BitSet unknown;
@@ -1233,7 +1233,7 @@ public class DTMCModelChecker extends ProbModelChecker
 	 * @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 computeReachRewardsValIter(DTMC dtmc, MCRewards mcRewards, BitSet target, BitSet inf, double init[], BitSet known)
+	public ModelCheckerResult computeReachRewardsValIter(DTMC dtmc, MCRewards mcRewards, BitSet target, BitSet inf, double init[], BitSet known)
 			throws PrismException
 	{
 		ModelCheckerResult res;
diff --git a/prism/src/explicit/DTMCSimple.java b/prism/src/explicit/DTMCSimple.java
index b20fdd2..3bee98e 100644
--- a/prism/src/explicit/DTMCSimple.java
+++ b/prism/src/explicit/DTMCSimple.java
@@ -27,12 +27,14 @@
 package explicit;
 
 import java.util.*;
-import java.util.Map.Entry;
 import java.io.*;
 
 import common.IterableStateSet;
 
 import explicit.rewards.*;
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
+import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntIterators;
 import prism.PrismException;
 
 /**
@@ -217,9 +219,9 @@ public class DTMCSimple extends DTMCExplicit implements ModelSimple
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(final int s)
+	public IntIterator getSuccessorsIterator(final int s)
 	{
-		return trans.get(s).getSupport().iterator();
+		return IntIterators.wrap(trans.get(s).getSupport());
 	}
 
 	@Override
@@ -270,7 +272,7 @@ public class DTMCSimple extends DTMCExplicit implements ModelSimple
 	}
 
 	@Override
-	public Iterator<Entry<Integer, Double>> getTransitionsIterator(int s)
+	public Iterator<Int2DoubleMap.Entry> getTransitionsIterator(int s)
 	{
 		return trans.get(s).iterator();
 	}
diff --git a/prism/src/explicit/DTMCUniformisedSimple.java b/prism/src/explicit/DTMCUniformisedSimple.java
index 86bc8d3..7db1f8d 100644
--- a/prism/src/explicit/DTMCUniformisedSimple.java
+++ b/prism/src/explicit/DTMCUniformisedSimple.java
@@ -26,12 +26,10 @@
 
 package explicit;
 
-import java.util.BitSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
+import java.util.*;
 
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
+import it.unimi.dsi.fastutil.ints.IntIterator;
 import parser.State;
 import parser.Values;
 import prism.ModelType;
@@ -134,7 +132,7 @@ public class DTMCUniformisedSimple extends DTMCExplicit
 		return ctmc.getNumTransitions() + numExtraTransitions;
 	}
 
-	public Iterator<Integer> getSuccessorsIterator(final int s)
+	public IntIterator getSuccessorsIterator(final int s)
 	{
 		// TODO
 		throw new Error("Not yet supported");
@@ -205,7 +203,7 @@ public class DTMCUniformisedSimple extends DTMCExplicit
 		throw new RuntimeException("Not implemented yet");
 	}
 
-	public Iterator<Entry<Integer,Double>> getTransitionsIterator(int s)
+	public Iterator<Int2DoubleMap.Entry> getTransitionsIterator(int s)
 	{
 		// TODO
 		throw new RuntimeException("Not implemented yet");
diff --git a/prism/src/explicit/Distribution.java b/prism/src/explicit/Distribution.java
index e9a45e8..64d162f 100644
--- a/prism/src/explicit/Distribution.java
+++ b/prism/src/explicit/Distribution.java
@@ -26,29 +26,38 @@
 
 package explicit;
 
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
+import java.util.*;
+import java.util.function.IntConsumer;
 
+import explicit.nxt.IntDoubleConsumer;
+import explicit.nxt.SparseBitSet;
+import it.unimi.dsi.fastutil.ints.*;
 import prism.PrismUtils;
 
 /**
  * Explicit representation of a probability distribution.
  * Basically, a mapping from (integer-valued) indices to (non-zero, double-valued) probabilities. 
  */
-public class Distribution implements Iterable<Entry<Integer, Double>>
-{
-	private HashMap<Integer, Double> map;
+public class Distribution implements Iterable<Int2DoubleMap.Entry> {
+	private static final int[] INTS = new int[]{};
+	private static final double[] DOUBLES = new double[]{};
+
+	int[] keys;
+	double[] values;
 
 	/**
 	 * Create an empty distribution.
 	 */
 	public Distribution()
 	{
-		clear();
+		keys = INTS;
+		values = DOUBLES;
+	}
+
+	public Distribution(int key, double value)
+	{
+		keys = new int[]{key};
+		values = new double[]{value};
 	}
 
 	/**
@@ -56,12 +65,8 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public Distribution(Distribution distr)
 	{
-		this();
-		Iterator<Entry<Integer, Double>> i = distr.iterator();
-		while (i.hasNext()) {
-			Map.Entry<Integer, Double> e = i.next();
-			add(e.getKey(), e.getValue());
-		}
+		keys = distr.keys.clone();
+		values = distr.values.clone();
 	}
 
 	/**
@@ -72,12 +77,7 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public Distribution(Distribution distr, int permut[])
 	{
-		this();
-		Iterator<Entry<Integer, Double>> i = distr.iterator();
-		while (i.hasNext()) {
-			Map.Entry<Integer, Double> e = i.next();
-			add(permut[e.getKey()], e.getValue());
-		}
+		throw new UnsupportedOperationException();
 	}
 
 	/**
@@ -85,7 +85,8 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public void clear()
 	{
-		map = new HashMap<Integer, Double>();
+		keys = INTS;
+		values = DOUBLES;
 	}
 
 	/**
@@ -95,14 +96,34 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public boolean add(int j, double prob)
 	{
-		Double d = (Double) map.get(j);
-		if (d == null) {
-			map.put(j, prob);
-			return false;
-		} else {
-			set(j, d + prob);
+		if (j < 0 || prob <= 0) {
+			throw new IllegalArgumentException();
+		}
+
+		int i = Arrays.binarySearch(keys, j);
+
+		if (i >= 0) {
+			values[i] += prob;
 			return true;
 		}
+
+		// Compute insertion point
+		i = -(i + 1);
+
+		int[] localKeys = new int[keys.length + 1];
+		double[] localValues = new double[keys.length + 1];
+
+		System.arraycopy(keys, 0, localKeys, 0, i);
+		System.arraycopy(values, 0, localValues, 0, i);
+		localKeys[i] = j;
+		localValues[i] = prob;
+		System.arraycopy(keys, i, localKeys, i + 1, keys.length - i);
+		System.arraycopy(values, i, localValues, i + 1, keys.length - i);
+
+		keys = localKeys;
+		values = localValues;
+
+		return false;
 	}
 
 	/**
@@ -110,10 +131,7 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public void set(int j, double prob)
 	{
-		if (prob == 0.0)
-			map.remove(j);
-		else
-			map.put(j, prob);
+		throw new UnsupportedOperationException();
 	}
 
 	/**
@@ -121,9 +139,7 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public double get(int j)
 	{
-		Double d;
-		d = (Double) map.get(j);
-		return d == null ? 0.0 : d.doubleValue();
+		return values[Arrays.binarySearch(keys, j)];
 	}
 
 	/**
@@ -131,7 +147,7 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public boolean contains(int j)
 	{
-		return map.get(j) != null;
+		return Arrays.binarySearch(keys, j) >= 0;
 	}
 
 	/**
@@ -139,12 +155,23 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public boolean isSubsetOf(BitSet set)
 	{
-		Iterator<Entry<Integer, Double>> i = iterator();
-		while (i.hasNext()) {
-			Map.Entry<Integer, Double> e = i.next();
-			if (!set.get((Integer) e.getKey()))
+		for (int key : keys) {
+			if (!set.get(key)) {
 				return false;
+			}
 		}
+
+		return true;
+	}
+
+	public boolean isSubsetOf(SparseBitSet set)
+	{
+		for (int key : keys) {
+			if (!set.get(key)) {
+				return false;
+			}
+		}
+
 		return true;
 	}
 
@@ -153,29 +180,42 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public boolean containsOneOf(BitSet set)
 	{
-		Iterator<Entry<Integer, Double>> i = iterator();
-		while (i.hasNext()) {
-			Map.Entry<Integer, Double> e = i.next();
-			if (set.get((Integer) e.getKey()))
+		for (int key : keys) {
+			if (set.get(key)) {
 				return true;
+			}
 		}
+
 		return false;
 	}
 
 	/**
 	 * Get the support of the distribution.
 	 */
-	public Set<Integer> getSupport()
+	public int[] getSupport()
 	{
-		return map.keySet();
+		return keys;
 	}
 
 	/**
 	 * Get an iterator over the entries of the map defining the distribution.
 	 */
-	public Iterator<Entry<Integer, Double>> iterator()
+	public Iterator<Int2DoubleMap.Entry> iterator()
 	{
-		return map.entrySet().iterator();
+		return new Iterator<Int2DoubleMap.Entry>() {
+			DistributionEntry entry = new DistributionEntry();
+
+			@Override
+			public boolean hasNext() {
+				return (entry.index + 1) < keys.length;
+			}
+
+			@Override
+			public Int2DoubleMap.Entry next() {
+				entry.index++;
+				return entry;
+			}
+		};
 	}
 
 	/**
@@ -183,7 +223,7 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public boolean isEmpty()
 	{
-		return map.isEmpty();
+		return keys.length == 0;
 	}
 
 	/**
@@ -191,7 +231,7 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public int size()
 	{
-		return map.size();
+		return keys.length;
 	}
 
 	/**
@@ -200,11 +240,22 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	public double sum()
 	{
 		double d = 0.0;
-		Iterator<Entry<Integer, Double>> i = iterator();
-		while (i.hasNext()) {
-			Map.Entry<Integer, Double> e = i.next();
-			d += e.getValue();
+
+		for (double prob : values) {
+			d  += prob;
 		}
+
+		return d;
+	}
+
+	public double sumWeighted(double[] vect) {
+		double d = 0.0;
+		int i = 0;
+
+		for (int key : keys) {
+			d += vect[key] * values[i];
+		}
+
 		return d;
 	}
 
@@ -213,14 +264,7 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public double sumAllBut(int j)
 	{
-		double d = 0.0;
-		Iterator<Entry<Integer, Double>> i = iterator();
-		while (i.hasNext()) {
-			Map.Entry<Integer, Double> e = i.next();
-			if (e.getKey() != j)
-				d += e.getValue();
-		}
-		return d;
+		throw new UnsupportedOperationException();
 	}
 
 	/**
@@ -229,43 +273,75 @@ public class Distribution implements Iterable<Entry<Integer, Double>>
 	 */
 	public Distribution map(int map[])
 	{
-		Distribution distrNew = new Distribution();
-		Iterator<Entry<Integer, Double>> i = iterator();
-		while (i.hasNext()) {
-			Map.Entry<Integer, Double> e = i.next();
-			distrNew.add(map[e.getKey()], e.getValue());
-		}
-		return distrNew;
+		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public boolean equals(Object o)
-	{
-		Double d1, d2;
-		Distribution d = (Distribution) o;
-		if (d.size() != size())
-			return false;
-		Iterator<Entry<Integer, Double>> i = iterator();
-		while (i.hasNext()) {
-			Map.Entry<Integer, Double> e = i.next();
-			d1 = e.getValue();
-			d2 = d.map.get(e.getKey());
-			if (d2 == null || !PrismUtils.doublesAreClose(d1, d2, 1e-12, false))
-				return false;
-		}
-		return true;
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		Distribution that = (Distribution) o;
+		return Arrays.equals(keys, that.keys) &&
+				PrismUtils.doublesAreClose(values, that.values, 1e-12, false);
 	}
 
 	@Override
 	public int hashCode()
 	{
-		// Simple hash code
-		return map.size();
+		return Arrays.hashCode(keys);
 	}
 
 	@Override
 	public String toString()
 	{
-		return map.toString();
+		return "{" + keys.toString() + " -> " + values.toString() + "}";
+	}
+
+	public void forEach(IntDoubleConsumer consumer) {
+		int i = 0;
+		for (int key : keys) {
+			consumer.accept(key, values[i]);
+			i++;
+		}
+	}
+
+	public void forEach(IntConsumer consumer) {
+		for (int key : keys) {
+			consumer.accept(key);
+		}
+	}
+
+	class DistributionEntry implements Int2DoubleMap.Entry {
+		int index = -1;
+
+		@Override
+        public Integer getKey() {
+            return getIntKey();
+        }
+
+		@Override
+        public int getIntKey() {
+            return keys[index];
+        }
+
+		@Override
+        public Double getValue() {
+            return getDoubleValue();
+        }
+
+		@Override
+        public double setValue(double value) {
+            throw new UnsupportedOperationException();
+        }
+
+		@Override
+        public double getDoubleValue() {
+            return values[index];
+        }
+
+		@Override
+        public Double setValue(Double value) {
+			throw new UnsupportedOperationException();
+        }
 	}
 }
diff --git a/prism/src/explicit/ECComputer.java b/prism/src/explicit/ECComputer.java
index f9ac517..d01e360 100644
--- a/prism/src/explicit/ECComputer.java
+++ b/prism/src/explicit/ECComputer.java
@@ -48,6 +48,15 @@ public abstract class ECComputer extends PrismComponent
 	}
 
 	/**
+	 * Static method to create a new ECComputer object, depending on current settings.
+	 */
+	public static ECComputer createECComputer(NondetModel model) throws PrismException
+	{
+		// Only one algorithm implemented currently
+		return new ECComputerDefault(model);
+	}
+
+	/**
 	 * Base constructor.
 	 */
 	public ECComputer(PrismComponent parent) throws PrismException
@@ -56,6 +65,14 @@ public abstract class ECComputer extends PrismComponent
 	}
 
 	/**
+	 * Base constructor.
+	 */
+	public ECComputer() throws PrismException
+	{
+		super();
+	}
+
+	/**
 	 * Compute states of maximal end components (MECs) and store them.
 	 * They can be retrieved using {@link #getMECStates()}.
 	 */
diff --git a/prism/src/explicit/ECComputerDefault.java b/prism/src/explicit/ECComputerDefault.java
index 020ebbc..ea5d043 100644
--- a/prism/src/explicit/ECComputerDefault.java
+++ b/prism/src/explicit/ECComputerDefault.java
@@ -60,6 +60,15 @@ public class ECComputerDefault extends ECComputer
 		this.model = model;
 	}
 
+	/**
+	 * Build (M)EC computer for a given model.
+	 */
+	public ECComputerDefault(NondetModel model) throws PrismException
+	{
+		super();
+		this.model = model;
+	}
+
 	// Methods for ECComputer interface
 
 	@Override
@@ -113,7 +122,6 @@ public class ECComputerDefault extends ECComputer
 		// Find MECs
 		boolean changed = true;
 		while (changed) {
-			changed = false;
 			BitSet E = L.remove(0);
 			SubNondetModel submodel = restrict(model, E);
 			List<BitSet> sccs = translateStates(submodel, computeSCCs(submodel));
diff --git a/prism/src/explicit/IndexedSet.java b/prism/src/explicit/IndexedSet.java
index 373b36e..65f1dbc 100644
--- a/prism/src/explicit/IndexedSet.java
+++ b/prism/src/explicit/IndexedSet.java
@@ -26,6 +26,10 @@
 
 package explicit;
 
+import it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+
 import java.util.*;
 
 /**
@@ -34,7 +38,7 @@ import java.util.*;
  */
 public class IndexedSet<T> implements StateStorage<T>
 {
-	protected Map<T, Integer> set;
+	protected Object2IntMap<T> set;
 	protected int indexOfLastAdd;
 
 	public IndexedSet()
@@ -45,13 +49,8 @@ public class IndexedSet<T> implements StateStorage<T>
 	public IndexedSet(boolean sorted)
 	{
 		indexOfLastAdd = -1;
-		set = sorted ? new TreeMap<T, Integer>() : new HashMap<T, Integer>();
-	}
-	
-	public IndexedSet(Comparator<T> comparator)
-	{
-		indexOfLastAdd = -1;
-		set = new TreeMap<T, Integer>(comparator);
+		set = sorted ? new Object2IntAVLTreeMap<>() : new Object2IntOpenHashMap<>();
+		set.defaultReturnValue(-1);
 	}
 
 	@Override
@@ -63,8 +62,8 @@ public class IndexedSet<T> implements StateStorage<T>
 	@Override
 	public boolean add(T state)
 	{
-		Integer i = set.get(state);
-		if (i != null) {
+		int i = set.getInt(state);
+		if (i > -1) {
 			indexOfLastAdd = i;
 			return false;
 		} else {
@@ -116,7 +115,7 @@ public class IndexedSet<T> implements StateStorage<T>
 	@Override
 	public ArrayList<T> toArrayList()
 	{
-		ArrayList<T> list = new ArrayList<T>(set.size());
+		ArrayList<T> list = new ArrayList<>(set.size());
 		toArrayList(list);
 		return list;
 	}
@@ -131,10 +130,11 @@ public class IndexedSet<T> implements StateStorage<T>
 		int i, n;
 
 		n = set.size();
-		for (i = 0; i < n ; i++)
+		for (i = 0; i < n ; i++) {
 			list.add(null);
-		for (Map.Entry<T, Integer> e : set.entrySet()) {
-			list.set(e.getValue(), e.getKey());
+		}
+		for (Object2IntMap.Entry<T> e : set.object2IntEntrySet()) {
+			list.set(e.getIntValue(), e.getKey());
 		}
 	}
 	
diff --git a/prism/src/explicit/LTLModelChecker.java b/prism/src/explicit/LTLModelChecker.java
index 11adf61..82ff1af 100644
--- a/prism/src/explicit/LTLModelChecker.java
+++ b/prism/src/explicit/LTLModelChecker.java
@@ -39,6 +39,7 @@ import java.util.Map;
 import java.util.Stack;
 import java.util.Vector;
 
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
 import parser.State;
 import parser.VarList;
 import parser.ast.Declaration;
@@ -483,16 +484,16 @@ public class LTLModelChecker extends PrismComponent
 			// Go through transitions from state s_1 in original model
 			int numChoices = (model instanceof NondetModel) ? ((NondetModel) model).getNumChoices(s_1) : 1;
 			for (int j = 0; j < numChoices; j++) {
-				Iterator<Map.Entry<Integer, Double>> iter;
+				Iterator<Int2DoubleMap.Entry> iter;
 				switch (modelType) {
 				case DTMC:
 					iter = ((DTMC) model).getTransitionsIterator(s_1);
 					break;
 				case MDP:
-					iter = ((MDP) model).getTransitionsIterator(s_1, j);
+					iter = ((MDP) model).getTransitions(s_1, j).iterator();
 					break;
 				case STPG:
-					iter = ((STPG) model).getTransitionsIterator(s_1, j);
+					iter = ((STPG) model).getTransitions(s_1, j).iterator();
 					break;
 				default:
 					throw new PrismNotSupportedException("Product construction not implemented for " + modelType + "s");
diff --git a/prism/src/explicit/LTSExplicit.java b/prism/src/explicit/LTSExplicit.java
index aa6da40..5c6f888 100644
--- a/prism/src/explicit/LTSExplicit.java
+++ b/prism/src/explicit/LTSExplicit.java
@@ -27,12 +27,12 @@
 
 package explicit;
 
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
+import java.util.*;
 
+import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntIterators;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
 import prism.ModelType;
 import prism.PrismException;
 import prism.PrismLog;
@@ -45,7 +45,7 @@ import strat.MDStrategy;
  */
 public class LTSExplicit extends ModelExplicit implements LTS
 {
-	protected ArrayList<ArrayList<Integer>> successors = new ArrayList<ArrayList<Integer>>();
+	protected ArrayList<ArrayList<Integer>> successors = new ArrayList<>();
 	protected int numTransitions = 0;
 	protected int maxNumChoices = 0;
 
@@ -131,10 +131,10 @@ public class LTSExplicit extends ModelExplicit implements LTS
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(int s, int i)
+	public IntIterator getSuccessorsIterator(int s, int i)
 	{
 		// single successor for s, i
-		return Collections.singletonList(successors.get(s).get(i)).iterator();
+		return IntIterators.singleton(successors.get(s).get(i));
 	}
 
 	@Override
@@ -150,12 +150,10 @@ public class LTSExplicit extends ModelExplicit implements LTS
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(int s)
+	public IntIterator getSuccessorsIterator(int s)
 	{
-		// make successors unique
-		LinkedHashSet<Integer> succ = new LinkedHashSet<Integer>();
-		succ.addAll(successors.get(s));
-		return succ.iterator();
+		IntSet set = new IntOpenHashSet(successors.get(s));
+		return set.iterator();
 	}
 
 	@Override
diff --git a/prism/src/explicit/MDP.java b/prism/src/explicit/MDP.java
index 0b5e2ee..c1c87be 100644
--- a/prism/src/explicit/MDP.java
+++ b/prism/src/explicit/MDP.java
@@ -27,12 +27,13 @@
 package explicit;
 
 import java.util.BitSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map.Entry;
+import java.util.function.IntConsumer;
 
+import explicit.nxt.IntDoubleConsumer;
 import explicit.rewards.MCRewards;
 import explicit.rewards.MDPRewards;
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
 
 /**
  * Interface for classes that provide (read) access to an explicit-state MDP.
@@ -42,7 +43,15 @@ public interface MDP extends NondetModel
 	/**
 	 * Get an iterator over the transitions from choice {@code i} of state {@code s}.
 	 */
-	public Iterator<Entry<Integer, Double>> getTransitionsIterator(int s, int i);
+	Iterable<Int2DoubleMap.Entry> getTransitions(int s, int i);
+
+	default void forEachTransition(int s, int i, IntDoubleConsumer consumer) {
+		throw new UnsupportedOperationException();
+	}
+
+	default void forEachTransition(int s, int i, IntConsumer consumer) {
+		throw new UnsupportedOperationException();
+	}
 
 	/**
 	 * Perform a single step of precomputation algorithm Prob0, i.e., for states i in {@code subset},
@@ -150,16 +159,15 @@ public interface MDP extends NondetModel
 	 * and store new values directly in {@code vect} as computed.
 	 * The maximum (absolute/relative) difference between old/new
 	 * elements of {@code vect} is also returned.
-	 * Optionally, store optimal (memoryless) strategy info. 
+	 * Optionally, store optimal (memoryless) strategy info.
 	 * @param vect Vector to multiply by (and store the result in)
 	 * @param min Min or max for (true=min, false=max)
 	 * @param subset Only do multiplication for these rows (ignored if null)
-	 * @param complement If true, {@code subset} is taken to be its complement (ignored if {@code subset} is null)
 	 * @param absolute If true, compute absolute, rather than relative, difference
 	 * @param strat Storage for (memoryless) strategy choice indices (ignored if null)
 	 * @return The maximum difference between old/new elements of {@code vect}
 	 */
-	public double mvMultGSMinMax(double vect[], boolean min, BitSet subset, boolean complement, boolean absolute, int strat[]);
+	public double mvMultGSMinMax(double vect[], boolean min, BitSet subset, boolean absolute, int strat[]);
 
 	/**
 	 * Do a single row of Jacobi-style matrix-vector multiplication followed by min/max.
diff --git a/prism/src/explicit/MDPExplicit.java b/prism/src/explicit/MDPExplicit.java
index 53a5276..9955f0f 100644
--- a/prism/src/explicit/MDPExplicit.java
+++ b/prism/src/explicit/MDPExplicit.java
@@ -29,13 +29,9 @@ package explicit;
 
 import java.io.FileWriter;
 import java.io.IOException;
-import java.util.BitSet;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.TreeMap;
+import java.util.*;
 
-import common.IterableStateSet;
+import common.IterableBitSet;
 
 import prism.ModelType;
 import prism.PrismException;
@@ -92,11 +88,8 @@ public abstract class MDPExplicit extends ModelExplicit implements MDP
 			numChoices = getNumChoices(i);
 			for (j = 0; j < numChoices; j++) {
 				// Extract transitions and sort by destination state index (to match PRISM-exported files)
-				Iterator<Map.Entry<Integer, Double>> iter = getTransitionsIterator(i, j);
-				while (iter.hasNext()) {
-					Map.Entry<Integer, Double> e = iter.next();
-					sorted.put(e.getKey(), e.getValue());
-				}
+				getTransitions(i, j).forEach(e -> sorted.put(e.getIntKey(), e.getDoubleValue()));
+
 				// Print out (sorted) transitions
 				for (Map.Entry<Integer, Double> e : sorted.entrySet()) {
 					// Note use of PrismUtils.formatDouble to match PRISM-exported files
@@ -124,9 +117,7 @@ public abstract class MDPExplicit extends ModelExplicit implements MDP
 				out.print(":" + action);
 			out.print("\" ];\n");
 			out.print(nij + " [ shape=point,width=0.1,height=0.1,label=\"\" ];\n");
-			Iterator<Map.Entry<Integer, Double>> iter = getTransitionsIterator(i, j);
-			while (iter.hasNext()) {
-				Map.Entry<Integer, Double> e = iter.next();
+			for (Map.Entry<Integer, Double> e : getTransitions(i, j)) {
 				out.print(nij + " -> " + e.getKey() + " [ label=\"" + e.getValue() + "\" ];\n");
 			}
 		}
@@ -153,10 +144,8 @@ public abstract class MDPExplicit extends ModelExplicit implements MDP
 					out.print(":" + action);
 				out.print("\"" + style + " ];\n");
 				out.print(nij + " [ shape=point,height=0.1,label=\"\"" + style + " ];\n");
-				Iterator<Map.Entry<Integer, Double>> iter = getTransitionsIterator(i, j);
-				while (iter.hasNext()) {
-					Map.Entry<Integer, Double> e = iter.next();
-					out.print(nij + " -> " + e.getKey() + " [ label=\"" + e.getValue() + "\"" + style + " ];\n");
+				for (Map.Entry<Integer, Double> e : getTransitions(i, j)) {
+					out.print(nij + " -> " + e.getKey() + " [ label=\"" + e.getValue() + "\" ];\n");
 				}
 			}
 		}
@@ -181,9 +170,7 @@ public abstract class MDPExplicit extends ModelExplicit implements MDP
 				numChoices = getNumChoices(i);
 				for (j = 0; j < numChoices; j++) {
 					// Extract transitions and sort by destination state index (to match PRISM-exported files)
-					Iterator<Map.Entry<Integer, Double>> iter = getTransitionsIterator(i, j);
-					while (iter.hasNext()) {
-						Map.Entry<Integer, Double> e = iter.next();
+					for (Map.Entry<Integer, Double> e : getTransitions(i, j)) {
 						sorted.put(e.getKey(), e.getValue());
 					}
 					// Print out (sorted) transitions
@@ -235,37 +222,42 @@ public abstract class MDPExplicit extends ModelExplicit implements MDP
 	@Override
 	public void mvMultMinMax(double vect[], boolean min, double result[], BitSet subset, boolean complement, int strat[])
 	{
-		for (int s : new IterableStateSet(subset, numStates, complement)) {
-			result[s] = mvMultMinMaxSingle(s, vect, min, strat);
+		if (complement) {
+			PrimitiveIterator.OfInt iterator = new IterableBitSet(subset, numStates-1, complement).iterator();
+
+			while (iterator.hasNext()) {
+				int s = iterator.nextInt();
+				result[s] = mvMultMinMaxSingle(s, vect, min, strat);
+			}
+		} else {
+			for (int s = subset.nextSetBit(0); s >= 0; s = subset.nextSetBit(s+1)) {
+				result[s] = mvMultMinMaxSingle(s, vect, min, strat);
+			}
 		}
 	}
 
 	@Override
-	public double mvMultGSMinMax(double vect[], boolean min, BitSet subset, boolean complement, boolean absolute, int strat[])
+	public double mvMultGSMinMax(double vect[], boolean min, BitSet subset, boolean absolute, int strat[])
 	{
 		double d, diff, maxDiff = 0.0;
-		for (int s : new IterableStateSet(subset, numStates, complement)) {
+
+		for (int s = subset.nextSetBit(0); s >= 0; s = subset.nextSetBit(s+1)) {
 			d = mvMultJacMinMaxSingle(s, vect, min, strat);
 			diff = absolute ? (Math.abs(d - vect[s])) : (Math.abs(d - vect[s]) / d);
 			maxDiff = diff > maxDiff ? diff : maxDiff;
 			vect[s] = d;
 		}
-		// Use this code instead for backwards Gauss-Seidel
-		/*for (s = numStates - 1; s >= 0; s--) {
-			if (subset.get(s)) {
-				d = mvMultJacMinMaxSingle(s, vect, min, strat);
-				diff = absolute ? (Math.abs(d - vect[s])) : (Math.abs(d - vect[s]) / d);
-				maxDiff = diff > maxDiff ? diff : maxDiff;
-				vect[s] = d;
-			}
-		}*/
+
 		return maxDiff;
 	}
 
 	@Override
 	public void mvMultRewMinMax(double vect[], MDPRewards mdpRewards, boolean min, double result[], BitSet subset, boolean complement, int strat[])
 	{
-		for (int s : new IterableStateSet(subset, numStates, complement)) {
+		PrimitiveIterator.OfInt iterator = new IterableBitSet(subset, numStates-1, complement).iterator();
+
+		while (iterator.hasNext()) {
+			int s = iterator.nextInt();
 			result[s] = mvMultRewMinMaxSingle(s, vect, mdpRewards, min, strat);
 		}
 	}
@@ -274,7 +266,10 @@ public abstract class MDPExplicit extends ModelExplicit implements MDP
 	public double mvMultRewGSMinMax(double vect[], MDPRewards mdpRewards, boolean min, BitSet subset, boolean complement, boolean absolute, int strat[])
 	{
 		double d, diff, maxDiff = 0.0;
-		for (int s : new IterableStateSet(subset, numStates, complement)) {
+		PrimitiveIterator.OfInt iterator = new IterableBitSet(subset, numStates-1, complement).iterator();
+
+		while (iterator.hasNext()) {
+			int s = iterator.nextInt();
 			d = mvMultRewJacMinMaxSingle(s, vect, mdpRewards, min, strat);
 			diff = absolute ? (Math.abs(d - vect[s])) : (Math.abs(d - vect[s]) / d);
 			maxDiff = diff > maxDiff ? diff : maxDiff;
diff --git a/prism/src/explicit/MDPModelChecker.java b/prism/src/explicit/MDPModelChecker.java
index 7c5b50b..af04748 100644
--- a/prism/src/explicit/MDPModelChecker.java
+++ b/prism/src/explicit/MDPModelChecker.java
@@ -28,7 +28,6 @@ package explicit;
 
 import java.util.Arrays;
 import java.util.BitSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -245,7 +244,7 @@ public class MDPModelChecker extends ProbModelChecker
 	 *
 	 * Clears the StateValues object x.
 	 *
-	 * @param tr the transition matrix
+	 * @param mdp the transition matrix
 	 * @param a the set of states labeled with a
 	 * @param x the value vector
 	 * @param min compute min instead of max
@@ -810,7 +809,7 @@ public class MDPModelChecker extends ProbModelChecker
 		while (!done && iters < maxIters) {
 			iters++;
 			// Matrix-vector multiply
-			maxDiff = mdp.mvMultGSMinMax(soln, min, unknown, false, termCrit == TermCrit.ABSOLUTE, strat);
+			maxDiff = mdp.mvMultGSMinMax(soln, min, unknown, termCrit == TermCrit.ABSOLUTE, strat);
 			// Check termination
 			done = maxDiff < termCritParam;
 		}
@@ -1700,9 +1699,7 @@ public class MDPModelChecker extends ProbModelChecker
 			for (int s = explore.nextSetBit(0); s >= 0; s = explore.nextSetBit(s + 1)) {
 				explore.set(s, false);
 				if (strat[s] >= 0) {
-					Iterator<Map.Entry<Integer, Double>> iter = mdp.getTransitionsIterator(s, strat[s]);
-					while (iter.hasNext()) {
-						Map.Entry<Integer, Double> e = iter.next();
+					for (Map.Entry<Integer, Double> e : mdp.getTransitions(s, strat[s])) {
 						int dest = e.getKey();
 						if (!restrict.get(dest)) {
 							foundMore = true;
diff --git a/prism/src/explicit/MDPSimple.java b/prism/src/explicit/MDPSimple.java
index 0fb287a..5d08e51 100644
--- a/prism/src/explicit/MDPSimple.java
+++ b/prism/src/explicit/MDPSimple.java
@@ -31,16 +31,14 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.Arrays;
 import java.util.Map.Entry;
+import java.util.function.IntConsumer;
 
-import common.IterableStateSet;
-
+import explicit.nxt.IntDoubleConsumer;
+import explicit.nxt.SparseBitSet;
+import it.unimi.dsi.fastutil.ints.*;
 import prism.PrismException;
 import prism.PrismUtils;
 import explicit.rewards.MCRewards;
@@ -55,11 +53,11 @@ import explicit.rewards.MDPRewards;
 public class MDPSimple extends MDPExplicit implements NondetModelSimple
 {
 	// Transition function (Steps)
-	protected List<List<Distribution>> trans;
+	public List<Distribution[]> trans;
 
 	// Action labels
 	// (null list means no actions; null in element s means no actions for state s)
-	protected List<List<Object>> actions;
+	public List<List<Object>> actions;
 
 	// Flag: allow duplicates in distribution sets?
 	protected boolean allowDupes = false;
@@ -97,18 +95,18 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 		copyFrom(mdp);
 		// Copy storage directly to avoid worrying about duplicate distributions (and for efficiency) 
 		for (int s = 0; s < numStates; s++) {
-			List<Distribution> distrs = trans.get(s);
+			Distribution[] distrs = trans.get(s);
 			for (Distribution distr : mdp.trans.get(s)) {
-				distrs.add(new Distribution(distr));
+				throw new UnsupportedOperationException();
 			}
 		}
 		if (mdp.actions != null) {
-			actions = new ArrayList<List<Object>>(numStates);
+			actions = new ArrayList<>(numStates);
 			for (int s = 0; s < numStates; s++)
 				actions.add(null);
 			for (int s = 0; s < numStates; s++) {
 				if (mdp.actions.get(s) != null) {
-					int n = mdp.trans.get(s).size();
+					int n = mdp.trans.get(s).length;
 					List<Object> list = new ArrayList<Object>(n);
 					for (int i = 0; i < n; i++) {
 						list.add(mdp.actions.get(s).get(i));
@@ -151,19 +149,16 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 		// Copy storage directly to avoid worrying about duplicate distributions (and for efficiency)
 		// (Since permut is a bijection, all structures and statistics are identical)
 		for (int s = 0; s < numStates; s++) {
-			List<Distribution> distrs = trans.get(permut[s]);
-			for (Distribution distr : mdp.trans.get(s)) {
-				distrs.add(new Distribution(distr, permut));
-			}
+			Distribution[] distrs = trans.set(permut[s], mdp.trans.get(s).clone());
 		}
 		if (mdp.actions != null) {
-			actions = new ArrayList<List<Object>>(numStates);
+			actions = new ArrayList<>(numStates);
 			for (int s = 0; s < numStates; s++)
 				actions.add(null);
 			for (int s = 0; s < numStates; s++) {
 				if (mdp.actions.get(s) != null) {
-					int n = mdp.trans.get(s).size();
-					List<Object> list = new ArrayList<Object>(n);
+					int n = mdp.trans.get(s).length;
+					List<Object> list = new ArrayList<>(n);
 					for (int i = 0; i < n; i++) {
 						list.add(mdp.actions.get(s).get(i));
 					}
@@ -190,11 +185,11 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 		for (int s = 0; s < numStates; s++) {
 			for (int c = 0; c < mdp.getNumChoices(s); c++) {
 				Distribution distr = new Distribution();
-				Iterator<Entry<Integer, Double>> it = mdp.getTransitionsIterator(s, c);
-				while (it.hasNext()) {
-					Entry<Integer, Double> entry = it.next();
+
+				for (Entry<Integer, Double> entry : mdp.getTransitions(s, c)) {
 					distr.add(entry.getKey(), entry.getValue());
 				}
+
 				this.addChoice(s, distr);
 			}
 		}
@@ -228,9 +223,9 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 		super.initialise(numStates);
 		numDistrs = numTransitions = maxNumDistrs = 0;
 		maxNumDistrsOk = true;
-		trans = new ArrayList<List<Distribution>>(numStates);
+		trans = new ArrayList<>(numStates);
 		for (int i = 0; i < numStates; i++) {
-			trans.add(new ArrayList<Distribution>());
+			trans.add(null);
 		}
 		actions = null;
 	}
@@ -242,13 +237,13 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 		if (s >= numStates || s < 0)
 			return;
 		// Clear data structures and update stats
-		List<Distribution> list = trans.get(s);
-		numDistrs -= list.size();
+		Distribution[] list = trans.get(s);
+		numDistrs -= list.length;
 		for (Distribution distr : list) {
 			numTransitions -= distr.size();
 		}
 		maxNumDistrsOk = false;
-		trans.get(s).clear();
+		trans.set(s, null);
 		if (actions != null && actions.get(s) != null)
 			actions.get(s).clear();
 	}
@@ -264,7 +259,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	public void addStates(int numToAdd)
 	{
 		for (int i = 0; i < numToAdd; i++) {
-			trans.add(new ArrayList<Distribution>());
+			trans.add(null);
 			if (actions != null)
 				actions.add(null);
 			numStates++;
@@ -350,26 +345,33 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	 */
 	public int addChoice(int s, Distribution distr)
 	{
-		List<Distribution> set;
-		// Check state exists
-		if (s >= numStates || s < 0)
-			return -1;
-		// Add distribution (if new)
-		if (!allowDupes) {
-			int i = indexOfChoice(s, distr);
-			if (i != -1)
-				return i;
+		Distribution[] oldSet = trans.get(s);
+		Distribution[] set;
+
+		if (oldSet != null) {
+			// Add distribution (if new)
+			if (!allowDupes) {
+				int i = Arrays.asList().indexOf(distr);
+				if (i != -1)
+					return i;
+			}
+
+			set = Arrays.copyOf(oldSet, oldSet.length + 1);
+		} else {
+			set = new Distribution[1];
 		}
-		set = trans.get(s);
-		set.add(distr);
+
+		trans.set(s, set);
+
+		set[set.length - 1] = (distr);
 		// Add null action if necessary
 		if (actions != null && actions.get(s) != null)
 			actions.get(s).add(null);
 		// Update stats
 		numDistrs++;
-		maxNumDistrs = Math.max(maxNumDistrs, set.size());
+		maxNumDistrs = Math.max(maxNumDistrs, set.length);
 		numTransitions += distr.size();
-		return set.size() - 1;
+		return set.length - 1;
 	}
 
 	/**
@@ -381,28 +383,10 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	 */
 	public int addActionLabelledChoice(int s, Distribution distr, Object action)
 	{
-		List<Distribution> set;
-		// Check state exists
-		if (s >= numStates || s < 0)
-			return -1;
-		// Add distribution/action (if new)
-		if (!allowDupes) {
-			int i = indexOfActionLabelledChoice(s, distr, action);
-			if (i != -1)
-				return i;
-		}
-		set = trans.get(s);
-		set.add(distr);
-		// Add null action if necessary
-		if (actions != null && actions.get(s) != null)
-			actions.get(s).add(null);
-		// Set action
-		setAction(s, set.size() - 1, action);
-		// Update stats
-		numDistrs++;
-		maxNumDistrs = Math.max(maxNumDistrs, set.size());
-		numTransitions += distr.size();
-		return set.size() - 1;
+		addChoice(s, distr);
+		Distribution[] set = trans.get(s);
+		setAction(s, set.length - 1, action);
+		return set.length - 1;
 	}
 
 	/**
@@ -418,14 +402,14 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 			return;
 		// If no actions array created yet, create it
 		if (actions == null) {
-			actions = new ArrayList<List<Object>>(numStates);
+			actions = new ArrayList<>(numStates);
 			for (int j = 0; j < numStates; j++)
 				actions.add(null);
 		}
 		// If no actions for state i yet, create list
 		if (actions.get(s) == null) {
-			int n = trans.get(s).size();
-			List<Object> list = new ArrayList<Object>(n);
+			int n = trans.get(s).length;
+			List<Object> list = new ArrayList<>(n);
 			for (int j = 0; j < n; j++) {
 				list.add(null);
 			}
@@ -444,15 +428,9 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(final int s)
+	public IntIterator getSuccessorsIterator(final int s)
 	{
-		// Need to build set to avoid duplicates
-		// So not necessarily the fastest method to access successors
-		HashSet<Integer> succs = new HashSet<Integer>();
-		for (Distribution distr : trans.get(s)) {
-			succs.addAll(distr.getSupport());
-		}
-		return succs.iterator();
+		throw new UnsupportedOperationException();
 	}
 
 	@Override
@@ -462,6 +440,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 			if (distr.contains(s2))
 				return true;
 		}
+
 		return false;
 	}
 
@@ -469,9 +448,11 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	public boolean allSuccessorsInSet(int s, BitSet set)
 	{
 		for (Distribution distr : trans.get(s)) {
-			if (!distr.isSubsetOf(set))
+			if (!distr.isSubsetOf(set)) {
 				return false;
+			}
 		}
+
 		return true;
 	}
 
@@ -479,9 +460,11 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	public boolean someSuccessorsInSet(int s, BitSet set)
 	{
 		for (Distribution distr : trans.get(s)) {
-			if (distr.containsOneOf(set))
+			if (distr.containsOneOf(set)) {
 				return true;
+			}
 		}
+
 		return false;
 	}
 
@@ -490,7 +473,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	{
 		for (int i = 0; i < numStates; i++) {
 			// Note that no distributions is a deadlock, not an empty distribution
-			if (trans.get(i).isEmpty()) {
+			if (trans.get(i).length == 0) {
 				addDeadlockState(i);
 				if (fix) {
 					Distribution distr = new Distribution();
@@ -505,7 +488,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	public void checkForDeadlocks(BitSet except) throws PrismException
 	{
 		for (int i = 0; i < numStates; i++) {
-			if (trans.get(i).isEmpty() && (except == null || !except.get(i)))
+			if (trans.get(i).length == 0 && (except == null || !except.get(i)))
 				throw new PrismException("MDP has a deadlock in state " + i);
 		}
 	}
@@ -515,7 +498,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	@Override
 	public int getNumChoices(int s)
 	{
-		return trans.get(s).size();
+		return trans.get(s).length;
 	}
 
 	@Override
@@ -548,19 +531,25 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	@Override
 	public boolean allSuccessorsInSet(int s, int i, BitSet set)
 	{
-		return trans.get(s).get(i).isSubsetOf(set);
+		return trans.get(s)[i].isSubsetOf(set);
+	}
+
+	@Override
+	public boolean allSuccessorsInSet(int s, int i, SparseBitSet set)
+	{
+		return trans.get(s)[i].isSubsetOf(set);
 	}
 
 	@Override
 	public boolean someSuccessorsInSet(int s, int i, BitSet set)
 	{
-		return trans.get(s).get(i).containsOneOf(set);
+		return trans.get(s)[i].containsOneOf(set);
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(final int s, final int i)
+	public IntIterator getSuccessorsIterator(int s, int i)
 	{
-		return trans.get(s).get(i).getSupport().iterator();
+		return IntIterators.wrap(trans.get(s)[i].getSupport());
 	}
 
 	// Accessors (for MDP)
@@ -568,23 +557,32 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	@Override
 	public int getNumTransitions(int s, int i)
 	{
-		return trans.get(s).get(i).size();
+		return trans.get(s)[i].size();
 	}
 
 	@Override
-	public Iterator<Entry<Integer, Double>> getTransitionsIterator(int s, int i)
+	public Iterable<Int2DoubleMap.Entry> getTransitions(int s, int i)
 	{
-		return trans.get(s).get(i).iterator();
+		return trans.get(s)[i];
+	}
+
+	@Override
+	public void forEachTransition(int s, int i, IntDoubleConsumer consumer) {
+		trans.get(s)[i].forEach(consumer);
+	}
+
+	@Override
+	public void forEachTransition(int s, int i, IntConsumer consumer) {
+		trans.get(s)[i].forEach(consumer);
 	}
 
 	@Override
 	public void prob0step(BitSet subset, BitSet u, boolean forall, BitSet result)
 	{
-		boolean b1, b2;
-		for (int i : new IterableStateSet(subset, numStates)) {
-			b1 = forall; // there exists or for all
+		for (int i = subset.nextSetBit(0); i >= 0; i = subset.nextSetBit(i+1)) {
+			boolean b1 = forall; // there exists or for all
 			for (Distribution distr : trans.get(i)) {
-				b2 = distr.containsOneOf(u);
+				boolean b2 = distr.containsOneOf(u);
 				if (forall) {
 					if (!b2) {
 						b1 = false;
@@ -605,7 +603,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	public void prob1Astep(BitSet subset, BitSet u, BitSet v, BitSet result)
 	{
 		boolean b1;
-		for (int i : new IterableStateSet(subset, numStates)) {
+		for (int i = subset.nextSetBit(0); i >= 0; i = subset.nextSetBit(i+1)) {
 			b1 = true;
 			for (Distribution distr : trans.get(i)) {
 				if (!(distr.isSubsetOf(u) && distr.containsOneOf(v))) {
@@ -622,7 +620,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	{
 		int j, stratCh = -1;
 		boolean b1;
-		for (int i : new IterableStateSet(subset, numStates)) {
+		for (int i = subset.nextSetBit(0); i >= 0; i = subset.nextSetBit(i+1)) {
 			j = 0;
 			b1 = false;
 			for (Distribution distr : trans.get(i)) {
@@ -649,7 +647,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	public void prob1step(BitSet subset, BitSet u, BitSet v, boolean forall, BitSet result)
 	{
 		boolean b1, b2;
-		for (int i : new IterableStateSet(subset, numStates)) {
+		for (int i = subset.nextSetBit(0); i >= 0; i = subset.nextSetBit(i+1)) {
 			b1 = forall; // there exists or for all
 			for (Distribution distr : trans.get(i)) {
 				b2 = distr.containsOneOf(v) && distr.isSubsetOf(u);
@@ -672,30 +670,24 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	@Override
 	public boolean prob1stepSingle(int s, int i, BitSet u, BitSet v)
 	{
-		Distribution distr = trans.get(s).get(i);
+		Distribution distr = trans.get(s)[i];
 		return distr.containsOneOf(v) && distr.isSubsetOf(u);
 	}
 
 	@Override
 	public double mvMultMinMaxSingle(int s, double vect[], boolean min, int strat[])
 	{
-		int j, k, stratCh = -1;
-		double d, prob, minmax;
+		int j, stratCh = -1;
+		double minmax;
 		boolean first;
-		List<Distribution> step;
 
 		j = 0;
 		minmax = 0;
 		first = true;
-		step = trans.get(s);
-		for (Distribution distr : step) {
+		for (Distribution distr : trans.get(s)) {
 			// Compute sum for this distribution
-			d = 0.0;
-			for (Map.Entry<Integer, Double> e : distr) {
-				k = (Integer) e.getKey();
-				prob = (Double) e.getValue();
-				d += prob * vect[k];
-			}
+			double d = distr.sumWeighted(vect);
+
 			// Check whether we have exceeded min/max so far
 			if (first || (min && d < minmax) || (!min && d > minmax)) {
 				minmax = d;
@@ -722,25 +714,19 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	@Override
 	public List<Integer> mvMultMinMaxSingleChoices(int s, double vect[], boolean min, double val)
 	{
-		int j, k;
-		double d, prob;
+		int j;
+		double d;
 		List<Integer> res;
-		List<Distribution> step;
 
 		// Create data structures to store strategy
 		res = new ArrayList<Integer>();
 		// One row of matrix-vector operation 
 		j = -1;
-		step = trans.get(s);
-		for (Distribution distr : step) {
+		for (Distribution distr : trans.get(s)) {
 			j++;
 			// Compute sum for this distribution
-			d = 0.0;
-			for (Map.Entry<Integer, Double> e : distr) {
-				k = (Integer) e.getKey();
-				prob = (Double) e.getValue();
-				d += prob * vect[k];
-			}
+			d = distr.sumWeighted(vect);
+
 			// Store strategy info if value matches
 			//if (PrismUtils.doublesAreClose(val, d, termCritParam, termCrit == TermCrit.ABSOLUTE)) {
 			if (PrismUtils.doublesAreClose(val, d, 1e-12, false)) {
@@ -755,19 +741,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	@Override
 	public double mvMultSingle(int s, int i, double vect[])
 	{
-		double d, prob;
-		int k;
-
-		Distribution distr = trans.get(s).get(i);
-		// Compute sum for this distribution
-		d = 0.0;
-		for (Map.Entry<Integer, Double> e : distr) {
-			k = (Integer) e.getKey();
-			prob = (Double) e.getValue();
-			d += prob * vect[k];
-		}
-
-		return d;
+		return trans.get(s)[i].sumWeighted(vect);
 	}
 
 	@Override
@@ -776,13 +750,11 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 		int j, k, stratCh = -1;
 		double diag, d, prob, minmax;
 		boolean first;
-		List<Distribution> step;
 
 		j = 0;
 		minmax = 0;
 		first = true;
-		step = trans.get(s);
-		for (Distribution distr : step) {
+		for (Distribution distr : trans.get(s)) {
 			diag = 1.0;
 			// Compute sum for this distribution
 			d = 0.0;
@@ -824,27 +796,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	@Override
 	public double mvMultJacSingle(int s, int i, double vect[])
 	{
-		double diag, d, prob;
-		int k;
-		Distribution distr;
-
-		distr = trans.get(s).get(i);
-		diag = 1.0;
-		// Compute sum for this distribution
-		d = 0.0;
-		for (Map.Entry<Integer, Double> e : distr) {
-			k = (Integer) e.getKey();
-			prob = (Double) e.getValue();
-			if (k != s) {
-				d += prob * vect[k];
-			} else {
-				diag -= prob;
-			}
-		}
-		if (diag > 0)
-			d /= diag;
-
-		return d;
+		return trans.get(s)[i].sumWeighted(vect);
 	}
 
 	@Override
@@ -853,21 +805,15 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 		int j, k, stratCh = -1;
 		double d, prob, minmax;
 		boolean first;
-		List<Distribution> step;
 
 		minmax = 0;
 		first = true;
 		j = -1;
-		step = trans.get(s);
-		for (Distribution distr : step) {
+		for (Distribution distr : trans.get(s)) {
 			j++;
 			// Compute sum for this distribution
-			d = mdpRewards.getTransitionReward(s, j);
-			for (Map.Entry<Integer, Double> e : distr) {
-				k = (Integer) e.getKey();
-				prob = (Double) e.getValue();
-				d += prob * vect[k];
-			}
+			d = mdpRewards.getTransitionReward(s, j) + distr.sumWeighted(vect);
+
 			// Check whether we have exceeded min/max so far
 			if (first || (min && d < minmax) || (!min && d > minmax)) {
 				minmax = d;
@@ -895,22 +841,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	@Override
 	public double mvMultRewSingle(int s, int i, double[] vect, MCRewards mcRewards)
 	{
-		double d, prob;
-		int k;
-
-		Distribution distr = trans.get(s).get(i);
-		// Compute sum for this distribution
-		// TODO: use transition rewards when added to DTMCss
-		// d = mcRewards.getTransitionReward(s);
-		d = 0;
-		for (Map.Entry<Integer, Double> e : distr) {
-			k = (Integer) e.getKey();
-			prob = (Double) e.getValue();
-			d += prob * vect[k];
-		}
-		d += mcRewards.getStateReward(s);
-		
-		return d;
+		return mcRewards.getStateReward(s) + trans.get(s)[i].sumWeighted(vect);
 	}
 	
 	@Override
@@ -919,20 +850,18 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 		int j, k, stratCh = -1;
 		double diag, d, prob, minmax;
 		boolean first;
-		List<Distribution> step;
 
 		minmax = 0;
 		first = true;
 		j = -1;
-		step = trans.get(s);
-		for (Distribution distr : step) {
+		for (Distribution distr : trans.get(s)) {
 			j++;
 			diag = 1.0;
 			// Compute sum for this distribution
 			d = mdpRewards.getTransitionReward(s, j);
-			for (Map.Entry<Integer, Double> e : distr) {
-				k = (Integer) e.getKey();
-				prob = (Double) e.getValue();
+			for (Int2DoubleMap.Entry e : distr) {
+				k = e.getIntKey();
+				prob = e.getDoubleValue();
 				if (k != s) {
 					d += prob * vect[k];
 				} else {
@@ -969,24 +898,20 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	@Override
 	public List<Integer> mvMultRewMinMaxSingleChoices(int s, double vect[], MDPRewards mdpRewards, boolean min, double val)
 	{
-		int j, k;
-		double d, prob;
+		int j;
+		double d;
 		List<Integer> res;
-		List<Distribution> step;
 
 		// Create data structures to store strategy
-		res = new ArrayList<Integer>();
+		res = new ArrayList<>();
 		// One row of matrix-vector operation 
 		j = -1;
-		step = trans.get(s);
-		for (Distribution distr : step) {
+		for (Distribution distr : trans.get(s)) {
 			j++;
 			// Compute sum for this distribution
 			d = mdpRewards.getTransitionReward(s, j);
-			for (Map.Entry<Integer, Double> e : distr) {
-				k = (Integer) e.getKey();
-				prob = (Double) e.getValue();
-				d += prob * vect[k];
+			for (Int2DoubleMap.Entry e : distr) {
+				d += e.getDoubleValue() * vect[e.getIntKey()];
 			}
 			d += mdpRewards.getStateReward(s);
 			// Store strategy info if value matches
@@ -1004,12 +929,8 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	public void mvMultRight(int[] states, int[] strat, double[] source, double[] dest)
 	{
 		for (int s : states) {
-			Iterator<Entry<Integer, Double>> it = this.getTransitionsIterator(s, strat[s]);
-			while (it.hasNext()) {
-				Entry<Integer, Double> next = it.next();
-				int col = next.getKey();
-				double prob = next.getValue();
-				dest[col] += prob * source[s];
+			for (Int2DoubleMap.Entry next : getTransitions(s, strat[s])) {
+				dest[next.getIntKey()] += next.getDoubleValue() * source[s];
 			}
 		}
 	}
@@ -1019,7 +940,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	/**
 	 * Get the list of choices (distributions) for state s.
 	 */
-	public List<Distribution> getChoices(int s)
+	public Distribution[] getChoices(int s)
 	{
 		return trans.get(s);
 	}
@@ -1029,16 +950,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	 */
 	public Distribution getChoice(int s, int i)
 	{
-		return trans.get(s).get(i);
-	}
-
-	/**
-	 * Returns the index of the choice {@code distr} for state {@code s}, if it exists.
-	 * If none, -1 is returned. If there are multiple (i.e. allowDupes is true), the first is returned. 
-	 */
-	public int indexOfChoice(int s, Distribution distr)
-	{
-		return trans.get(s).indexOf(distr);
+		return trans.get(s)[i];
 	}
 
 	/**
@@ -1047,11 +959,11 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 	 */
 	public int indexOfActionLabelledChoice(int s, Distribution distr, Object action)
 	{
-		List<Distribution> set = trans.get(s);
-		int i, n = set.size();
+		Distribution[] set = trans.get(s);
+		int i, n = set.length;
 		if (distr == null) {
 			for (i = 0; i < n; i++) {
-				if (set.get(i) == null) {
+				if (set[i] == null) {
 					Object a = getAction(s, i);
 					if (action == null) {
 						if (a == null)
@@ -1064,7 +976,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 			}
 		} else {
 			for (i = 0; i < n; i++) {
-				if (distr.equals(set.get(i))) {
+				if (distr.equals(set[i])) {
 					Object a = getAction(s, i);
 					if (action == null) {
 						if (a == null)
@@ -1100,7 +1012,7 @@ public class MDPSimple extends MDPExplicit implements NondetModelSimple
 				o = getAction(i, j);
 				if (o != null)
 					s += o + ":";
-				s += trans.get(i).get(j);
+				s += trans.get(i)[j];
 			}
 			s += "]";
 		}
diff --git a/prism/src/explicit/MDPSparse.java b/prism/src/explicit/MDPSparse.java
index 278a323..65cdb5e 100644
--- a/prism/src/explicit/MDPSparse.java
+++ b/prism/src/explicit/MDPSparse.java
@@ -31,17 +31,13 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.Map.Entry;
-import java.util.TreeMap;
-
-import common.IterableStateSet;
+import java.util.function.IntConsumer;
 
+import explicit.nxt.IntDoubleConsumer;
+import explicit.nxt.SparseBitSet;
+import it.unimi.dsi.fastutil.ints.*;
 import parser.State;
 import prism.PrismException;
 import prism.PrismUtils;
@@ -82,31 +78,15 @@ public class MDPSparse extends MDPExplicit
 	/**
 	 * Copy constructor (from MDPSimple).
 	 */
-	public MDPSparse(MDPSimple mdp)
-	{
-		this(mdp, false);
-	}
-
-	/**
-	 * Copy constructor (from MDPSimple). Optionally, transitions within choices
-	 * are sorted (by ascending order of column index).
-	 * @param mdp The MDP to copy
-	 * @param sort Whether or not to sort column indices
-	 */
-	public MDPSparse(MDPSimple mdp, boolean sort)
-	{
+	public MDPSparse(MDPSimple mdp) {
 		int i, j, k, n;
-		TreeMap<Integer, Double> sorted = null;
 		initialise(mdp.getNumStates());
 		copyFrom(mdp);
 		// Copy stats
 		numDistrs = mdp.getNumChoices();
 		numTransitions = mdp.getNumTransitions();
 		maxNumDistrs = mdp.getMaxNumChoices();
-		// Copy transition function
-		if (sort) {
-			sorted = new TreeMap<Integer, Double>();
-		}
+
 		nonZeros = new double[numTransitions];
 		cols = new int[numTransitions];
 		choiceStarts = new int[numDistrs + 1];
@@ -121,28 +101,16 @@ public class MDPSparse extends MDPExplicit
 					actions[j + l] = mdp.getAction(i, l);
 				}
 			}
+
 			for (Distribution distr : mdp.trans.get(i)) {
 				choiceStarts[j] = k;
-				for (Map.Entry<Integer, Double> e : distr) {
-					if (sort) {
-						sorted.put(e.getKey(), e.getValue());
-					} else {
-						cols[k] = (Integer) e.getKey();
-						nonZeros[k] = (Double) e.getValue();
-						k++;
-					}
-				}
-				if (sort) {
-					for (Map.Entry<Integer, Double> e : sorted.entrySet()) {
-						cols[k] = (Integer) e.getKey();
-						nonZeros[k] = (Double) e.getValue();
-						k++;
-					}
-					sorted.clear();
-				}
+				System.arraycopy(distr.keys, 0, cols, k, distr.keys.length);
+				System.arraycopy(distr.values, 0, nonZeros, k, distr.values.length);
+				k += distr.keys.length;
 				j++;
 			}
 		}
+
 		choiceStarts[numDistrs] = numTransitions;
 		rowStarts[numStates] = numDistrs;
 	}
@@ -270,9 +238,7 @@ public class MDPSparse extends MDPExplicit
 				choiceStarts[choiceIndex] = colIndex;
 				this.actions[choiceIndex] = mdp.getAction(s, a);
 				choiceIndex++;
-				Iterator<Entry<Integer, Double>> it = mdp.getTransitionsIterator(s, a);
-				while (it.hasNext()) {
-					Entry<Integer, Double> next = it.next();
+				for (Entry<Integer, Double> next : mdp.getTransitions(s, a)) {
 					cols[colIndex] = reverseStates[next.getKey()];
 					nonZeros[colIndex] = next.getValue();
 					colIndex++;
@@ -386,16 +352,17 @@ public class MDPSparse extends MDPExplicit
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(final int s)
+	public IntIterator getSuccessorsIterator(final int s)
 	{
 		// Need to build set to avoid duplicates
 		// So not necessarily the fastest method to access successors
 		int start = choiceStarts[rowStarts[s]];
 		int end = choiceStarts[rowStarts[s + 1]];
-		HashSet<Integer> succs = new HashSet<Integer>();
+		IntSet succs = new IntOpenHashSet();
 		for (int i = start; i < end; i++) {
 			succs.add(cols[i]);
 		}
+
 		return succs.iterator();
 	}
 
@@ -522,6 +489,21 @@ public class MDPSparse extends MDPExplicit
 	}
 
 	@Override
+	public boolean allSuccessorsInSet(int s, int i, SparseBitSet set) {
+		int j, k, l2, h2;
+		j = rowStarts[s] + i;
+		l2 = choiceStarts[j];
+		h2 = choiceStarts[j + 1];
+		for (k = l2; k < h2; k++) {
+			// Assume that only non-zero entries are stored
+			if (!set.get(cols[k])) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	@Override
 	public boolean someSuccessorsInSet(int s, int i, BitSet set)
 	{
 		int j, k, l2, h2;
@@ -538,15 +520,15 @@ public class MDPSparse extends MDPExplicit
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(final int s, final int i)
+	public IntIterator getSuccessorsIterator(final int s, final int i)
 	{
 		int start = choiceStarts[rowStarts[s] + i];
 		int end = choiceStarts[rowStarts[s] + i + 1];
-		List<Integer> succs = new ArrayList<Integer>();
-		for (int j = start; j < end; j++) {
-			succs.add(cols[j]);
+		IntList successors = new IntArrayList(end - start);
+		for (; start < end; start++) {
+			successors.add(cols[start]);
 		}
-		return succs.iterator();
+		return successors.iterator();
 	}
 
 	// Accessors (for MDP)
@@ -557,58 +539,97 @@ public class MDPSparse extends MDPExplicit
 		return choiceStarts[rowStarts[s] + i + 1] - choiceStarts[rowStarts[s] + i];
 	}
 
+	class SparseEntry implements Int2DoubleMap.Entry {
+		int i;
+
+		@Override
+		public Integer getKey()
+		{
+			return getIntKey();
+		}
+
+		@Override
+		public int getIntKey() {
+			return cols[i];
+		}
+
+		@Override
+		public Double getValue()
+		{
+			return getDoubleValue();
+		}
+
+		@Override
+		public double setValue(double value) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public double getDoubleValue() {
+			return nonZeros[i];
+		}
+
+		@Override
+		public Double setValue(Double arg0)
+		{
+			throw new UnsupportedOperationException();
+		}
+	}
+
+	class SparseIterator implements Iterator<Int2DoubleMap.Entry> {
+
+		final int end;
+		final SparseEntry entry;
+
+		private SparseIterator(int s, int i) {
+			entry = new SparseEntry();
+			entry.i = choiceStarts[rowStarts[s] + i] - 1;
+			end = choiceStarts[rowStarts[s] + i + 1];
+		}
+
+		@Override
+		public boolean hasNext()
+		{
+			return entry.i + 1 < end;
+		}
+
+		@Override
+		public Int2DoubleMap.Entry next() {
+			entry.i++;
+			return entry;
+		}
+
+		@Override
+		public void remove()
+		{
+			throw new UnsupportedOperationException();
+		}
+	}
+
 	@Override
-	public Iterator<Entry<Integer, Double>> getTransitionsIterator(final int s, final int i)
+	public Iterable<Int2DoubleMap.Entry> getTransitions(final int s, final int i)
 	{
-		return new Iterator<Entry<Integer, Double>>()
-		{
-			final int start = choiceStarts[rowStarts[s] + i];
-			int col = start;
-			final int end = choiceStarts[rowStarts[s] + i + 1];
-
-			@Override
-			public boolean hasNext()
-			{
-				return col < end;
-			}
+		return () -> new SparseIterator(s, i);
+	}
 
-			@Override
-			public Entry<Integer, Double> next()
-			{
-				assert (col < end);
-				final int i = col;
-				col++;
-				return new Entry<Integer, Double>()
-				{
-					int key = cols[i];
-					double value = nonZeros[i];
-
-					@Override
-					public Integer getKey()
-					{
-						return key;
-					}
+	@Override
+	public void forEachTransition(int s, int i, IntDoubleConsumer consumer) {
+		int current = choiceStarts[rowStarts[s] + i];
+		int end = choiceStarts[rowStarts[s] + i + 1];
 
-					@Override
-					public Double getValue()
-					{
-						return value;
-					}
+		for (; current < end; current++) {
+			consumer.accept(cols[current], nonZeros[current]);
+		}
+	}
 
-					@Override
-					public Double setValue(Double arg0)
-					{
-						throw new UnsupportedOperationException();
-					}
-				};
-			}
+	@Override
+	public void forEachTransition(int s, int i, IntConsumer consumer) {
+		int current = choiceStarts[rowStarts[s] + i];
+		int end = choiceStarts[rowStarts[s] + i + 1];
 
-			@Override
-			public void remove()
-			{
-				throw new UnsupportedOperationException();
-			}
-		};
+		for (; current < end; current++) {
+			consumer.accept(cols[current]);
+		}
 	}
 
 	@Override
@@ -616,7 +637,7 @@ public class MDPSparse extends MDPExplicit
 	{
 		int j, k, l1, h1, l2, h2;
 		boolean b1, some;
-		for (int i : new IterableStateSet(subset, numStates)) {
+		for (int i = subset.nextSetBit(0); i >= 0; i = subset.nextSetBit(i+1)) {
 			b1 = forall; // there exists or for all
 			l1 = rowStarts[i];
 			h1 = rowStarts[i + 1];
@@ -652,7 +673,7 @@ public class MDPSparse extends MDPExplicit
 	{
 		int j, k, l1, h1, l2, h2;
 		boolean b1, some, all;
-		for (int i : new IterableStateSet(subset, numStates)) {
+		for (int i = subset.nextSetBit(0); i >= 0; i = subset.nextSetBit(i+1)) {
 			b1 = true;
 			l1 = rowStarts[i];
 			h1 = rowStarts[i + 1];
@@ -667,8 +688,9 @@ public class MDPSparse extends MDPExplicit
 						all = false;
 						break; // Stop early (already know b1 will be set to false)
 					}
-					if (v.get(cols[k])) {
-						some = true;
+
+					if (!some) {
+						some = v.get(cols[k]);
 					}
 				}
 				if (!(some && all)) {
@@ -683,25 +705,26 @@ public class MDPSparse extends MDPExplicit
 	@Override
 	public void prob1Estep(BitSet subset, BitSet u, BitSet v, BitSet result, int strat[])
 	{
-		int j, k, l1, h1, l2, h2, stratCh = -1;
+		int l1, h1, l2, h2, stratCh = -1;
 		boolean b1, some, all;
-		for (int i : new IterableStateSet(subset, numStates)) {
+		for (int i = subset.nextSetBit(0); i >= 0; i = subset.nextSetBit(i+1)) {
 			b1 = false;
 			l1 = rowStarts[i];
 			h1 = rowStarts[i + 1];
-			for (j = l1; j < h1; j++) {
+			for (int j = l1; j < h1; j++) {
 				some = false;
 				all = true;
 				l2 = choiceStarts[j];
 				h2 = choiceStarts[j + 1];
-				for (k = l2; k < h2; k++) {
+				for (int k = l2; k < h2; k++) {
 					// Assume that only non-zero entries are stored
 					if (!u.get(cols[k])) {
 						all = false;
 						break; // Stop early (already know b1 will not be set to true)
 					}
-					if (v.get(cols[k])) {
-						some = true;
+
+					if (!some) {
+						some = v.get(cols[k]);
 					}
 				}
 				if (some && all) {
@@ -725,26 +748,27 @@ public class MDPSparse extends MDPExplicit
 	@Override
 	public void prob1step(BitSet subset, BitSet u, BitSet v, boolean forall, BitSet result)
 	{
-		int j, k, l1, h1, l2, h2;
-		boolean b1, some, all;
-		for (int i : new IterableStateSet(subset, numStates)) {
-			b1 = forall; // there exists or for all
-			l1 = rowStarts[i];
-			h1 = rowStarts[i + 1];
-			for (j = l1; j < h1; j++) {
-				some = false;
-				all = true;
-				l2 = choiceStarts[j];
-				h2 = choiceStarts[j + 1];
-				for (k = l2; k < h2; k++) {
+		for (int i = subset.nextSetBit(0); i >= 0; i = subset.nextSetBit(i+1)) {
+			boolean b1 = forall; // there exists or for all
+			int l1 = rowStarts[i];
+			int h1 = rowStarts[i + 1];
+			for (int j = l1; j < h1; j++) {
+				boolean some = false;
+				boolean all = true;
+				int l2 = choiceStarts[j];
+				int h2 = choiceStarts[j + 1];
+
+				for (int k = l2; k < h2; k++) {
 					// Assume that only non-zero entries are stored
-					if (v.get(cols[k])) {
-						some = true;
+					if (!some) {
+						some = v.get(cols[k]);
 					}
-					if (!u.get(cols[k])) {
-						all = false;
+
+					if (all) {
+						all = u.get(cols[k]);
 					}
 				}
+
 				if (forall) {
 					if (!(some && all)) {
 						b1 = false;
@@ -757,6 +781,7 @@ public class MDPSparse extends MDPExplicit
 					}
 				}
 			}
+
 			result.set(i, b1);
 		}
 	}
diff --git a/prism/src/explicit/Model.java b/prism/src/explicit/Model.java
index fc93f99..e538421 100644
--- a/prism/src/explicit/Model.java
+++ b/prism/src/explicit/Model.java
@@ -27,11 +27,9 @@
 package explicit;
 
 import java.io.File;
-import java.util.BitSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 
+import it.unimi.dsi.fastutil.ints.IntIterator;
 import parser.State;
 import parser.Values;
 import parser.VarList;
@@ -145,7 +143,7 @@ public interface Model
 	/**
 	 * Get an iterator over the successors of state s.
 	 */
-	public Iterator<Integer> getSuccessorsIterator(int s);
+	public IntIterator getSuccessorsIterator(int s);
 	
 	/**
 	 * Returns true if state s2 is a successor of state s1.
diff --git a/prism/src/explicit/ModelExplicit.java b/prism/src/explicit/ModelExplicit.java
index 5a4f068..fc5c3e2 100644
--- a/prism/src/explicit/ModelExplicit.java
+++ b/prism/src/explicit/ModelExplicit.java
@@ -27,13 +27,7 @@
 package explicit;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
+import java.util.*;
 
 import parser.State;
 import parser.Values;
@@ -54,7 +48,7 @@ public abstract class ModelExplicit implements Model
 	/** Number of states */
 	protected int numStates;
 	/** Which states are initial states */
-	protected List<Integer> initialStates; // TODO: should be a (linkedhash?) set really
+	protected List<Integer> initialStates;
 	/** States that are/were deadlocks. Where requested and where appropriate (DTMCs/MDPs),
 	 * these states may have been fixed at build time by adding self-loops. */
 	protected TreeSet<Integer> deadlocks;
@@ -129,12 +123,12 @@ public abstract class ModelExplicit implements Model
 	public void initialise(int numStates)
 	{
 		this.numStates = numStates;
-		initialStates = new ArrayList<Integer>();
-		deadlocks = new TreeSet<Integer>();
+		initialStates = new ArrayList<>();
+		deadlocks = new TreeSet<>();
 		statesList = null;
 		constantValues = null;
 		varList = null;
-		labels = new TreeMap<String, BitSet>();
+		labels = new TreeMap<>();
 	}
 
 	/**
@@ -262,7 +256,7 @@ public abstract class ModelExplicit implements Model
 	@Override
 	public int getFirstInitialState()
 	{
-		return initialStates.isEmpty() ? -1 : initialStates.get(0);
+		return initialStates.isEmpty() ? -1 : initialStates.iterator().next();
 	}
 
 	@Override
diff --git a/prism/src/explicit/NondetModel.java b/prism/src/explicit/NondetModel.java
index 4475826..c065a0d 100644
--- a/prism/src/explicit/NondetModel.java
+++ b/prism/src/explicit/NondetModel.java
@@ -27,8 +27,13 @@
 package explicit;
 
 import java.util.BitSet;
-import java.util.Iterator;
+import java.util.Collection;
+import java.util.stream.IntStream;
 
+import com.google.common.collect.Lists;
+import explicit.nxt.SparseBitSet;
+import it.unimi.dsi.fastutil.ints.IntCollection;
+import it.unimi.dsi.fastutil.ints.IntIterator;
 import prism.PrismLog;
 import strat.MDStrategy;
 
@@ -76,7 +81,15 @@ public interface NondetModel extends Model
 	 * @param set The set to test for inclusion
 	 */
 	public boolean allSuccessorsInSet(int s, int i, BitSet set);
-	
+
+	default boolean allSuccessorsInSet(int s, int i, SparseBitSet set) {
+		throw new UnsupportedOperationException();
+	}
+
+	default boolean allSuccessorsInSet(int s, int i, IntCollection set) {
+		throw new UnsupportedOperationException();
+	}
+
 	/**
 	 * Check if some successor state from choice {@code i} of state {@code s} is in the set {@code set}.
 	 * @param s The state to check
@@ -90,8 +103,16 @@ public interface NondetModel extends Model
 	 * @param s The state
 	 * @param i Choice index
 	 */
-	public Iterator<Integer> getSuccessorsIterator(int s, int i);
-	
+	public IntIterator getSuccessorsIterator(int s, int i);
+
+	default IntStream getSuccessorsIntStream(int s, int i) {
+		throw new UnsupportedOperationException();
+	}
+
+	default Collection<Integer> getSuccessors(int s, int i) {
+		return Lists.newArrayList(getSuccessorsIterator(s, i));
+	}
+
 	/**
 	 * Construct a model that is induced by applying strategy {@code strat} to this model.
 	 * Note that the "new" model may be just an implicit (read-only) representation. 
diff --git a/prism/src/explicit/PrismExplicit.java b/prism/src/explicit/PrismExplicit.java
index f35e90f..f6eaa29 100644
--- a/prism/src/explicit/PrismExplicit.java
+++ b/prism/src/explicit/PrismExplicit.java
@@ -29,6 +29,7 @@ package explicit;
 import java.io.File;
 import java.io.FileNotFoundException;
 
+import explicit.nxt.LDBAMDPModelChecker;
 import parser.ast.Expression;
 import parser.ast.ModulesFile;
 import parser.ast.PropertiesFile;
@@ -215,7 +216,7 @@ public class PrismExplicit extends PrismComponent
 			mc = new DTMCModelChecker(this);
 			break;
 		case MDP:
-			mc = new MDPModelChecker(this);
+			mc = new LDBAMDPModelChecker(this);
 			break;
 		case CTMC:
 			mc = new CTMCModelChecker(this);
diff --git a/prism/src/explicit/ProbModelChecker.java b/prism/src/explicit/ProbModelChecker.java
index da30ec0..068dcba 100644
--- a/prism/src/explicit/ProbModelChecker.java
+++ b/prism/src/explicit/ProbModelChecker.java
@@ -30,6 +30,7 @@ import java.io.File;
 import java.util.BitSet;
 import java.util.List;
 
+import explicit.nxt.LDBAMDPModelChecker;
 import parser.ast.Coalition;
 import parser.ast.Expression;
 import parser.ast.ExpressionProb;
@@ -43,12 +44,7 @@ import parser.type.TypeBool;
 import parser.type.TypeDouble;
 import parser.type.TypePathBool;
 import parser.type.TypePathDouble;
-import prism.IntegerBound;
-import prism.OpRelOpBound;
-import prism.PrismComponent;
-import prism.PrismException;
-import prism.PrismNotSupportedException;
-import prism.PrismSettings;
+import prism.*;
 import explicit.rewards.ConstructRewards;
 import explicit.rewards.MCRewards;
 import explicit.rewards.MDPRewards;
@@ -593,7 +589,7 @@ public class ProbModelChecker extends NonProbModelChecker
 		// and whether we want to use the corresponding algorithms
 		boolean useSimplePathAlgo = expr.isSimplePathFormula();
 
-		if (useSimplePathAlgo &&
+		if (useSimplePathAlgo && settings != null &&
 		    settings.getBoolean(PrismSettings.PRISM_PATH_VIA_AUTOMATA) &&
 		    LTLModelChecker.isSupportedLTLFormula(model.getModelType(), expr)) {
 			// If PRISM_PATH_VIA_AUTOMATA is true, we want to use the LTL engine
@@ -808,7 +804,7 @@ public class ProbModelChecker extends NonProbModelChecker
 			res = ((DTMCModelChecker) this).computeUntilProbs((DTMC) model, remain, target);
 			break;
 		case MDP:
-			res = ((MDPModelChecker) this).computeUntilProbs((MDP) model, remain, target, minMax.isMin());
+			res = ((LDBAMDPModelChecker) this).computeUntilProbs((MDP) model, remain, target, minMax.isMin());
 			break;
 		case STPG:
 			res = ((STPGModelChecker) this).computeUntilProbs((STPG) model, remain, target, minMax.isMin1(), minMax.isMin2());
@@ -816,6 +812,11 @@ public class ProbModelChecker extends NonProbModelChecker
 		default:
 			throw new PrismNotSupportedException("Cannot model check " + expr + " for " + model.getModelType() + "s");
 		}
+
+		if (result == null) {
+			result = new Result();
+		}
+
 		result.setStrategy(res.strat);
 		return StateValues.createFromDoubleArray(res.soln, model);
 	}
diff --git a/prism/src/explicit/STPG.java b/prism/src/explicit/STPG.java
index 904ff65..47358d7 100644
--- a/prism/src/explicit/STPG.java
+++ b/prism/src/explicit/STPG.java
@@ -32,6 +32,7 @@ import java.util.List;
 import java.util.Map.Entry;
 
 import explicit.rewards.STPGRewards;
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
 
 /**
  * Interface for classes that provide (read) access to an explicit-state stochastic two-player game (STPG).
@@ -66,7 +67,7 @@ public interface STPG extends NondetModel
 	/**
 	 * Get an iterator over the transitions from choice {@code i} of state {@code s}.
 	 */
-	public Iterator<Entry<Integer, Double>> getTransitionsIterator(int s, int i);
+	Iterable<Int2DoubleMap.Entry> getTransitions(int s, int i);
 
 	/**
 	 * Is choice {@code i} of state {@code s} in nested form? (See {@link explicit.STPG} for details)
@@ -91,7 +92,7 @@ public interface STPG extends NondetModel
 	/**
 	 * Get an iterator over the transitions from nested choice {@code i,j} of state {@code s}.
 	 */
-	public Iterator<Entry<Integer, Double>> getNestedTransitionsIterator(int s, int i, int j);
+	public Iterator<Int2DoubleMap.Entry> getNestedTransitionsIterator(int s, int i, int j);
 
 	/**
 	 * Perform a single step of precomputation algorithm Prob0, i.e., for states i in {@code subset},
diff --git a/prism/src/explicit/STPGAbstrSimple.java b/prism/src/explicit/STPGAbstrSimple.java
index de4ea1b..191fc73 100644
--- a/prism/src/explicit/STPGAbstrSimple.java
+++ b/prism/src/explicit/STPGAbstrSimple.java
@@ -27,6 +27,7 @@
 
 package explicit;
 
+import java.lang.reflect.Array;
 import java.util.*;
 import java.util.Map.Entry;
 import java.io.*;
@@ -34,6 +35,7 @@ import java.io.*;
 import common.IterableStateSet;
 
 import explicit.rewards.STPGRewards;
+import it.unimi.dsi.fastutil.ints.*;
 import prism.ModelType;
 import prism.PrismException;
 import prism.PrismNotSupportedException;
@@ -93,7 +95,7 @@ public class STPGAbstrSimple extends ModelExplicit implements STPG, NondetModelS
 		copyFrom(m);
 		for (i = 0; i < numStates; i++) {
 			set = newDistributionSet(null);
-			set.addAll(m.getChoices(i));
+			set.addAll(Arrays.asList(m.getChoices(i)));
 			addDistributionSet(i, set);
 		}
 	}
@@ -289,17 +291,9 @@ public class STPGAbstrSimple extends ModelExplicit implements STPG, NondetModelS
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(final int s)
+	public IntIterator getSuccessorsIterator(final int s)
 	{
-		// Need to build set to avoid duplicates
-		// So not necessarily the fastest method to access successors
-		HashSet<Integer> succs = new HashSet<Integer>();
-		for (DistributionSet distrs : trans.get(s)) {
-			for (Distribution distr : distrs) {
-				succs.addAll(distr.getSupport());
-			}
-		}
-		return succs.iterator();
+		throw new UnsupportedOperationException();
 	}
 
 	@Override
@@ -497,21 +491,20 @@ public class STPGAbstrSimple extends ModelExplicit implements STPG, NondetModelS
 	}
 
 	@Override
+	public boolean allSuccessorsInSet(int s, int i, IntCollection set) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
 	public boolean someSuccessorsInSet(int s, int i, BitSet set)
 	{
 		return trans.get(s).get(i).containsOneOf(set);
 	}
 	
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(final int s, final int i)
+	public IntIterator getSuccessorsIterator(final int s, final int i)
 	{
-		// Need to build set to avoid duplicates
-		// So not necessarily the fastest method to access successors
-		HashSet<Integer> succs = new HashSet<Integer>();
-		for (Distribution distr : trans.get(s).get(i)) {
-			succs.addAll(distr.getSupport());
-		}
-		return succs.iterator();
+		throw new UnsupportedOperationException();
 	}
 
 	// Accessors (for STPG)
@@ -531,7 +524,7 @@ public class STPGAbstrSimple extends ModelExplicit implements STPG, NondetModelS
 	}
 
 	@Override
-	public Iterator<Entry<Integer, Double>> getTransitionsIterator(int s, int i)
+	public Iterable<Int2DoubleMap.Entry> getTransitions(int s, int i)
 	{
 		// All choices are nested
 		return null;
@@ -580,7 +573,7 @@ public class STPGAbstrSimple extends ModelExplicit implements STPG, NondetModelS
 	}
 
 	@Override
-	public Iterator<Entry<Integer, Double>> getNestedTransitionsIterator(int s, int i, int j)
+	public Iterator<Int2DoubleMap.Entry> getNestedTransitionsIterator(int s, int i, int j)
 	{
 		DistributionSet ds = trans.get(s).get(i);
 		Iterator<Distribution> iter = ds.iterator();
@@ -952,7 +945,7 @@ public class STPGAbstrSimple extends ModelExplicit implements STPG, NondetModelS
 		// General purpose toString(), based on STPG access methods
 		int s, i, j, ni, nj;
 		boolean first, firsti, firstTr;
-		Iterator<Entry<Integer, Double>> it;
+		Iterator<Int2DoubleMap.Entry> it;
 		String str = "";
 		first = true;
 		str = "[ ";
@@ -967,7 +960,7 @@ public class STPGAbstrSimple extends ModelExplicit implements STPG, NondetModelS
 			firsti = true;
 			for (i = 0; i < ni; i++) {
 				// Do non-nested choices
-				it = getTransitionsIterator(s, i);
+				it = getTransitions(s, i).iterator();
 				if (it == null)
 					continue;
 				if (firsti)
diff --git a/prism/src/explicit/STPGExplicit.java b/prism/src/explicit/STPGExplicit.java
index df71ea5..5b337b0 100644
--- a/prism/src/explicit/STPGExplicit.java
+++ b/prism/src/explicit/STPGExplicit.java
@@ -33,6 +33,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
 import prism.ModelType;
 import explicit.rewards.MDPRewards;
 import explicit.rewards.STPGRewards;
@@ -169,9 +170,9 @@ public class STPGExplicit extends MDPSimple implements STPG
 	}
 
 	@Override
-	public Iterator<Entry<Integer, Double>> getTransitionsIterator(int s, int i)
+	public Iterable<Int2DoubleMap.Entry> getTransitions(int s, int i)
 	{
-		return super.getTransitionsIterator(s, i);
+		return super.getTransitions(s, i);
 	}
 
 	@Override
@@ -203,7 +204,7 @@ public class STPGExplicit extends MDPSimple implements STPG
 	}
 
 	@Override
-	public Iterator<Entry<Integer, Double>> getNestedTransitionsIterator(int s, int i, int j)
+	public Iterator<Int2DoubleMap.Entry> getNestedTransitionsIterator(int s, int i, int j)
 	{
 		// No nested choices
 		return null;
@@ -407,7 +408,7 @@ public class STPGExplicit extends MDPSimple implements STPG
 				o = getAction(i, j);
 				if (o != null)
 					s += o + ":";
-				s += trans.get(i).get(j);
+				s += trans.get(i)[j];
 			}
 			s += "]";
 		}
@@ -430,13 +431,11 @@ public class STPGExplicit extends MDPSimple implements STPG
 		int j, k, advCh = -1;
 		double d, prob, minmax;
 		boolean first;
-		List<Distribution> step;
 
 		minmax = 0;
 		first = true;
 		j = -1;
-		step = trans.get(s);
-		for (Distribution distr : step) {
+		for (Distribution distr : trans.get(s)) {
 			j++;
 
 			// Compute sum for this distribution
diff --git a/prism/src/explicit/StateModelChecker.java b/prism/src/explicit/StateModelChecker.java
index 6b8d828..f138090 100644
--- a/prism/src/explicit/StateModelChecker.java
+++ b/prism/src/explicit/StateModelChecker.java
@@ -440,7 +440,7 @@ public class StateModelChecker extends PrismComponent
 	 */
 	public StateValues checkExpression(Model model, Expression expr, BitSet statesOfInterest) throws PrismException
 	{
-		StateValues res = null;
+		StateValues res;
 
 		// If-then-else
 		if (expr instanceof ExpressionITE) {
diff --git a/prism/src/explicit/SubNondetModel.java b/prism/src/explicit/SubNondetModel.java
index 7cfd2ce..7d556dc 100644
--- a/prism/src/explicit/SubNondetModel.java
+++ b/prism/src/explicit/SubNondetModel.java
@@ -28,16 +28,13 @@
 package explicit;
 
 import java.io.File;
-import java.util.ArrayList;
-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 java.util.*;
 
 import common.IterableStateSet;
+import it.unimi.dsi.fastutil.ints.IntCollection;
+import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
 import parser.State;
 import parser.Values;
 import parser.VarList;
@@ -53,15 +50,14 @@ import strat.MDStrategy;
  */
 public class SubNondetModel implements NondetModel
 {
-
 	private NondetModel model = null;
 	private BitSet states = null;
 	private Map<Integer, BitSet> actions = null;
 	private BitSet initialStates = null;
 	private List<State> statesList = null;
-	private Map<Integer, Integer> stateLookupTable = new HashMap<Integer, Integer>();
-	private Map<Integer, Map<Integer, Integer>> actionLookupTable = new HashMap<Integer, Map<Integer, Integer>>();
-	private Map<Integer, Integer> inverseStateLookupTable = new HashMap<Integer, Integer>();
+	private Map<Integer, Integer> stateLookupTable = new HashMap<>();
+	private Map<Integer, Map<Integer, Integer>> actionLookupTable = new HashMap<>();
+	private Map<Integer, Integer> inverseStateLookupTable = new HashMap<>();
 
 	/**
 	 * (Optionally) the stored predecessor relation. Becomes inaccurate after the model is changed!
@@ -104,7 +100,7 @@ public class SubNondetModel implements NondetModel
 	@Override
 	public Iterable<Integer> getInitialStates()
 	{
-		List<Integer> is = new ArrayList<Integer>();
+		List<Integer> is = new ArrayList<>();
 		for (int i = initialStates.nextSetBit(0); i >= 0; i = initialStates.nextSetBit(i + 1)) {
 			is.add(translateState(i));
 		}
@@ -166,7 +162,7 @@ public class SubNondetModel implements NondetModel
 
 	private List<State> generateSubStateList(BitSet states)
 	{
-		List<State> statesList = new ArrayList<State>();
+		List<State> statesList = new ArrayList<>();
 		for (int i : new IterableStateSet(states, model.getNumStates())){
 			statesList.add(model.getStatesList().get(i));
 		}
@@ -211,17 +207,15 @@ public class SubNondetModel implements NondetModel
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(int s)
+	public IntIterator getSuccessorsIterator(int s)
 	{
 		s = translateState(s);
-		HashSet<Integer> succs = new HashSet<Integer>();
+		IntSet succs = new IntOpenHashSet();
+
 		for (int i : new IterableStateSet(actions.get(s), model.getNumChoices(s))){
-			Iterator<Integer> it = model.getSuccessorsIterator(s, i);
-			while (it.hasNext()) {
-				int j = it.next();
-				succs.add(inverseTranslateState(j));
-			}
+			model.getSuccessorsIterator(s, i).forEachRemaining((j) -> succs.add(inverseTranslateState(j)));
 		}
+
 		return succs.iterator();
 	}
 
@@ -406,41 +400,28 @@ public class SubNondetModel implements NondetModel
 	@Override
 	public boolean allSuccessorsInSet(int s, int i, BitSet set)
 	{
-		Iterator<Integer> successors = getSuccessorsIterator(s, i);
-		while (successors.hasNext()) {
-			Integer successor = successors.next();
-			if (!set.get(successor)) {
-				return false;
-			}
-		}
-		return true;
+		return getSuccessorsIntStream(s, i).allMatch(set::get);
+	}
+
+	@Override
+	public boolean allSuccessorsInSet(int s, int i, IntCollection set) {
+		throw new UnsupportedOperationException();
 	}
 
 	@Override
 	public boolean someSuccessorsInSet(int s, int i, BitSet set)
 	{
-		Iterator<Integer> successors = getSuccessorsIterator(s, i);
-		while (successors.hasNext()) {
-			Integer successor = successors.next();
-			if (set.get(successor)) {
-				return true;
-			}
-		}
-		return false;
+		return getSuccessorsIntStream(s, i).anyMatch(set::get);
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(int s, int i)
+	public IntIterator getSuccessorsIterator(int s, int i)
 	{
 		int sOriginal = translateState(s);
 		int iOriginal = translateAction(s, i);
-		List<Integer> succ = new ArrayList<Integer>();
-		Iterator<Integer> it = model.getSuccessorsIterator(sOriginal, iOriginal);
-		while (it.hasNext()) {
-			int j = it.next();
-			succ.add(inverseTranslateState(j));
-		}
-		return succ.iterator();
+		IntSet set = new IntOpenHashSet();
+		model.getSuccessorsIterator(sOriginal, iOriginal).forEachRemaining(j -> set.add(inverseTranslateState(j)));
+		return set.iterator();
 	}
 
 	private BitSet translateSet(BitSet set)
diff --git a/prism/src/explicit/nxt/ExpressionToLTL.java b/prism/src/explicit/nxt/ExpressionToLTL.java
new file mode 100644
index 0000000..6bb2973
--- /dev/null
+++ b/prism/src/explicit/nxt/ExpressionToLTL.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016  (Salomon Sickert)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+import parser.ast.*;
+import parser.type.TypeBool;
+import prism.PrismLangException;
+import rabinizer.ltl.*;
+
+/*
+ * Convert a property expression (an LTL formula) to classes understood by ltl2ldba
+ */
+public class ExpressionToLTL {
+
+    public static Formula convert(Expression e) throws PrismLangException {
+        if (e == null) {
+            return null;
+        }
+
+        if (e instanceof ExpressionTemporal) {
+            return convertTemporal((ExpressionTemporal) e);
+        } else if (e instanceof ExpressionBinaryOp) {
+            return convertBinaryOp((ExpressionBinaryOp) e);
+        } else if (e instanceof ExpressionUnaryOp) {
+            return convertUnaryOp((ExpressionUnaryOp) e);
+        } else if (e instanceof ExpressionLiteral) {
+            return convertLiteral((ExpressionLiteral) e);
+        } else if (e instanceof ExpressionLabel) {
+            return convertLabel((ExpressionLabel) e);
+        }
+
+        throw new IllegalArgumentException("Cannot convert expression: " + e.toString());
+    }
+
+    private static Formula convertTemporal(ExpressionTemporal e) throws PrismLangException {
+        Formula ltl1 = convert(e.getOperand1());
+        Formula ltl2 = convert(e.getOperand2());
+
+        switch (e.getOperator()) {
+            case ExpressionTemporal.P_X:
+                return new XOperator(ltl2);
+
+            case ExpressionTemporal.P_U:
+                return new UOperator(ltl1, ltl2);
+
+            case ExpressionTemporal.P_F:
+                return new FOperator(ltl2);
+
+            case ExpressionTemporal.P_G:
+                return new GOperator(ltl2);
+
+            case ExpressionTemporal.P_W:
+            case ExpressionTemporal.P_R:
+                return convert(e.convertToUntilForm());
+
+            default:
+                throw new PrismLangException("Cannot convert expression: ", e);
+        }
+    }
+
+    private static Formula convertBinaryOp(ExpressionBinaryOp e) throws PrismLangException {
+        Formula ltl1 = convert(e.getOperand1());
+        Formula ltl2 = convert(e.getOperand2());
+
+        switch (e.getOperator()) {
+            case ExpressionBinaryOp.IMPLIES:
+                return new Disjunction(ltl1.not(), ltl2);
+
+            case ExpressionBinaryOp.IFF:
+                return new Conjunction(new Disjunction(ltl1.not(), ltl2), new Disjunction(ltl1, ltl2.not()));
+
+            case ExpressionBinaryOp.OR:
+                return new Disjunction(ltl1, ltl2);
+
+            case ExpressionBinaryOp.AND:
+                return new Conjunction(ltl1, ltl2);
+
+            default:
+                throw new PrismLangException("Cannot convert expression: ", e);
+        }
+    }
+
+    private static Formula convertUnaryOp(ExpressionUnaryOp e) throws PrismLangException {
+        Formula ltl1 = convert(e.getOperand());
+
+        switch (e.getOperator()) {
+            case ExpressionUnaryOp.NOT:
+                return ltl1.not();
+
+            case ExpressionUnaryOp.PARENTH:
+                return ltl1;
+
+            default:
+                throw new PrismLangException("Cannot convert expression: ", e);
+        }
+    }
+
+    private static Formula convertLiteral(ExpressionLiteral e) throws PrismLangException {
+        if (!(e.getType() instanceof TypeBool)) {
+            throw new PrismLangException("Cannot convert expression: ", e);
+        }
+
+        return BooleanConstant.get(e.evaluateBoolean());
+    }
+
+    private static Formula convertLabel(ExpressionLabel e) throws PrismLangException {
+        return new Literal(Integer.parseInt(e.getName()), false);
+    }
+}
diff --git a/prism/src/explicit/nxt/IntDoubleConsumer.java b/prism/src/explicit/nxt/IntDoubleConsumer.java
new file mode 100644
index 0000000..8b58f80
--- /dev/null
+++ b/prism/src/explicit/nxt/IntDoubleConsumer.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 (Salomon Sickert)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+@FunctionalInterface
+public interface IntDoubleConsumer {
+    void accept(int key, double value);
+}
\ No newline at end of file
diff --git a/prism/src/explicit/nxt/LDBALTLModelChecker.java b/prism/src/explicit/nxt/LDBALTLModelChecker.java
new file mode 100644
index 0000000..b595c3f
--- /dev/null
+++ b/prism/src/explicit/nxt/LDBALTLModelChecker.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2016
+ *
+ * Dave Parker <david.parker@comlab.ox.ac.uk> (University of Oxford)
+ * Joachim Klein <klein@tcs.inf.tu-dresden.de> (TU Dresden)
+ * Salomon Sickert <sickert@in.tum.de> (TU München)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+import explicit.*;
+import explicit.Model;
+import explicit.ProbModelChecker;
+import explicit.StateModelChecker;
+import explicit.StateValues;
+import jhoafparser.consumer.HOAConsumerException;
+import jhoafparser.consumer.HOAConsumerPrint;
+import parser.ast.*;
+import parser.type.TypeBool;
+import parser.type.TypePathBool;
+import prism.*;
+import rabinizer.automata.Optimisation;
+import ltl2ldba.DetLimitAutomaton;
+import ltl2ldba.DetLimitAutomatonFactory;
+import rabinizer.ltl.Formula;
+
+import java.io.*;
+import java.util.*;
+import java.util.List;
+
+/**
+ * LTL model checking functionality, Based on MDPModelChecker
+ */
+public class LDBALTLModelChecker extends PrismComponent
+{
+
+	/**
+	 * Create a new LTLModelChecker, inherit basic state from parent (unless null).
+	 */
+	public LDBALTLModelChecker(PrismComponent parent)
+	{
+		super(parent);
+	}
+
+	/**
+	 * Extract maximal state formula from an LTL path formula, model check them (with passed in model checker) and
+	 * replace them with ExpressionLabel objects L0, L1, etc. Expression passed in is modified directly, but the result
+	 * is also returned. As an optimisation, expressions that results in true/false for all states are converted to an
+	 * actual true/false, and duplicate results (or their negations) reuse the same label. BitSets giving the states which
+	 * satisfy each label are put into the vector labelBS, which should be empty when this function is called.
+	 */
+	public Expression checkMaximalStateFormulas(StateModelChecker mc, Model model, Expression expr, List<BitSet> labelBS) throws PrismException
+	{
+		// A state formula
+		if (expr.getType() instanceof TypeBool) {
+			// Model check state formula for all states
+			StateValues sv = mc.checkExpression(model, expr, null);
+			BitSet bs = sv.getBitSet();
+			// Detect special cases (true, false) for optimisation
+			if (bs.isEmpty()) {
+				return Expression.False();
+			}
+			if (bs.cardinality() == model.getNumStates()) {
+				return Expression.True();
+			}
+			// See if we already have an identical result
+			// (in which case, reuse it)
+			int i = labelBS.indexOf(bs);
+			if (i != -1) {
+				sv.clear();
+				return new ExpressionLabel(Integer.toString(i));
+			}
+			// Also, see if we already have the negation of this result
+			// (in which case, reuse it)
+			BitSet bsNeg = new BitSet(model.getNumStates());
+			bsNeg.set(0, model.getNumStates());
+			bsNeg.andNot(bs);
+			i = labelBS.indexOf(bsNeg);
+			if (i != -1) {
+				sv.clear();
+				return Expression.Not(new ExpressionLabel(Integer.toString(i)));
+			}
+			// Otherwise, add result to list, return new label
+			labelBS.add(bs);
+			return new ExpressionLabel(Integer.toString(labelBS.size() - 1));
+		}
+		// A path formula (recurse, modify, return)
+		else if (expr.getType() instanceof TypePathBool) {
+			if (expr instanceof ExpressionBinaryOp) {
+				ExpressionBinaryOp exprBinOp = (ExpressionBinaryOp) expr;
+				exprBinOp.setOperand1(checkMaximalStateFormulas(mc, model, exprBinOp.getOperand1(), labelBS));
+				exprBinOp.setOperand2(checkMaximalStateFormulas(mc, model, exprBinOp.getOperand2(), labelBS));
+			} else if (expr instanceof ExpressionUnaryOp) {
+				ExpressionUnaryOp exprUnOp = (ExpressionUnaryOp) expr;
+				exprUnOp.setOperand(checkMaximalStateFormulas(mc, model, exprUnOp.getOperand(), labelBS));
+			} else if (expr instanceof ExpressionTemporal) {
+				ExpressionTemporal exprTemp = (ExpressionTemporal) expr;
+				if (exprTemp.getOperand1() != null) {
+					exprTemp.setOperand1(checkMaximalStateFormulas(mc, model, exprTemp.getOperand1(), labelBS));
+				}
+				if (exprTemp.getOperand2() != null) {
+					exprTemp.setOperand2(checkMaximalStateFormulas(mc, model, exprTemp.getOperand2(), labelBS));
+				}
+			}
+		}
+		return expr;
+	}
+
+	/**
+	 * Construct a deterministic automaton (DA) for an LTL formula, having first extracted maximal state formulas
+	 * and model checked them with the passed in model checker. The maximal state formulas are assigned labels
+	 * (L0, L1, etc.) which become the atomic propositions in the resulting DA. BitSets giving the states which
+	 * satisfy each label are put into the vector {@code labelBS}, which should be empty when this function is called.
+	 *
+	 * @param mc a ProbModelChecker, used for checking maximal state formulas
+	 * @param model the model
+	 * @param expr a path expression, i.e. the LTL formula
+	 * @param labelBS empty vector to be filled with BitSets for subformulas
+	 * @return the DA
+	 */
+	private LDBAWrapper constructDAForLTLFormula(ProbModelChecker mc, MDP model, Expression expr, List<BitSet> labelBS) throws PrismException
+	{
+		Expression ltl;
+		DetLimitAutomaton da;
+		long time;
+
+		if (Expression.containsTemporalTimeBounds(expr)) {
+			if (model.getModelType().continuousTime()) {
+				throw new PrismException("Automaton construction for time-bounded operators not supported for " + model.getModelType()+".");
+			}
+
+			if (!expr.isSimplePathFormula()) {
+				throw new PrismNotSupportedException("Time-bounded operators not supported in LTL: " + expr);
+			}
+		}
+
+		// Model check maximal state formulas
+		ltl = checkMaximalStateFormulas(mc, model, expr.deepCopy(), labelBS);
+
+		// Convert LTL formula to deterministic automaton
+		mainLog.println("\nBuilding deterministic automaton (for " + ltl + ")...");
+		Formula ltlFormula = ExpressionToLTL.convert(ltl);
+		EnumSet<Optimisation> optimisations = EnumSet.allOf(Optimisation.class);
+		optimisations.remove(Optimisation.REMOVE_EPSILON_TRANSITIONS);
+		optimisations.remove(Optimisation.LAZY_ACCEPTING_COMPONENT_CONSTRUCTION);
+		time = System.currentTimeMillis();
+
+		da = DetLimitAutomatonFactory.createDetLimitAutomaton(ltlFormula, null, optimisations);
+
+		mainLog.println("A has at least " + da.size() + " states. The initial state is " + da.getInitialState());
+		time = System.currentTimeMillis() - time;
+		mainLog.println("Time for translation: " + time / 1000.0 + " seconds.");
+		// If required, export DA
+		if (settings != null && settings.getExportPropAut()) {
+			mainLog.println("Exporting to file \"" + settings.getExportPropAutFilename() + "\"...");
+			PrintStream out = PrismUtils.newPrintStream(settings.getExportPropAutFilename());
+			try {
+				da.toHOA(new HOAConsumerPrint(out));
+			} catch (HOAConsumerException e) {
+				e.printStackTrace();
+			}
+			out.close();
+		}
+		
+		return new LDBAWrapper(da);
+	}
+
+	/**
+	 * Generate a deterministic automaton for the given LTL formula
+	 * and construct the product of this automaton with an MDP.
+	 *
+	 * @param mc a ProbModelChecker, used for checking maximal state formulas
+	 * @param model the model
+	 * @param expr a path expression
+	 * @param statesOfInterest the set of states for which values should be calculated (null = all states)
+	 * @return the product with the DA
+	 * @throws PrismException
+	 */
+	public LDBAProduct<MDP> constructProductMDP(ProbModelChecker mc, MDP model, Expression expr, BitSet statesOfInterest) throws PrismException
+	{
+		// Convert LTL formula to automaton
+		List<BitSet> labelBS = new ArrayList<>();
+		LDBAWrapper da = constructDAForLTLFormula(mc, model, expr, labelBS);
+
+		// Build product of model and automaton
+		long startTime = System.nanoTime();
+		mainLog.println("\nConstructing MDP-LDBA product and finding accepting MECs...");
+		LDBAProduct<MDP> product = LDBAProductBuilder.constructProductModel(da, model, labelBS, statesOfInterest);
+		long estimatedTime = System.nanoTime() - startTime;
+		System.out.println("Construction Time: " + (estimatedTime * 0.000000001) + " seconds.");
+		mainLog.print("\n" + product.getProductModel().infoStringTable());
+
+		return product;
+	}
+}
diff --git a/prism/src/explicit/nxt/LDBAMDPModelChecker.java b/prism/src/explicit/nxt/LDBAMDPModelChecker.java
new file mode 100644
index 0000000..62ec705
--- /dev/null
+++ b/prism/src/explicit/nxt/LDBAMDPModelChecker.java
@@ -0,0 +1,1727 @@
+/*
+ * Copyright (C) 2016
+ *
+ * Dave Parker <david.parker@comlab.ox.ac.uk> (University of Oxford)
+ * Joachim Klein <klein@tcs.inf.tu-dresden.de> (TU Dresden)
+ * Salomon Sickert <sickert@in.tum.de> (TU München)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+import common.IterableBitSet;
+import explicit.*;
+import explicit.Model;
+import explicit.ProbModelChecker;
+import explicit.StateValues;
+import explicit.rewards.MCRewards;
+import explicit.rewards.MCRewardsFromMDPRewards;
+import explicit.rewards.MDPRewards;
+import explicit.rewards.Rewards;
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
+import parser.VarList;
+import parser.ast.Declaration;
+import parser.ast.DeclarationIntUnbounded;
+import parser.ast.Expression;
+import prism.*;
+import strat.MDStrategyArray;
+
+import java.util.*;
+
+/**
+ * Explicit-state model checker for Markov decision processes (MDPs).
+ */
+public class LDBAMDPModelChecker extends ProbModelChecker
+{
+	/**
+	 * Create a new MDPModelChecker, inherit basic state from parent (unless null).
+	 */
+	public LDBAMDPModelChecker(PrismComponent parent) throws PrismException
+	{
+		super(parent);
+	}
+	
+	// Model checking functions
+
+	@Override
+	protected StateValues checkProbPathFormulaLTL(Model model, Expression expr, boolean qual, MinMax minMax, BitSet statesOfInterest) throws PrismException
+	{
+		LDBALTLModelChecker mcLtl;
+		StateValues probsProduct, probs;
+		LDBAMDPModelChecker mcProduct;
+		LDBAProduct<MDP> product;
+
+		// For min probabilities, need to negate the formula
+		// (add parentheses to allow re-parsing if required)
+		if (minMax.isMin()) {
+			expr = Expression.Not(Expression.Parenth(expr.deepCopy()));
+		}
+
+		// For LTL model checking routines
+		mcLtl = new LDBALTLModelChecker(this);
+		product = mcLtl.constructProductMDP(this, (MDP) model, expr, statesOfInterest);
+		
+		// Output product, if required
+		if (getExportProductTrans()) {
+				mainLog.println("\nExporting product transition matrix to file \"" + getExportProductTransFilename() + "\"...");
+				product.getProductModel().exportToPrismExplicitTra(getExportProductTransFilename());
+		}
+		if (getExportProductStates()) {
+			mainLog.println("\nExporting product state space to file \"" + getExportProductStatesFilename() + "\"...");
+			PrismFileLog out = new PrismFileLog(getExportProductStatesFilename());
+			VarList newVarList = (VarList) modulesFile.createVarList().clone();
+			String daVar = "_da";
+			while (newVarList.getIndex(daVar) != -1) {
+				daVar = "_" + daVar;
+			}
+			newVarList.addVar(0, new Declaration(daVar, new DeclarationIntUnbounded()), 1, null);
+			product.getProductModel().exportStates(Prism.EXPORT_PLAIN, newVarList, out);
+			out.close();
+		}
+		
+		// Find accepting states + compute reachability probabilities
+		mainLog.println("\nComputing reachability probabilities...");
+		mcProduct = new LDBAMDPModelChecker(this);
+		mcProduct.inheritSettings(this);
+		probsProduct = StateValues.createFromDoubleArray(mcProduct.computeReachProbs(product.getProductModel(), product.getGoalStates(), false).soln, product.getProductModel());
+
+		// Subtract from 1 if we're model checking a negated formula for regular Pmin
+		if (minMax.isMin()) {
+			probsProduct.timesConstant(-1.0);
+			probsProduct.plusConstant(1.0);
+		}
+
+		// Mapping probabilities in the original model
+		probs = product.projectToOriginalModel(probsProduct);
+		probsProduct.clear();
+
+		return probs;
+	}
+
+	/**
+	 * Compute rewards for a co-safe LTL reward operator.
+	 */
+	protected StateValues checkRewardCoSafeLTL(Model model, Rewards modelRewards, Expression expr, MinMax minMax, BitSet statesOfInterest) throws PrismException
+	{
+		LDBALTLModelChecker mcLtl;
+		MDPRewards productRewards;
+		StateValues rewardsProduct, rewards;
+		LDBAMDPModelChecker mcProduct;
+		LDBAProduct<MDP> product;
+
+		// For LTL model checking routines
+		mcLtl = new LDBALTLModelChecker(this);
+
+		// Build product of MDP and automaton
+		product = mcLtl.constructProductMDP(this, (MDP)model, expr, statesOfInterest);
+		
+		// Adapt reward info to product model
+		productRewards = ((MDPRewards) modelRewards).liftFromModel(product);
+		
+		// Output product, if required
+		if (getExportProductTrans()) {
+				mainLog.println("\nExporting product transition matrix to file \"" + getExportProductTransFilename() + "\"...");
+				product.getProductModel().exportToPrismExplicitTra(getExportProductTransFilename());
+		}
+		if (getExportProductStates()) {
+			mainLog.println("\nExporting product state space to file \"" + getExportProductStatesFilename() + "\"...");
+			PrismFileLog out = new PrismFileLog(getExportProductStatesFilename());
+			VarList newVarList = (VarList) modulesFile.createVarList().clone();
+			String daVar = "_da";
+			while (newVarList.getIndex(daVar) != -1) {
+				daVar = "_" + daVar;
+			}
+			newVarList.addVar(0, new Declaration(daVar, new DeclarationIntUnbounded()), 1, null);
+			product.getProductModel().exportStates(Prism.EXPORT_PLAIN, newVarList, out);
+			out.close();
+		}
+		
+
+		mainLog.println("\nComputing reachability rewards... (NOT SUPPORTED)");
+		mcProduct = new LDBAMDPModelChecker(this);
+		mcProduct.inheritSettings(this);
+		rewardsProduct = StateValues.createFromDoubleArray(mcProduct.computeReachRewards(product.getProductModel(), productRewards, null, minMax.isMin()).soln, product.getProductModel());
+		
+		// Mapping rewards in the original model
+		rewards = product.projectToOriginalModel(rewardsProduct);
+		rewardsProduct.clear();
+		
+		return rewards;
+	}
+	
+	// Numerical computation functions
+
+	/**
+	 * Compute next=state probabilities.
+	 * i.e. compute the probability of being in a state in {@code target} in the next step.
+	 * @param mdp The MDP
+	 * @param target Target states
+	 * @param min Min or max probabilities (true=min, false=max)
+	 */
+	public ModelCheckerResult computeNextProbs(MDP mdp, BitSet target, boolean min) throws PrismException
+	{
+		ModelCheckerResult res = null;
+		int n;
+		double soln[], soln2[];
+		long timer;
+
+		timer = System.currentTimeMillis();
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// Create/initialise solution vector(s)
+		soln = Utils.bitsetToDoubleArray(target, n);
+		soln2 = new double[n];
+
+		// Next-step probabilities 
+		mdp.mvMultMinMax(soln, min, soln2, null, false, null);
+
+		// Return results
+		res = new ModelCheckerResult();
+		res.soln = soln2;
+		res.numIters = 1;
+		res.timeTaken = timer / 1000.0;
+		return res;
+	}
+
+	/**
+	 * Given a value vector x, compute the probability:
+	 *   v(s) = min/max sched [ Sum_s' P_sched(s,s')*x(s') ]  for s labeled with a,
+	 *   v(s) = 0   for s not labeled with a.
+	 *
+	 * Clears the StateValues object x.
+	 *
+	 * @param mdp the transition matrix
+	 * @param a the set of states labeled with a
+	 * @param x the value vector
+	 * @param min compute min instead of max
+	 */
+	public double[] computeRestrictedNext(MDP mdp, BitSet a, double[] x, boolean min)
+	{
+		int n;
+		double soln[];
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// initialized to 0.0
+		soln = new double[n];
+
+		// Next-step probabilities multiplication
+		// restricted to a states
+		mdp.mvMultMinMax(x, min, soln, a, false, null);
+
+		return soln;
+	}
+
+	/**
+	 * Compute reachability probabilities.
+	 * i.e. compute the min/max probability of reaching a state in {@code target}.
+	 * @param mdp The MDP
+	 * @param target Target states
+	 * @param min Min or max probabilities (true=min, false=max)
+	 */
+	public ModelCheckerResult computeReachProbs(MDP mdp, BitSet target, boolean min) throws PrismException
+	{
+		return computeReachProbs(mdp, null, target, min, null, null);
+	}
+
+	public ModelCheckerResult computeReachProbs(MDP mdp, SparseBitSet target, boolean min) throws PrismException
+	{
+		BitSet target2 = new BitSet();
+
+		for (int i = target.nextSetBit(0); i >= 0; i = target.nextSetBit(i + 1)) {
+			target2.set(i);
+		}
+
+		return computeReachProbs(mdp, null, target2, min, null, null);
+	}
+
+	/**
+	 * Compute until probabilities.
+	 * i.e. compute the min/max probability of reaching a state in {@code target},
+	 * while remaining in those in {@code remain}.
+	 * @param mdp The MDP
+	 * @param remain Remain in these states (optional: null means "all")
+	 * @param target Target states
+	 * @param min Min or max probabilities (true=min, false=max)
+	 */
+	public ModelCheckerResult computeUntilProbs(MDP mdp, BitSet remain, BitSet target, boolean min) throws PrismException
+	{
+		return computeReachProbs(mdp, remain, target, min, null, null);
+	}
+
+	/**
+	 * Compute reachability/until probabilities.
+	 * i.e. compute the min/max probability of reaching a state in {@code target},
+	 * while remaining in those in {@code remain}.
+	 * @param mdp The MDP
+	 * @param remain Remain in these states (optional: null means "all")
+	 * @param target Target states
+	 * @param min Min or max probabilities (true=min, false=max)
+	 * @param init Optionally, an initial solution vector (may 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).
+	 * Also, 'known' values cannot be passed for some solution methods, e.g. policy iteration.  
+	 */
+	public ModelCheckerResult computeReachProbs(MDP mdp, BitSet remain, BitSet target, boolean min, double init[], BitSet known) throws PrismException
+	{
+		ModelCheckerResult res = null;
+		BitSet no, yes;
+		int n, numYes, numNo;
+		long timer, timerProb0, timerProb1;
+		int strat[] = null;
+		// Local copy of setting
+		MDPSolnMethod mdpSolnMethod = this.mdpSolnMethod;
+
+		// Switch to a supported method, if necessary
+		if (mdpSolnMethod == MDPSolnMethod.LINEAR_PROGRAMMING) {
+			mdpSolnMethod = MDPSolnMethod.GAUSS_SEIDEL;
+			mainLog.printWarning("Switching to MDP solution method \"" + mdpSolnMethod.fullName() + "\"");
+		}
+
+		// Check for some unsupported combinations
+		if (mdpSolnMethod == MDPSolnMethod.VALUE_ITERATION && valIterDir == ValIterDir.ABOVE) {
+			if (!(precomp && prob0))
+				throw new PrismException("Precomputation (Prob0) must be enabled for value iteration from above");
+			if (!min)
+				throw new PrismException("Value iteration from above only works for minimum probabilities");
+		}
+		if (mdpSolnMethod == MDPSolnMethod.POLICY_ITERATION || mdpSolnMethod == MDPSolnMethod.MODIFIED_POLICY_ITERATION) {
+			if (known != null) {
+				throw new PrismException("Policy iteration methods cannot be passed 'known' values for some states");
+			}
+		}
+
+		// Start probabilistic reachability
+		timer = System.currentTimeMillis();
+		mainLog.println("\nStarting probabilistic reachability (" + (min ? "min" : "max") + ")...");
+
+		// Check for deadlocks in non-target state (because breaks e.g. prob1)
+		mdp.checkForDeadlocks(target);
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// Optimise by enlarging target set (if more info is available)
+		if (init != null && known != null && !known.isEmpty()) {
+			BitSet targetNew = (BitSet) target.clone();
+			for (int i : new IterableBitSet(known)) {
+				if (init[i] == 1.0) {
+					targetNew.set(i);
+				}
+			}
+			target = targetNew;
+		}
+
+		// If required, export info about target states 
+		if (getExportTarget()) {
+			BitSet bsInit = new BitSet(n);
+			for (int i = 0; i < n; i++) {
+				bsInit.set(i, mdp.isInitialState(i));
+			}
+			List<BitSet> labels = Arrays.asList(bsInit, target);
+			List<String> labelNames = Arrays.asList("init", "target");
+			mainLog.println("\nExporting target states info to file \"" + getExportTargetFilename() + "\"...");
+			exportLabels(mdp, labels, labelNames, Prism.EXPORT_PLAIN, new PrismFileLog(getExportTargetFilename()));
+		}
+
+		// If required, create/initialise strategy storage
+		// Set choices to -1, denoting unknown
+		// (except for target states, which are -2, denoting arbitrary)
+		if (genStrat || exportAdv) {
+			strat = new int[n];
+			for (int i = 0; i < n; i++) {
+				strat[i] = target.get(i) ? -2 : -1;
+			}
+		}
+
+		// Precomputation
+		timerProb0 = System.currentTimeMillis();
+		if (precomp && prob0) {
+			no = prob0(mdp, remain, target, min, strat);
+		} else {
+			no = new BitSet();
+		}
+		timerProb0 = System.currentTimeMillis() - timerProb0;
+		timerProb1 = System.currentTimeMillis();
+		if (precomp && prob1) {
+			yes = prob1(mdp, remain, target, min, strat);
+		} else {
+			yes = (BitSet) target.clone();
+		}
+		timerProb1 = System.currentTimeMillis() - timerProb1;
+
+		// Print results of precomputation
+		numYes = yes.cardinality();
+		numNo = no.cardinality();
+		mainLog.println("target=" + target.cardinality() + ", yes=" + numYes + ", no=" + numNo + ", maybe=" + (n - (numYes + numNo)));
+
+		// If still required, store strategy for no/yes (0/1) states.
+		// This is just for the cases max=0 and min=1, where arbitrary choices suffice (denoted by -2)
+		if (genStrat || exportAdv) {
+			if (min) {
+				for (int i = yes.nextSetBit(0); i >= 0; i = yes.nextSetBit(i + 1)) {
+					if (!target.get(i))
+						strat[i] = -2;
+				}
+			} else {
+				for (int i = no.nextSetBit(0); i >= 0; i = no.nextSetBit(i + 1)) {
+					strat[i] = -2;
+				}
+			}
+		}
+
+		// Compute probabilities (if needed)
+		if (numYes + numNo < n) {
+			switch (mdpSolnMethod) {
+			case VALUE_ITERATION:
+				res = computeReachProbsValIter(mdp, no, yes, min, init, known, strat);
+				break;
+			case GAUSS_SEIDEL:
+				res = computeReachProbsGaussSeidel(mdp, no, yes, min, init, known, strat);
+				break;
+			case POLICY_ITERATION:
+				res = computeReachProbsPolIter(mdp, no, yes, min, strat);
+				break;
+			case MODIFIED_POLICY_ITERATION:
+				res = computeReachProbsModPolIter(mdp, no, yes, min, strat);
+				break;
+			default:
+				throw new PrismException("Unknown MDP solution method " + mdpSolnMethod.fullName());
+			}
+		} else {
+			res = new ModelCheckerResult();
+			res.soln = Utils.bitsetToDoubleArray(yes, n);
+		}
+
+		// Finished probabilistic reachability
+		timer = System.currentTimeMillis() - timer;
+		mainLog.println("Probabilistic reachability took " + timer / 1000.0 + " seconds.");
+
+		// Store strategy
+		if (genStrat) {
+			res.strat = new MDStrategyArray(mdp, strat);
+		}
+		// Export adversary
+		if (exportAdv) {
+			// Prune strategy
+			restrictStrategyToReachableStates(mdp, strat);
+			// Export
+			PrismLog out = new PrismFileLog(exportAdvFilename);
+			new DTMCFromMDPMemorylessAdversary(mdp, strat).exportToPrismExplicitTra(out);
+			out.close();
+		}
+
+		// Update time taken
+		res.timeTaken = timer / 1000.0;
+		res.timeProb0 = timerProb0 / 1000.0;
+		res.timePre = (timerProb0 + timerProb1) / 1000.0;
+
+		return res;
+	}
+
+	/**
+	 * Prob0 precomputation algorithm.
+	 * i.e. determine the states of an MDP which, with min/max probability 0,
+	 * reach a state in {@code target}, while remaining in those in {@code remain}.
+	 * {@code min}=true gives Prob0E, {@code min}=false gives Prob0A. 
+	 * Optionally, for min only, store optimal (memoryless) strategy info for 0 states. 
+	 * @param mdp The MDP
+	 * @param remain Remain in these states (optional: null means "all")
+	 * @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)
+	 */
+	public BitSet prob0(MDP mdp, BitSet remain, BitSet target, boolean min, int strat[])
+	{
+		int n, iters;
+		BitSet u, soln, unknown;
+		boolean u_done;
+		long timer;
+
+		// Start precomputation
+		timer = System.currentTimeMillis();
+		mainLog.println("Starting Prob0 (" + (min ? "min" : "max") + ")...");
+
+		// Special case: no target states
+		if (target.cardinality() == 0) {
+			soln = new BitSet(mdp.getNumStates());
+			soln.set(0, mdp.getNumStates());
+			return soln;
+		}
+
+		// Initialise vectors
+		n = mdp.getNumStates();
+		u = new BitSet(n);
+		soln = new BitSet(n);
+
+		// Determine set of states actually need to perform computation for
+		unknown = new BitSet();
+		unknown.set(0, n);
+		unknown.andNot(target);
+		if (remain != null)
+			unknown.and(remain);
+
+		// Fixed point loop
+		iters = 0;
+		u_done = false;
+		// Least fixed point - should start from 0 but we optimise by
+		// starting from 'target', thus bypassing first iteration
+		u.or(target);
+		soln.or(target);
+		while (!u_done) {
+			iters++;
+			// Single step of Prob0
+			mdp.prob0step(unknown, u, min, soln);
+			// Check termination
+			u_done = soln.equals(u);
+			// u = soln
+			u.clear();
+			u.or(soln);
+		}
+
+		// Negate
+		u.flip(0, n);
+
+		// Finished precomputation
+		timer = System.currentTimeMillis() - timer;
+		mainLog.print("Prob0 (" + (min ? "min" : "max") + ")");
+		mainLog.println(" took " + iters + " iterations and " + timer / 1000.0 + " seconds.");
+
+		// If required, generate strategy. This is for min probs,
+		// so it can be done *after* the main prob0 algorithm (unlike for prob1).
+		// We simply pick, for all "no" states, the first choice for which all transitions stay in "no"
+		if (strat != null) {
+			for (int i = u.nextSetBit(0); i >= 0; i = u.nextSetBit(i + 1)) {
+				int numChoices = mdp.getNumChoices(i);
+				for (int k = 0; k < numChoices; k++) {
+					if (mdp.allSuccessorsInSet(i, k, u)) {
+						strat[i] = k;
+						continue;
+					}
+				}
+			}
+		}
+
+		return u;
+	}
+
+	/**
+	 * Prob1 precomputation algorithm.
+	 * i.e. determine the states of an MDP which, with min/max probability 1,
+	 * reach a state in {@code target}, while remaining in those in {@code remain}.
+	 * {@code min}=true gives Prob1A, {@code min}=false gives Prob1E. 
+	 * Optionally, for max only, store optimal (memoryless) strategy info for 1 states. 
+	 * @param mdp The MDP
+	 * @param remain Remain in these states (optional: null means "all")
+	 * @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)
+	 */
+	public BitSet prob1(MDP mdp, BitSet remain, BitSet target, boolean min, int strat[])
+	{
+		int n, iters;
+		BitSet u, v, soln, unknown;
+		boolean u_done, v_done;
+		long timer;
+
+		// Start precomputation
+		timer = System.currentTimeMillis();
+		mainLog.println("Starting Prob1 (" + (min ? "min" : "max") + ")...");
+
+		// Special case: no target states
+		if (target.cardinality() == 0) {
+			return new BitSet(mdp.getNumStates());
+		}
+
+		// Initialise vectors
+		n = mdp.getNumStates();
+		u = new BitSet(n);
+		v = new BitSet(n);
+		soln = new BitSet(n);
+
+		// Determine set of states actually need to perform computation for
+		unknown = new BitSet();
+		unknown.set(0, n);
+		unknown.andNot(target);
+		if (remain != null)
+			unknown.and(remain);
+
+		// Nested fixed point loop
+		iters = 0;
+		u_done = false;
+		// Greatest fixed point
+		u.set(0, n);
+		while (!u_done) {
+			v_done = false;
+			// Least fixed point - should start from 0 but we optimise by
+			// starting from 'target', thus bypassing first iteration
+			v.clear();
+			v.or(target);
+			soln.clear();
+			soln.or(target);
+			while (!v_done) {
+				iters++;
+				// Single step of Prob1
+				if (min)
+					mdp.prob1Astep(unknown, u, v, soln);
+				else
+					mdp.prob1Estep(unknown, u, v, soln, null);
+				// Check termination (inner)
+				v_done = soln.equals(v);
+				// v = soln
+				v.clear();
+				v.or(soln);
+			}
+			// Check termination (outer)
+			u_done = v.equals(u);
+			// u = v
+			u.clear();
+			u.or(v);
+		}
+
+		// If we need to generate a strategy, do another iteration of the inner loop for this
+		// We could do this during the main double fixed point above, but we would generate surplus
+		// strategy info for non-1 states during early iterations of the outer loop,
+		// which are not straightforward to remove since this method does not know which states
+		// already have valid strategy info from Prob0.
+		// Notice that we only need to look at states in u (since we already know the answer),
+		// so we restrict 'unknown' further 
+		unknown.and(u);
+		if (!min && strat != null) {
+			v_done = false;
+			v.clear();
+			v.or(target);
+			soln.clear();
+			soln.or(target);
+			while (!v_done) {
+				mdp.prob1Estep(unknown, u, v, soln, strat);
+				v_done = soln.equals(v);
+				v.clear();
+				v.or(soln);
+			}
+			u_done = v.equals(u);
+		}
+
+		// Finished precomputation
+		timer = System.currentTimeMillis() - timer;
+		mainLog.print("Prob1 (" + (min ? "min" : "max") + ")");
+		mainLog.println(" took " + iters + " iterations and " + timer / 1000.0 + " seconds.");
+
+		return u;
+	}
+
+	/**
+	 * Compute reachability probabilities using value iteration.
+	 * Optionally, store optimal (memoryless) strategy info. 
+	 * @param mdp The MDP
+	 * @param no Probability 0 states
+	 * @param yes Probability 1 states
+	 * @param min Min or max probabilities (true=min, false=max)
+	 * @param init Optionally, an initial solution vector (will be overwritten) 
+	 * @param known Optionally, a set of states for which the exact answer is known
+	 * @param strat Storage for (memoryless) strategy choice indices (ignored if null)
+	 * Note: if 'known' is specified (i.e. is non-null, 'init' must also be given and is used for the exact values.  
+	 */
+	protected ModelCheckerResult computeReachProbsValIter(MDP mdp, BitSet no, BitSet yes, boolean min, double init[], BitSet known, int strat[])
+			throws PrismException
+	{
+		ModelCheckerResult res;
+		BitSet unknown;
+		int i, n, iters;
+		double soln[], soln2[], tmpsoln[], initVal;
+		boolean done;
+		long timer;
+
+		// Start value iteration
+		timer = System.currentTimeMillis();
+		mainLog.println("Starting value iteration (" + (min ? "min" : "max") + ")...");
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// Create solution vector(s)
+		soln = new double[n];
+		soln2 = (init == null) ? new double[n] : init;
+
+		// Initialise solution vectors. 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 < n; i++)
+					soln[i] = soln2[i] = known.get(i) ? init[i] : yes.get(i) ? 1.0 : no.get(i) ? 0.0 : init[i];
+			} else {
+				for (i = 0; i < n; i++)
+					soln[i] = soln2[i] = yes.get(i) ? 1.0 : no.get(i) ? 0.0 : init[i];
+			}
+		} else {
+			for (i = 0; i < n; i++)
+				soln[i] = soln2[i] = yes.get(i) ? 1.0 : no.get(i) ? 0.0 : initVal;
+		}
+
+		// Determine set of states actually need to compute values for
+		unknown = new BitSet();
+		unknown.set(0, n);
+		unknown.andNot(yes);
+		unknown.andNot(no);
+		if (known != null)
+			unknown.andNot(known);
+
+		// Start iterations
+		iters = 0;
+		done = false;
+		while (!done && iters < maxIters) {
+			iters++;
+			// Matrix-vector multiply and min/max ops
+			mdp.mvMultMinMax(soln, min, soln2, unknown, false, strat);
+			// Check termination
+			done = PrismUtils.doublesAreClose(soln, soln2, termCritParam, termCrit == TermCrit.ABSOLUTE);
+			// Swap vectors for next iter
+			tmpsoln = soln;
+			soln = soln2;
+			soln2 = tmpsoln;
+		}
+
+		// Finished value iteration
+		timer = System.currentTimeMillis() - timer;
+		mainLog.print("Value iteration (" + (min ? "min" : "max") + ")");
+		mainLog.println(" took " + iters + " iterations and " + timer / 1000.0 + " seconds.");
+
+		// Non-convergence is an error (usually)
+		if (!done && errorOnNonConverge) {
+			String msg = "Iterative method did not converge within " + iters + " iterations.";
+			msg += "\nConsider using a different numerical method or increasing the maximum number of iterations";
+			throw new PrismException(msg);
+		}
+
+		// Return results
+		res = new ModelCheckerResult();
+		res.soln = soln;
+		res.numIters = iters;
+		res.timeTaken = timer / 1000.0;
+		return res;
+	}
+
+	/**
+	 * Compute reachability probabilities using Gauss-Seidel (including Jacobi-style updates).
+	 * @param mdp The MDP
+	 * @param no Probability 0 states
+	 * @param yes Probability 1 states
+	 * @param min Min or max probabilities (true=min, false=max)
+	 * @param init Optionally, an initial solution vector (will be overwritten) 
+	 * @param known Optionally, a set of states for which the exact answer is known
+	 * @param strat Storage for (memoryless) strategy choice indices (ignored if null)
+	 * Note: if 'known' is specified (i.e. is non-null, 'init' must also be given and is used for the exact values.  
+	 */
+	protected ModelCheckerResult computeReachProbsGaussSeidel(MDP mdp, BitSet no, BitSet yes, boolean min, double init[], BitSet known, int strat[])
+			throws PrismException
+	{
+		ModelCheckerResult res;
+		BitSet unknown;
+		int i, n, iters;
+		double soln[], initVal, maxDiff;
+		boolean done;
+		long timer;
+
+		// Start value iteration
+		timer = System.currentTimeMillis();
+		mainLog.println("Starting Gauss-Seidel (" + (min ? "min" : "max") + ")...");
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// Create solution vector
+		soln = (init == null) ? new double[n] : 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 < n; 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 < n; i++)
+					soln[i] = yes.get(i) ? 1.0 : no.get(i) ? 0.0 : init[i];
+			}
+		} else {
+			for (i = 0; i < n; i++)
+				soln[i] = yes.get(i) ? 1.0 : no.get(i) ? 0.0 : initVal;
+		}
+
+		// Determine set of states actually need to compute values for
+		unknown = new BitSet();
+		unknown.set(0, n);
+		unknown.andNot(yes);
+		unknown.andNot(no);
+		if (known != null)
+			unknown.andNot(known);
+
+		// Start iterations
+		iters = 0;
+		done = false;
+		while (!done && iters < maxIters) {
+			iters++;
+			// Matrix-vector multiply
+			maxDiff = mdp.mvMultGSMinMax(soln, min, unknown, termCrit == TermCrit.ABSOLUTE, strat);
+			// Check termination
+			done = maxDiff < termCritParam;
+		}
+
+		// Finished Gauss-Seidel
+		timer = System.currentTimeMillis() - timer;
+		mainLog.print("Gauss-Seidel");
+		mainLog.println(" took " + iters + " iterations and " + timer / 1000.0 + " seconds.");
+
+		// Non-convergence is an error (usually)
+		if (!done && errorOnNonConverge) {
+			String msg = "Iterative method did not converge within " + iters + " iterations.";
+			msg += "\nConsider using a different numerical method or increasing the maximum number of iterations";
+			throw new PrismException(msg);
+		}
+
+		// Return results
+		res = new ModelCheckerResult();
+		res.soln = soln;
+		res.numIters = iters;
+		res.timeTaken = timer / 1000.0;
+		return res;
+	}
+
+	/**
+	 * Compute reachability probabilities using policy iteration.
+	 * Optionally, store optimal (memoryless) strategy info. 
+	 * @param mdp: The MDP
+	 * @param no: Probability 0 states
+	 * @param yes: Probability 1 states
+	 * @param min: Min or max probabilities (true=min, false=max)
+	 * @param strat Storage for (memoryless) strategy choice indices (ignored if null)
+	 */
+	protected ModelCheckerResult computeReachProbsPolIter(MDP mdp, BitSet no, BitSet yes, boolean min, int strat[]) throws PrismException
+	{
+		ModelCheckerResult res;
+		int i, n, iters, totalIters;
+		double soln[], soln2[];
+		boolean done;
+		long timer;
+		DTMCModelChecker mcDTMC;
+		DTMC dtmc;
+
+		// Re-use solution to solve each new policy (strategy)?
+		boolean reUseSoln = true;
+
+		// Start policy iteration
+		timer = System.currentTimeMillis();
+		mainLog.println("Starting policy iteration (" + (min ? "min" : "max") + ")...");
+
+		// Create a DTMC model checker (for solving policies)
+		mcDTMC = new DTMCModelChecker(this);
+		mcDTMC.inheritSettings(this);
+		mcDTMC.setLog(new PrismDevNullLog());
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// Create solution vectors
+		soln = new double[n];
+		soln2 = new double[n];
+
+		// Initialise solution vectors.
+		for (i = 0; i < n; i++)
+			soln[i] = soln2[i] = yes.get(i) ? 1.0 : 0.0;
+
+		// If not passed in, create new storage for strategy and initialise
+		// Initial strategy just picks first choice (0) everywhere
+		if (strat == null) {
+			strat = new int[n];
+			for (i = 0; i < n; i++)
+				strat[i] = 0;
+		}
+		// Otherwise, just initialise for states not in yes/no
+		// (Optimal choices for yes/no should already be known)
+		else {
+			for (i = 0; i < n; i++)
+				if (!(no.get(i) || yes.get(i)))
+					strat[i] = 0;
+		}
+
+		// Start iterations
+		iters = totalIters = 0;
+		done = false;
+		while (!done) {
+			iters++;
+			// Solve induced DTMC for strategy
+			dtmc = new DTMCFromMDPMemorylessAdversary(mdp, strat);
+			res = mcDTMC.computeReachProbsGaussSeidel(dtmc, no, yes, reUseSoln ? soln : null, null);
+			soln = res.soln;
+			totalIters += res.numIters;
+			// Check if optimal, improve non-optimal choices
+			mdp.mvMultMinMax(soln, min, soln2, null, false, null);
+			done = true;
+			for (i = 0; i < n; i++) {
+				// Don't look at no/yes states - we may not have strategy info for them,
+				// so they might appear non-optimal
+				if (no.get(i) || yes.get(i))
+					continue;
+				if (!PrismUtils.doublesAreClose(soln[i], soln2[i], termCritParam, termCrit == TermCrit.ABSOLUTE)) {
+					done = false;
+					List<Integer> opt = mdp.mvMultMinMaxSingleChoices(i, soln, min, soln2[i]);
+					// Only update strategy if strictly better
+					if (!opt.contains(strat[i]))
+						strat[i] = opt.get(0);
+				}
+			}
+		}
+
+		// Finished policy iteration
+		timer = System.currentTimeMillis() - timer;
+		mainLog.print("Policy iteration");
+		mainLog.println(" took " + iters + " cycles (" + totalIters + " iterations in total) and " + 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.soln = soln;
+		res.numIters = totalIters;
+		res.timeTaken = timer / 1000.0;
+		return res;
+	}
+
+	/**
+	 * Compute reachability probabilities using modified policy iteration.
+	 * @param mdp: The MDP
+	 * @param no: Probability 0 states
+	 * @param yes: Probability 1 states
+	 * @param min: Min or max probabilities (true=min, false=max)
+	 * @param strat Storage for (memoryless) strategy choice indices (ignored if null)
+	 */
+	protected ModelCheckerResult computeReachProbsModPolIter(MDP mdp, BitSet no, BitSet yes, boolean min, int strat[]) throws PrismException
+	{
+		ModelCheckerResult res;
+		int i, n, iters, totalIters;
+		double soln[], soln2[];
+		boolean done;
+		long timer;
+		DTMCModelChecker mcDTMC;
+		DTMC dtmc;
+
+		// Start value iteration
+		timer = System.currentTimeMillis();
+		mainLog.println("Starting modified policy iteration (" + (min ? "min" : "max") + ")...");
+
+		// Create a DTMC model checker (for solving policies)
+		mcDTMC = new DTMCModelChecker(this);
+		mcDTMC.inheritSettings(this);
+		mcDTMC.setLog(new PrismDevNullLog());
+
+		// Limit iters for DTMC solution - this implements "modified" policy iteration
+		mcDTMC.setMaxIters(100);
+		mcDTMC.setErrorOnNonConverge(false);
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// Create solution vectors
+		soln = new double[n];
+		soln2 = new double[n];
+
+		// Initialise solution vectors.
+		for (i = 0; i < n; i++)
+			soln[i] = soln2[i] = yes.get(i) ? 1.0 : 0.0;
+
+		// If not passed in, create new storage for strategy and initialise
+		// Initial strategy just picks first choice (0) everywhere
+		if (strat == null) {
+			strat = new int[n];
+			for (i = 0; i < n; i++)
+				strat[i] = 0;
+		}
+		// Otherwise, just initialise for states not in yes/no
+		// (Optimal choices for yes/no should already be known)
+		else {
+			for (i = 0; i < n; i++)
+				if (!(no.get(i) || yes.get(i)))
+					strat[i] = 0;
+		}
+
+		// Start iterations
+		iters = totalIters = 0;
+		done = false;
+		while (!done) {
+			iters++;
+			// Solve induced DTMC for strategy
+			dtmc = new DTMCFromMDPMemorylessAdversary(mdp, strat);
+			res = mcDTMC.computeReachProbsGaussSeidel(dtmc, no, yes, soln, null);
+			soln = res.soln;
+			totalIters += res.numIters;
+			// Check if optimal, improve non-optimal choices
+			mdp.mvMultMinMax(soln, min, soln2, null, false, null);
+			done = true;
+			for (i = 0; i < n; i++) {
+				// Don't look at no/yes states - we don't store strategy info for them,
+				// so they might appear non-optimal
+				if (no.get(i) || yes.get(i))
+					continue;
+				if (!PrismUtils.doublesAreClose(soln[i], soln2[i], termCritParam, termCrit == TermCrit.ABSOLUTE)) {
+					done = false;
+					List<Integer> opt = mdp.mvMultMinMaxSingleChoices(i, soln, min, soln2[i]);
+					strat[i] = opt.get(0);
+				}
+			}
+		}
+
+		// Finished policy iteration
+		timer = System.currentTimeMillis() - timer;
+		mainLog.print("Modified policy iteration");
+		mainLog.println(" took " + iters + " cycles (" + totalIters + " iterations in total) and " + 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.soln = soln;
+		res.numIters = totalIters;
+		res.timeTaken = timer / 1000.0;
+		return res;
+	}
+
+	/**
+	 * Construct strategy information for min/max reachability probabilities.
+	 * (More precisely, list of indices of choices resulting in min/max.)
+	 * (Note: indices are guaranteed to be sorted in ascending order.)
+	 * @param mdp The MDP
+	 * @param state The state to generate strategy info for
+	 * @param target The set of target states to reach
+	 * @param min Min or max probabilities (true=min, false=max)
+	 * @param lastSoln Vector of values from which to recompute in one iteration 
+	 */
+	public List<Integer> probReachStrategy(MDP mdp, int state, BitSet target, boolean min, double lastSoln[]) throws PrismException
+	{
+		double val = mdp.mvMultMinMaxSingle(state, lastSoln, min, null);
+		return mdp.mvMultMinMaxSingleChoices(state, lastSoln, min, val);
+	}
+
+	/**
+	 * Compute bounded reachability probabilities.
+	 * i.e. compute the min/max probability of reaching a state in {@code target} within k steps.
+	 * @param mdp The MDP
+	 * @param target Target states
+	 * @param k Bound
+	 * @param min Min or max probabilities (true=min, false=max)
+	 */
+	public ModelCheckerResult computeBoundedReachProbs(MDP mdp, BitSet target, int k, boolean min) throws PrismException
+	{
+		return computeBoundedReachProbs(mdp, null, target, k, min, null, null);
+	}
+
+	/**
+	 * Compute bounded until probabilities.
+	 * i.e. compute the min/max probability of reaching a state in {@code target},
+	 * within k steps, and while remaining in states in {@code remain}.
+	 * @param mdp The MDP
+	 * @param remain Remain in these states (optional: null means "all")
+	 * @param target Target states
+	 * @param k Bound
+	 * @param min Min or max probabilities (true=min, false=max)
+	 */
+	public ModelCheckerResult computeBoundedUntilProbs(MDP mdp, BitSet remain, BitSet target, int k, boolean min) throws PrismException
+	{
+		return computeBoundedReachProbs(mdp, remain, target, k, min, null, null);
+	}
+
+	/**
+	 * Compute bounded reachability/until probabilities.
+	 * i.e. compute the min/max probability of reaching a state in {@code target},
+	 * within k steps, and while remaining in states in {@code remain}.
+	 * @param mdp The MDP
+	 * @param remain Remain in these states (optional: null means "all")
+	 * @param target Target states
+	 * @param k Bound
+	 * @param min Min or max probabilities (true=min, false=max)
+	 * @param init Optionally, an initial solution vector (may be overwritten) 
+	 * @param results Optional array of size k+1 to store (init state) results for each step (null if unused)
+	 */
+	public ModelCheckerResult computeBoundedReachProbs(MDP mdp, BitSet remain, BitSet target, int k, boolean min, double init[], double results[])
+			throws PrismException
+	{
+		ModelCheckerResult res = null;
+		BitSet unknown;
+		int i, n, iters;
+		double soln[], soln2[], tmpsoln[];
+		long timer;
+
+		// Start bounded probabilistic reachability
+		timer = System.currentTimeMillis();
+		mainLog.println("\nStarting bounded probabilistic reachability (" + (min ? "min" : "max") + ")...");
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// Create solution vector(s)
+		soln = new double[n];
+		soln2 = (init == null) ? new double[n] : init;
+
+		// Initialise solution vectors. Use passed in initial vector, if present
+		if (init != null) {
+			for (i = 0; i < n; i++)
+				soln[i] = soln2[i] = target.get(i) ? 1.0 : init[i];
+		} else {
+			for (i = 0; i < n; i++)
+				soln[i] = soln2[i] = target.get(i) ? 1.0 : 0.0;
+		}
+		// Store intermediate results if required
+		// (compute min/max value over initial states for first step)
+		if (results != null) {
+			// TODO: whether this is min or max should be specified somehow
+			results[0] = Utils.minMaxOverArraySubset(soln2, mdp.getInitialStates(), true);
+		}
+
+		// Determine set of states actually need to perform computation for
+		unknown = new BitSet();
+		unknown.set(0, n);
+		unknown.andNot(target);
+		if (remain != null)
+			unknown.and(remain);
+
+		// Start iterations
+		iters = 0;
+		while (iters < k) {
+			iters++;
+			// Matrix-vector multiply and min/max ops
+			mdp.mvMultMinMax(soln, min, soln2, unknown, false, null);
+			// Store intermediate results if required
+			// (compute min/max value over initial states for this step)
+			if (results != null) {
+				// TODO: whether this is min or max should be specified somehow
+				results[iters] = Utils.minMaxOverArraySubset(soln2, mdp.getInitialStates(), true);
+			}
+			// Swap vectors for next iter
+			tmpsoln = soln;
+			soln = soln2;
+			soln2 = tmpsoln;
+		}
+
+		// Finished bounded probabilistic reachability
+		timer = System.currentTimeMillis() - timer;
+		mainLog.print("Bounded probabilistic reachability (" + (min ? "min" : "max") + ")");
+		mainLog.println(" took " + iters + " iterations and " + timer / 1000.0 + " seconds.");
+
+		// Return results
+		res = new ModelCheckerResult();
+		res.soln = soln;
+		res.lastSoln = soln2;
+		res.numIters = iters;
+		res.timeTaken = timer / 1000.0;
+		res.timePre = 0.0;
+		return res;
+	}
+
+	/**
+	 * Compute expected cumulative (step-bounded) rewards.
+	 * i.e. compute the min/max reward accumulated within {@code k} steps.
+	 * @param mdp The MDP
+	 * @param mdpRewards The rewards
+	 * @param k Target states
+	 * @param min Min or max rewards (true=min, false=max)
+	 */
+	public ModelCheckerResult computeCumulativeRewards(MDP mdp, MDPRewards mdpRewards, int k, boolean min) throws PrismException
+	{
+		ModelCheckerResult res = null;
+		int i, n, iters;
+		long timer;
+		double soln[], soln2[], tmpsoln[];
+
+		// Start expected cumulative reward
+		timer = System.currentTimeMillis();
+		mainLog.println("\nStarting expected cumulative reward (" + (min ? "min" : "max") + ")...");
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// Create/initialise solution vector(s)
+		soln = new double[n];
+		soln2 = new double[n];
+		for (i = 0; i < n; i++)
+			soln[i] = soln2[i] = 0.0;
+
+		// Start iterations
+		iters = 0;
+		while (iters < k) {
+			iters++;
+			// Matrix-vector multiply and min/max ops
+			mdp.mvMultRewMinMax(soln, mdpRewards, min, soln2, null, false, null);
+			// Swap vectors for next iter
+			tmpsoln = soln;
+			soln = soln2;
+			soln2 = tmpsoln;
+		}
+
+		// Finished value iteration
+		timer = System.currentTimeMillis() - timer;
+		mainLog.print("Expected cumulative reward (" + (min ? "min" : "max") + ")");
+		mainLog.println(" took " + iters + " iterations and " + timer / 1000.0 + " seconds.");
+
+		// Return results
+		res = new ModelCheckerResult();
+		res.soln = soln;
+		res.numIters = iters;
+		res.timeTaken = timer / 1000.0;
+
+		return res;
+	}
+
+	/**
+	 * Compute expected reachability rewards.
+	 * @param mdp The MDP
+	 * @param mdpRewards The rewards
+	 * @param target Target states
+	 * @param min Min or max rewards (true=min, false=max)
+	 */
+	public ModelCheckerResult computeReachRewards(MDP mdp, MDPRewards mdpRewards, BitSet target, boolean min) throws PrismException
+	{
+		return computeReachRewards(mdp, mdpRewards, target, min, null, null);
+	}
+
+	/**
+	 * Compute expected reachability rewards.
+	 * i.e. compute the min/max reward accumulated to reach a state in {@code target}.
+	 * @param mdp The MDP
+	 * @param mdpRewards The rewards
+	 * @param target Target states
+	 * @param min Min or max rewards (true=min, false=max)
+	 * @param init Optionally, an initial solution vector (may 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).  
+	 * Also, 'known' values cannot be passed for some solution methods, e.g. policy iteration.  
+	 */
+	public ModelCheckerResult computeReachRewards(MDP mdp, MDPRewards mdpRewards, BitSet target, boolean min, double init[], BitSet known)
+			throws PrismException
+	{
+		ModelCheckerResult res = null;
+		BitSet inf;
+		int n, numTarget, numInf;
+		long timer, timerProb1;
+		int strat[] = null;
+		// Local copy of setting
+		MDPSolnMethod mdpSolnMethod = this.mdpSolnMethod;
+
+		// Switch to a supported method, if necessary
+		if (!(mdpSolnMethod == MDPSolnMethod.VALUE_ITERATION || mdpSolnMethod == MDPSolnMethod.GAUSS_SEIDEL || mdpSolnMethod == MDPSolnMethod.POLICY_ITERATION)) {
+			mdpSolnMethod = MDPSolnMethod.GAUSS_SEIDEL;
+			mainLog.printWarning("Switching to MDP solution method \"" + mdpSolnMethod.fullName() + "\"");
+		}
+
+		// Check for some unsupported combinations
+		if (mdpSolnMethod == MDPSolnMethod.POLICY_ITERATION) {
+			if (known != null) {
+				throw new PrismException("Policy iteration methods cannot be passed 'known' values for some states");
+			}
+		}
+		
+		// Start expected reachability
+		timer = System.currentTimeMillis();
+		mainLog.println("\nStarting expected reachability (" + (min ? "min" : "max") + ")...");
+
+		// Check for deadlocks in non-target state (because breaks e.g. prob1)
+		mdp.checkForDeadlocks(target);
+
+		// Store num states
+		n = mdp.getNumStates();
+		// Optimise by enlarging target set (if more info is available)
+		if (init != null && known != null && !known.isEmpty()) {
+			BitSet targetNew = (BitSet) target.clone();
+			for (int i : new IterableBitSet(known)) {
+				if (init[i] == 1.0) {
+					targetNew.set(i);
+				}
+			}
+			target = targetNew;
+		}
+
+		// If required, export info about target states 
+		if (getExportTarget()) {
+			BitSet bsInit = new BitSet(n);
+			for (int i = 0; i < n; i++) {
+				bsInit.set(i, mdp.isInitialState(i));
+			}
+			List<BitSet> labels = Arrays.asList(bsInit, target);
+			List<String> labelNames = Arrays.asList("init", "target");
+			mainLog.println("\nExporting target states info to file \"" + getExportTargetFilename() + "\"...");
+			exportLabels(mdp, labels, labelNames, Prism.EXPORT_PLAIN, new PrismFileLog(getExportTargetFilename()));
+		}
+
+		// If required, create/initialise strategy storage
+		// Set choices to -1, denoting unknown
+		// (except for target states, which are -2, denoting arbitrary)
+		if (genStrat || exportAdv || mdpSolnMethod == MDPSolnMethod.POLICY_ITERATION) {
+			strat = new int[n];
+			for (int i = 0; i < n; i++) {
+				strat[i] = target.get(i) ? -2 : -1;
+			}
+		}
+		
+		// Precomputation (not optional)
+		timerProb1 = System.currentTimeMillis();
+		inf = prob1(mdp, null, target, !min, strat);
+		inf.flip(0, n);
+		timerProb1 = System.currentTimeMillis() - timerProb1;
+		
+		// Print results of precomputation
+		numTarget = target.cardinality();
+		numInf = inf.cardinality();
+		mainLog.println("target=" + numTarget + ", inf=" + numInf + ", rest=" + (n - (numTarget + numInf)));
+
+		// If required, generate strategy for "inf" states.
+		if (genStrat || exportAdv || mdpSolnMethod == MDPSolnMethod.POLICY_ITERATION) {
+			if (min) {
+				// If min reward is infinite, all choices give infinity
+				// So the choice can be arbitrary, denoted by -2; 
+				for (int i = inf.nextSetBit(0); i >= 0; i = inf.nextSetBit(i + 1)) {
+					strat[i] = -2;
+				}
+			} else {
+				// If max reward is infinite, there is at least one choice giving infinity.
+				// So we pick, for all "inf" states, the first choice for which some transitions stays in "inf".
+				for (int i = inf.nextSetBit(0); i >= 0; i = inf.nextSetBit(i + 1)) {
+					int numChoices = mdp.getNumChoices(i);
+					for (int k = 0; k < numChoices; k++) {
+						if (mdp.someSuccessorsInSet(i, k, inf)) {
+							strat[i] = k;
+							continue;
+						}
+					}
+				}
+			}
+		}
+
+		// Compute rewards
+		switch (mdpSolnMethod) {
+		case VALUE_ITERATION:
+			res = computeReachRewardsValIter(mdp, mdpRewards, target, inf, min, init, known, strat);
+			break;
+		case GAUSS_SEIDEL:
+			res = computeReachRewardsGaussSeidel(mdp, mdpRewards, target, inf, min, init, known, strat);
+			break;
+		case POLICY_ITERATION:
+			res = computeReachRewardsPolIter(mdp, mdpRewards, target, inf, min, strat);
+			break;
+		default:
+			throw new PrismException("Unknown MDP solution method " + mdpSolnMethod.fullName());
+		}
+
+		// Store strategy
+		if (genStrat) {
+			res.strat = new MDStrategyArray(mdp, strat);
+		}
+		// Export adversary
+		if (exportAdv) {
+			// Prune strategy
+			restrictStrategyToReachableStates(mdp, strat);
+			// Export
+			PrismLog out = new PrismFileLog(exportAdvFilename);
+			new DTMCFromMDPMemorylessAdversary(mdp, strat).exportToPrismExplicitTra(out);
+			out.close();
+		}
+
+		// Finished expected reachability
+		timer = System.currentTimeMillis() - timer;
+		mainLog.println("Expected reachability took " + timer / 1000.0 + " seconds.");
+
+		// Update time taken
+		res.timeTaken = timer / 1000.0;
+		res.timePre = timerProb1 / 1000.0;
+
+		return res;
+	}
+
+	/**
+	 * Compute expected reachability rewards using value iteration.
+	 * Optionally, store optimal (memoryless) strategy info. 
+	 * @param mdp The MDP
+	 * @param mdpRewards The rewards
+	 * @param target Target states
+	 * @param inf States for which reward is infinite
+	 * @param min Min or max rewards (true=min, false=max)
+	 * @param init Optionally, an initial solution vector (will be overwritten) 
+	 * @param known Optionally, a set of states for which the exact answer is known
+	 * @param strat Storage for (memoryless) strategy choice indices (ignored if null)
+	 * Note: if 'known' is specified (i.e. is non-null, 'init' must also be given and is used for the exact values.
+	 */
+	protected ModelCheckerResult computeReachRewardsValIter(MDP mdp, MDPRewards mdpRewards, BitSet target, BitSet inf, boolean min, double init[], BitSet known, int strat[])
+			throws PrismException
+	{
+		ModelCheckerResult res;
+		BitSet unknown;
+		int i, n, iters;
+		double soln[], soln2[], tmpsoln[];
+		boolean done;
+		long timer;
+
+		// Start value iteration
+		timer = System.currentTimeMillis();
+		mainLog.println("Starting value iteration (" + (min ? "min" : "max") + ")...");
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// Create solution vector(s)
+		soln = new double[n];
+		soln2 = (init == null) ? new double[n] : init;
+
+		// Initialise solution vectors. Use (where available) the following in order of preference:
+		// (1) exact answer, if already known; (2) 0.0/infinity if in target/inf; (3) passed in initial value; (4) 0.0
+		if (init != null) {
+			if (known != null) {
+				for (i = 0; i < n; i++)
+					soln[i] = soln2[i] = known.get(i) ? init[i] : target.get(i) ? 0.0 : inf.get(i) ? Double.POSITIVE_INFINITY : init[i];
+			} else {
+				for (i = 0; i < n; i++)
+					soln[i] = soln2[i] = target.get(i) ? 0.0 : inf.get(i) ? Double.POSITIVE_INFINITY : init[i];
+			}
+		} else {
+			for (i = 0; i < n; i++)
+				soln[i] = soln2[i] = target.get(i) ? 0.0 : inf.get(i) ? Double.POSITIVE_INFINITY : 0.0;
+		}
+
+		// Determine set of states actually need to compute values for
+		unknown = new BitSet();
+		unknown.set(0, n);
+		unknown.andNot(target);
+		unknown.andNot(inf);
+		if (known != null)
+			unknown.andNot(known);
+
+		// Start iterations
+		iters = 0;
+		done = false;
+		while (!done && iters < maxIters) {
+			//mainLog.println(soln);
+			iters++;
+			// Matrix-vector multiply and min/max ops
+			mdp.mvMultRewMinMax(soln, mdpRewards, min, soln2, unknown, false, strat);
+			// Check termination
+			done = PrismUtils.doublesAreClose(soln, soln2, termCritParam, termCrit == TermCrit.ABSOLUTE);
+			// Swap vectors for next iter
+			tmpsoln = soln;
+			soln = soln2;
+			soln2 = tmpsoln;
+		}
+
+		// Finished value iteration
+		timer = System.currentTimeMillis() - timer;
+		mainLog.print("Value iteration (" + (min ? "min" : "max") + ")");
+		mainLog.println(" took " + iters + " iterations and " + timer / 1000.0 + " seconds.");
+
+		// Non-convergence is an error (usually)
+		if (!done && errorOnNonConverge) {
+			String msg = "Iterative method did not converge within " + iters + " iterations.";
+			msg += "\nConsider using a different numerical method or increasing the maximum number of iterations";
+			throw new PrismException(msg);
+		}
+
+		// Return results
+		res = new ModelCheckerResult();
+		res.soln = soln;
+		res.numIters = iters;
+		res.timeTaken = timer / 1000.0;
+		return res;
+	}
+
+	/**
+	 * Compute expected reachability rewards using Gauss-Seidel (including Jacobi-style updates).
+	 * Optionally, store optimal (memoryless) strategy info. 
+	 * @param mdp The MDP
+	 * @param mdpRewards The rewards
+	 * @param target Target states
+	 * @param inf States for which reward is infinite
+	 * @param min Min or max rewards (true=min, false=max)
+	 * @param init Optionally, an initial solution vector (will be overwritten) 
+	 * @param known Optionally, a set of states for which the exact answer is known
+	 * @param strat Storage for (memoryless) strategy choice indices (ignored if null)
+	 * Note: if 'known' is specified (i.e. is non-null, 'init' must also be given and is used for the exact values.
+	 */
+	protected ModelCheckerResult computeReachRewardsGaussSeidel(MDP mdp, MDPRewards mdpRewards, BitSet target, BitSet inf, boolean min, double init[],
+			BitSet known, int strat[]) throws PrismException
+	{
+		ModelCheckerResult res;
+		BitSet unknown;
+		int i, n, iters;
+		double soln[], maxDiff;
+		boolean done;
+		long timer;
+
+		// Start value iteration
+		timer = System.currentTimeMillis();
+		mainLog.println("Starting Gauss-Seidel (" + (min ? "min" : "max") + ")...");
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// Create solution vector(s)
+		soln = (init == null) ? new double[n] : init;
+
+		// Initialise solution vector. Use (where available) the following in order of preference:
+		// (1) exact answer, if already known; (2) 0.0/infinity if in target/inf; (3) passed in initial value; (4) 0.0
+		if (init != null) {
+			if (known != null) {
+				for (i = 0; i < n; i++)
+					soln[i] = known.get(i) ? init[i] : target.get(i) ? 0.0 : inf.get(i) ? Double.POSITIVE_INFINITY : init[i];
+			} else {
+				for (i = 0; i < n; i++)
+					soln[i] = target.get(i) ? 0.0 : inf.get(i) ? Double.POSITIVE_INFINITY : init[i];
+			}
+		} else {
+			for (i = 0; i < n; i++)
+				soln[i] = target.get(i) ? 0.0 : inf.get(i) ? Double.POSITIVE_INFINITY : 0.0;
+		}
+
+		// Determine set of states actually need to compute values for
+		unknown = new BitSet();
+		unknown.set(0, n);
+		unknown.andNot(target);
+		unknown.andNot(inf);
+		if (known != null)
+			unknown.andNot(known);
+
+		// Start iterations
+		iters = 0;
+		done = false;
+		while (!done && iters < maxIters) {
+			//mainLog.println(soln);
+			iters++;
+			// Matrix-vector multiply and min/max ops
+			maxDiff = mdp.mvMultRewGSMinMax(soln, mdpRewards, min, unknown, false, termCrit == TermCrit.ABSOLUTE, strat);
+			// Check termination
+			done = maxDiff < termCritParam;
+		}
+
+		// Finished Gauss-Seidel
+		timer = System.currentTimeMillis() - timer;
+		mainLog.print("Gauss-Seidel (" + (min ? "min" : "max") + ")");
+		mainLog.println(" took " + iters + " iterations and " + timer / 1000.0 + " seconds.");
+
+		// Non-convergence is an error (usually)
+		if (!done && errorOnNonConverge) {
+			String msg = "Iterative method did not converge within " + iters + " iterations.";
+			msg += "\nConsider using a different numerical method or increasing the maximum number of iterations";
+			throw new PrismException(msg);
+		}
+
+		// Return results
+		res = new ModelCheckerResult();
+		res.soln = soln;
+		res.numIters = iters;
+		res.timeTaken = timer / 1000.0;
+		return res;
+	}
+
+	/**
+	 * Compute expected reachability rewards using policy iteration.
+	 * The array {@code strat} is used both to pass in the initial strategy for policy iteration,
+	 * and as storage for the resulting optimal strategy (if needed).
+	 * Passing in an initial strategy is required when some states have infinite reward,
+	 * to avoid the possibility of policy iteration getting stuck on an infinite-value strategy.
+	 * @param mdp The MDP
+	 * @param mdpRewards The rewards
+	 * @param target Target states
+	 * @param inf States for which reward is infinite
+	 * @param min Min or max rewards (true=min, false=max)
+	 * @param strat Storage for (memoryless) strategy choice indices (ignored if null)
+	 */
+	protected ModelCheckerResult computeReachRewardsPolIter(MDP mdp, MDPRewards mdpRewards, BitSet target, BitSet inf, boolean min, int strat[])
+			throws PrismException
+	{
+		ModelCheckerResult res;
+		int i, n, iters, totalIters;
+		double soln[], soln2[];
+		boolean done;
+		long timer;
+		DTMCModelChecker mcDTMC;
+		DTMC dtmc;
+		MCRewards mcRewards;
+
+		// Re-use solution to solve each new policy (strategy)?
+		boolean reUseSoln = true;
+
+		// Start policy iteration
+		timer = System.currentTimeMillis();
+		mainLog.println("Starting policy iteration (" + (min ? "min" : "max") + ")...");
+
+		// Create a DTMC model checker (for solving policies)
+		mcDTMC = new DTMCModelChecker(this);
+		mcDTMC.inheritSettings(this);
+		mcDTMC.setLog(new PrismDevNullLog());
+
+		// Store num states
+		n = mdp.getNumStates();
+
+		// Create solution vector(s)
+		soln = new double[n];
+		soln2 = new double[n];
+
+		// Initialise solution vectors.
+		for (i = 0; i < n; i++)
+			soln[i] = soln2[i] = target.get(i) ? 0.0 : inf.get(i) ? Double.POSITIVE_INFINITY : 0.0;
+
+		// If not passed in, create new storage for strategy and initialise
+		// Initial strategy just picks first choice (0) everywhere
+		if (strat == null) {
+			strat = new int[n];
+			for (i = 0; i < n; i++)
+				strat[i] = 0;
+		}
+			
+		// Start iterations
+		iters = totalIters = 0;
+		done = false;
+		while (!done && iters < maxIters) {
+			iters++;
+			// Solve induced DTMC for strategy
+			dtmc = new DTMCFromMDPMemorylessAdversary(mdp, strat);
+			mcRewards = new MCRewardsFromMDPRewards(mdpRewards, strat);
+			res = mcDTMC.computeReachRewardsValIter(dtmc, mcRewards, target, inf, reUseSoln ? soln : null, null);
+			soln = res.soln;
+			totalIters += res.numIters;
+			// Check if optimal, improve non-optimal choices
+			mdp.mvMultRewMinMax(soln, mdpRewards, min, soln2, null, false, null);
+			done = true;
+			for (i = 0; i < n; i++) {
+				// Don't look at target/inf states - we may not have strategy info for them,
+				// so they might appear non-optimal
+				if (target.get(i) || inf.get(i))
+					continue;
+				if (!PrismUtils.doublesAreClose(soln[i], soln2[i], termCritParam, termCrit == TermCrit.ABSOLUTE)) {
+					done = false;
+					List<Integer> opt = mdp.mvMultRewMinMaxSingleChoices(i, soln, mdpRewards, min, soln2[i]);
+					// Only update strategy if strictly better
+					if (!opt.contains(strat[i]))
+						strat[i] = opt.get(0);
+				}
+			}
+		}
+
+		// Finished policy iteration
+		timer = System.currentTimeMillis() - timer;
+		mainLog.print("Policy iteration");
+		mainLog.println(" took " + iters + " cycles (" + totalIters + " iterations in total) and " + timer / 1000.0 + " seconds.");
+
+		// Return results
+		res = new ModelCheckerResult();
+		res.soln = soln;
+		res.numIters = totalIters;
+		res.timeTaken = timer / 1000.0;
+		return res;
+	}
+
+	/**
+	 * Construct strategy information for min/max expected reachability.
+	 * (More precisely, list of indices of choices resulting in min/max.)
+	 * (Note: indices are guaranteed to be sorted in ascending order.)
+	 * @param mdp The MDP
+	 * @param mdpRewards The rewards
+	 * @param state The state to generate strategy info for
+	 * @param target The set of target states to reach
+	 * @param min Min or max rewards (true=min, false=max)
+	 * @param lastSoln Vector of values from which to recompute in one iteration 
+	 */
+	public List<Integer> expReachStrategy(MDP mdp, MDPRewards mdpRewards, int state, BitSet target, boolean min, double lastSoln[]) throws PrismException
+	{
+		double val = mdp.mvMultRewMinMaxSingle(state, lastSoln, mdpRewards, min, null);
+		return mdp.mvMultRewMinMaxSingleChoices(state, lastSoln, mdpRewards, min, val);
+	}
+
+	/**
+	 * Restrict a (memoryless) strategy for an MDP, stored as an integer array of choice indices,
+	 * to the states of the MDP that are reachable under that strategy.  
+	 * @param mdp The MDP
+	 * @param strat The strategy
+	 */
+	public void restrictStrategyToReachableStates(MDP mdp, int strat[])
+	{
+		BitSet restrict = new BitSet();
+		BitSet explore = new BitSet();
+		// Get initial states
+		for (int is : mdp.getInitialStates()) {
+			restrict.set(is);
+			explore.set(is);
+		}
+		// Compute reachable states (store in 'restrict') 
+		boolean foundMore = true;
+		while (foundMore) {
+			foundMore = false;
+			for (int s = explore.nextSetBit(0); s >= 0; s = explore.nextSetBit(s + 1)) {
+				explore.set(s, false);
+				if (strat[s] >= 0) {
+					for (Int2DoubleMap.Entry e : mdp.getTransitions(s, strat[s])) {
+						int dest = e.getIntKey();
+						if (!restrict.get(dest)) {
+							foundMore = true;
+							restrict.set(dest);
+							explore.set(dest);
+						}
+					}
+				}
+			}
+		}
+		// Set strategy choice for non-reachable state to -1
+		int n = mdp.getNumStates();
+		for (int s = restrict.nextClearBit(0); s < n; s = restrict.nextClearBit(s + 1)) {
+			strat[s] = -3;
+		}
+	}
+
+	/**
+	 * Simple test program.
+	 */
+	public static void main(String args[])
+	{
+		LDBAMDPModelChecker mc;
+		MDPSimple mdp;
+		ModelCheckerResult res;
+		BitSet init, target;
+		Map<String, BitSet> labels;
+		boolean min = true;
+		try {
+			mc = new LDBAMDPModelChecker(null);
+			mdp = new MDPSimple();
+			mdp.buildFromPrismExplicit(args[0]);
+			//System.out.println(mdp);
+			labels = mc.loadLabelsFile(args[1]);
+			//System.out.println(labels);
+			init = labels.get("init");
+			target = labels.get(args[2]);
+			if (target == null)
+				throw new PrismException("Unknown label \"" + args[2] + "\"");
+			for (int i = 3; i < args.length; i++) {
+				if (args[i].equals("-min"))
+					min = true;
+				else if (args[i].equals("-max"))
+					min = false;
+				else if (args[i].equals("-nopre"))
+					mc.setPrecomp(false);
+			}
+			res = mc.computeReachProbs(mdp, target, min);
+			System.out.println(res.soln[init.nextSetBit(0)]);
+		} catch (PrismException e) {
+			System.out.println(e);
+		}
+	}
+}
diff --git a/prism/src/explicit/nxt/LDBAProduct.java b/prism/src/explicit/nxt/LDBAProduct.java
new file mode 100644
index 0000000..0d4b072
--- /dev/null
+++ b/prism/src/explicit/nxt/LDBAProduct.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016  (Salomon Sickert)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+import explicit.NondetModel;
+import explicit.Product;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+
+/**
+ * Make LTL product accessible as a Product
+ */
+public class LDBAProduct<M extends NondetModel> extends Product<M> {
+    private final Int2IntMap invMap;
+
+    private final SparseBitSet goalStates;
+
+    public LDBAProduct(M productModel, M originalModel, Int2IntMap invMap, SparseBitSet goalStates) {
+        super(productModel, originalModel);
+        this.invMap = invMap;
+        invMap.defaultReturnValue(-1);
+        this.goalStates = goalStates;
+    }
+
+    @Override
+    public int getAutomatonState(int productState) {
+        return invMap.get(productState);
+    }
+
+    public SparseBitSet getGoalStates() {
+        return goalStates;
+    }
+
+    @Override
+    public int getModelState(int productState) {
+        return invMap.get(productState);
+    }
+}
diff --git a/prism/src/explicit/nxt/LDBAProductBuilder.java b/prism/src/explicit/nxt/LDBAProductBuilder.java
new file mode 100644
index 0000000..6122705
--- /dev/null
+++ b/prism/src/explicit/nxt/LDBAProductBuilder.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2016
+ *
+ * Dave Parker <david.parker@comlab.ox.ac.uk> (University of Oxford)
+ * Joachim Klein <klein@tcs.inf.tu-dresden.de> (TU Dresden)
+ * Salomon Sickert <sickert@in.tum.de> (TU München)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+import explicit.*;
+import it.unimi.dsi.fastutil.ints.*;
+import parser.VarList;
+import parser.ast.Declaration;
+import parser.ast.DeclarationInt;
+import parser.ast.Expression;
+import prism.PrismException;
+import rabinizer.collections.valuationset.ValuationSet;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+/* Based on LDBAProductBuilder from explicit. */
+public final class LDBAProductBuilder {
+
+    private final LDBAWrapper da;
+    private final MDP model;
+    private final int daSize;
+    private final List<BitSet> labelBS;
+    private final BitSet statesOfInterest;
+
+    // Encoding:
+    // each state s' = <s, q> = s + (modelSize * q)
+    // s(s') = s' % modelSize
+    // q(s') = s' / modelSize
+    private int[] map;
+    private int trapState;
+
+    private LDBAProductBuilder(MDP m, LDBAWrapper da, List<BitSet> labelBS, BitSet statesOfInterest) {
+        model = m;
+        this.da = da;
+        daSize = da.size();
+
+        this.labelBS = labelBS;
+        this.statesOfInterest = statesOfInterest;
+        this.map = new int[m.getNumStates() * da.size()];
+        Arrays.fill(map, -1);
+        trapState = -1;
+    }
+
+    /**
+     * Lookup the product state
+     * @param s The model state
+     * @param q The LDBA state
+     * @return
+     */
+    private int getIndex(int s, int q) {
+        return (s * daSize) + q;
+    }
+
+    private void buildTrapState(MDPSimple prodModel) {
+        trapState = prodModel.addState();
+        prodModel.addChoice(trapState, new Distribution(trapState, 1.0d));
+    }
+
+    /**
+     * Construct the product of a DA and a model.
+     * @param da The DA
+     * @param model The model
+     * @param labelBS BitSets giving the set of states for each AP in the DA
+     * @param statesOfInterest the set of states for which values should be calculated (null = all states)
+     * @return The product model
+     */
+    public static LDBAProduct<MDP> constructProductModel(LDBAWrapper da, MDP model, List<BitSet> labelBS, BitSet statesOfInterest) throws PrismException
+    {
+        LDBAProductBuilder builder = new LDBAProductBuilder(model, da, labelBS, statesOfInterest);
+        return builder.build();
+    }
+
+    private LDBAProduct<MDP> build() throws PrismException {
+        MDPSimple prodModel = new MDPSimple();
+
+        if (model.getVarList() != null) {
+            VarList varList = model.getVarList();
+            // Create a (new, unique) name for the variable that will represent DA states
+            String daVar = "_da";
+            while (varList.getIndex(daVar) != -1) {
+                daVar = "_" + daVar;
+            }
+
+            VarList newVarList = (VarList) varList.clone();
+            Declaration decl = new Declaration(daVar, new DeclarationInt(Expression.Int(0), Expression.Int(Math.max(da.size() - 1, 1))));
+            newVarList.addVar(0, decl, 1, model.getConstantValues());
+            prodModel.setVarList(newVarList);
+        }
+
+        Deque<Point> queue = new ArrayDeque<>();
+
+        // We need results for all states of the original model in statesOfInterest
+        // We thus explore states of the product starting from these states.
+        // These are designated as initial states of the product model
+        // (a) to ensure reachability is done for these states; and
+        // (b) to later identify the corresponding product state for the original states
+        //     of interest
+
+        Int2IntMap initialStateInverseMapping = new Int2IntArrayMap(statesOfInterest.cardinality());
+
+        for (int s0 = statesOfInterest.nextSetBit(0); s0 >= 0; s0 = statesOfInterest.nextSetBit(s0+1)) {
+            // Find corresponding initial state in DA
+            int q0 = da.getEdgeDestByLabel(LDBAWrapper.getInitialState(), getLabel(s0));
+
+            if (q0 >= 0) {
+                int pi = getIndex(s0, q0);
+                int p0 = map[pi];
+
+                if (p0 == -1) {
+                    p0 = prodModel.addState();
+                    map[pi] = p0;
+                    queue.add(new Point(s0, q0));
+                    prodModel.addInitialState(p0);
+                    initialStateInverseMapping.put(p0, s0);
+                }
+                // TODO: detect "true" trap state.
+            } else if (trapState == -1) {
+                buildTrapState(prodModel);
+                prodModel.addInitialState(trapState);
+                initialStateInverseMapping.put(trapState, s0);
+            }
+        }
+
+        boolean initialComponentPresent = (da.getSmallestAcceptingState() > 0);
+        SparseBitSet goalStates = new SparseBitSet();
+        constructReachableStates(prodModel, queue, initialComponentPresent ? null : goalStates);
+
+        if (initialComponentPresent) {
+            // Build a mapping from state indices to states (s,q), encoded as (s + (modelSize * q))
+            int[] invMap = new int[prodModel.getNumStates()];
+            for (int i1 = da.getSmallestAcceptingState() - 1; i1 >= 0; i1--) {
+                int j = map[i1];
+
+                if (j != -1) {
+                    invMap[j] = i1;
+                }
+            }
+
+            // MECs have to be disjoint!
+            for (LightMEC mec : LightECComputer.computeMECs(prodModel, null)) {
+                for (int i = mec.states.nextSetBit(0); i >= 0; i = mec.states.nextSetBit(i + 1)) {
+                    // Get Model state
+                    int s0 = invMap[i] / daSize;
+                    // Get LDBA state.
+                    int q0 = invMap[i] % daSize;
+
+                    int[] js = da.getJumpTargets(q0);
+
+                    for (int j : js) {
+                        int pi = getIndex(s0, j);
+                        int p = map[pi];
+
+                        // Add state to model
+                        if (p == -1) {
+                            p = prodModel.addState();
+                            map[pi] = p;
+                            queue.add(new Point(s0, j));
+                            constructReachableStates(prodModel, queue, goalStates);
+                        }
+
+                        // Add transition
+                        prodModel.addChoice(i, new Distribution(p, 1.0d));
+                    }
+                }
+            }
+        }
+
+        // Clear memory
+        map = null;
+
+        // Copy model to the sparse model to improve performance
+        return new LDBAProduct<>(new MDPSparse(prodModel), model, initialStateInverseMapping, goalStates);
+    }
+
+    // Get BitSet representing APs (labels) satisfied by successor state s
+    private BitSet getLabel(int s) {
+        BitSet label = new BitSet();
+
+        ListIterator<BitSet> iterator = labelBS.listIterator();
+
+        while (iterator.hasNext()) {
+            if (iterator.next().get(s)) {
+                label.set(iterator.previousIndex());
+            }
+        }
+
+        return label;
+    }
+
+    private void constructReachableStates(MDPSimple prodModel, Deque<Point> queue, SparseBitSet goalStates) {
+        // local acceptance
+        List<Int2ObjectMap<BitSet>> buchi = new ArrayList<>();
+        SparseBitSet preMEC = null;
+
+        if (goalStates != null) {
+            preMEC = new SparseBitSet();
+            int size = da.getAcceptanceSize();
+
+            for (int i = 0; i < size; i++) {
+                buchi.add(new Int2ObjectLinkedOpenHashMap<>());
+            }
+        }
+
+        while (!queue.isEmpty()) {
+            Point current = queue.remove();
+
+            int s1 = current.x;
+            int q1 = current.y;
+            int p1 = map[getIndex(s1, q1)];
+
+            Map<BitSet, ValuationSet> acceptance = da.getAcceptance(q1);
+
+            if (preMEC != null) {
+                preMEC.set(p1);
+            }
+
+            // Go through transitions from state s_1 in original model
+            final int numChoices = model.getNumChoices(s1);
+
+            for (int j = 0; j < numChoices; j++) {
+                Distribution prodDistr = new Distribution();
+                BitSet accIndexes = new BitSet();
+
+                for (Int2DoubleMap.Entry e : model.getTransitions(s1, j)) {
+                    int s2 = e.getIntKey();
+                    BitSet label = getLabel(s2);
+                    int q2 = da.getEdgeDestByLabel(q1, label);
+                    int p2;
+
+                    if (q2 >= 0) {
+                        int pi = getIndex(s2, q2);
+                        p2 = map[pi];
+
+                        // Add state to model
+                        if (p2 == -1) {
+                            p2 = prodModel.addState();
+                            map[pi] = p2;
+                            queue.add(new Point(s2, q2));
+                        }
+
+                        // If there is an acceptance mapping, select all accepting edges
+                        if (acceptance != null) {
+                            acceptance.forEach((k, v) -> {
+                                if (v.contains(label)) {
+                                    accIndexes.or(k);
+                                }
+                            });
+                        }
+                    } else if (trapState == -1) {
+                        buildTrapState(prodModel);
+                        p2 = trapState;
+                    } else {
+                        p2 = trapState;
+                    }
+
+                    // Add transition
+                    prodDistr.add(p2, e.getDoubleValue());
+                }
+
+                for (int i = accIndexes.nextSetBit(0); i >= 0; i = accIndexes.nextSetBit(i+1)) {
+                    Int2ObjectMap<BitSet> map = buchi.get(i);
+                    BitSet actions = map.get(p1);
+
+                    if (actions == null) {
+                        actions = new BitSet();
+                        map.put(p1, actions);
+                    }
+
+                    actions.set(j);
+                }
+
+                prodModel.addChoice(p1, prodDistr);
+            }
+        }
+
+        if (goalStates != null && !preMEC.isEmpty()) {
+            SparseBitSet localGoalStates = LightAcceptingEC.findAcceptingECStates(prodModel, buchi, Collections.singleton(LightMEC.createMEC(prodModel, preMEC)));
+            goalStates.or(localGoalStates);
+        }
+    }
+}
diff --git a/prism/src/explicit/nxt/LDBAWrapper.java b/prism/src/explicit/nxt/LDBAWrapper.java
new file mode 100644
index 0000000..c1533bd
--- /dev/null
+++ b/prism/src/explicit/nxt/LDBAWrapper.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2016 (Salomon Sickert)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import ltl2ldba.AcceptingComponent;
+import ltl2ldba.DetLimitAutomaton;
+import rabinizer.automata.IState;
+import rabinizer.automata.Master;
+import rabinizer.collections.valuationset.ValuationSet;
+
+import java.util.*;
+
+public class LDBAWrapper {
+
+    private static final int[] EMPTY = new int[0];
+
+    private final DetLimitAutomaton a;
+    private final Object2IntMap<IState<?>> numbering;
+    private final List<IState<?>> map;
+    private final Int2ObjectMap<int[]> jumpCache;
+    private final int smallestAcceptingState;
+
+    public LDBAWrapper(DetLimitAutomaton a) {
+        this.a = a;
+
+        // The non-accepting trap state is stored as -1.
+        numbering = new Object2IntOpenHashMap<>(a.size());
+        numbering.defaultReturnValue(-2);
+        map = new ArrayList<>(a.size());
+
+        // Register initial state as state 0;
+        get(a.getInitialState());
+
+        if (a.initialComponent != null) {
+            a.initialComponent.getStates().forEach(this::get);
+            smallestAcceptingState = map.size();
+        } else {
+            smallestAcceptingState = 0;
+        }
+
+        a.acceptingComponent.getStates().forEach(this::get);
+        jumpCache = new Int2ObjectOpenHashMap<>();
+    }
+
+    public Map<BitSet, ValuationSet> getAcceptance(int i) {
+        IState<?> state = get(i);
+
+        if (state instanceof AcceptingComponent.State) {
+            AcceptingComponent.State s = (AcceptingComponent.State) state;
+            return s.getAcceptance();
+        }
+
+        return null;
+    }
+
+    public int getAcceptanceSize() {
+        return a.acceptingComponent.getAcceptanceSize();
+    }
+
+    /**
+     * Get the destination of the edge from state i with label lab
+     * @param i state
+     * @param label the label of the edge
+     * @return Returns -1 if no such edge is found.
+     */
+    public int getEdgeDestByLabel(int i, BitSet label){
+        IState<?> s = get(i);
+
+        if (s == null) {
+            return -1;
+        }
+
+        if (s instanceof Master.State) {
+            s = a.initialComponent.getSuccessor((Master.State) s, label);
+        } else {
+            s = a.acceptingComponent.getSuccessor((AcceptingComponent.State) s, label);
+        }
+
+        return get(s);
+    }
+
+    public static int getInitialState() {
+        return 0;
+    }
+
+    public int[] getJumpTargets(int i) {
+        int[] result = jumpCache.get(i);
+
+        if (result != null) {
+            return result;
+        }
+
+        IState<?> state = get(i);
+
+        if (state instanceof Master.State) {
+            Set<AcceptingComponent.State> targets = a.initialComponent.epsilonJumps.get((Master.State) state);
+
+            result = new int[targets.size()];
+            int c = 0;
+            for (AcceptingComponent.State target : targets) {
+                result[c] = get(target);
+                c++;
+            }
+
+            jumpCache.put(i, result);
+
+            return result;
+        }
+
+        return EMPTY;
+    }
+
+    public int getSmallestAcceptingState() {
+        return smallestAcceptingState;
+    }
+
+    public int size() {
+        return a.size();
+    }
+
+    @Override
+    public String toString() {
+        return a.toString();
+    }
+
+    private IState<?> get(int i) {
+        return map.get(i);
+    }
+
+    private int get(IState<?> state) {
+        if (state == null) {
+            return -1;
+        }
+
+        int i = numbering.getInt(state);
+
+        if (i == -2) {
+            i = numbering.size();
+            map.add(state);
+            numbering.put(state, i);
+        }
+
+        return i;
+    }
+}
diff --git a/prism/src/explicit/nxt/LightAcceptingEC.java b/prism/src/explicit/nxt/LightAcceptingEC.java
new file mode 100644
index 0000000..737d969
--- /dev/null
+++ b/prism/src/explicit/nxt/LightAcceptingEC.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016  (Salomon Sickert)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+import explicit.MDP;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import prism.PrismException;
+
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.List;
+
+public class LightAcceptingEC {
+
+    /**
+     * Use the given Büchi condition to identify goal states in the model. Restrict the search to the given preMECs.
+     * @param model The model MDP
+     * @param acceptance Generalised Büchi Acceptance
+     * @param preMECs MEC candidates that need to be refined.
+     * @return a set of states needed to be reached for acceptance
+     */
+    public static SparseBitSet findAcceptingECStates(MDP model, List<Int2ObjectMap<BitSet>> acceptance, Collection<LightMEC> preMECs) {
+        SparseBitSet allAcceptingStates = new SparseBitSet();
+
+        for (LightMEC mec : LightECComputer.computeMECs(model, preMECs)) {
+            boolean allSuccess = true;
+
+            for (Int2ObjectMap<BitSet> map : acceptance) {
+                boolean indexSuccess = false;
+
+                for (int i = mec.states.nextSetBit(0); i >= 0 && !indexSuccess; i = mec.states.nextSetBit(i + 1)) {
+                    BitSet actions = map.get(i);
+
+                    if (actions != null) {
+                        indexSuccess = actions.intersects(mec.actions.get(i));
+                    }
+                }
+
+                if (!indexSuccess) {
+                    allSuccess = false;
+                    break;
+                }
+            }
+
+            if (allSuccess) {
+                allAcceptingStates.or(mec.states);
+            }
+        }
+
+        return allAcceptingStates;
+    }
+
+}
diff --git a/prism/src/explicit/nxt/LightECComputer.java b/prism/src/explicit/nxt/LightECComputer.java
new file mode 100644
index 0000000..4d2ef16
--- /dev/null
+++ b/prism/src/explicit/nxt/LightECComputer.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016  (Salomon Sickert)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+import explicit.MDP;
+
+import java.util.*;
+
+/**
+ * Explicit maximal end component computer for a nondeterministic model such as an MDP.
+ * Implements a variation of the algorithm from p.48 of:
+ * Luca de Alfaro. Formal Verification of Probabilistic Systems. Ph.D. thesis, Stanford University (1997)
+ */
+public class LightECComputer {
+
+	/**
+	 * Compute MECs for the given MDP.
+	 * @param model
+	 * @param preMECs if null all reachable states are considered.
+     * @return A list of all MECs obtained from refining the preMECs.
+     */
+	public static List<LightMEC> computeMECs(MDP model, Collection<LightMEC> preMECs) {
+		if (preMECs == null) {
+			SparseBitSet bs = new SparseBitSet();
+			bs.set(0, model.getNumStates());
+			preMECs = new ArrayList<>();
+			preMECs.add(LightMEC.createMEC(model, bs));
+		}
+
+		List<LightMEC> mecs = new ArrayList<>();
+		Deque<LightMEC> workList = new ArrayDeque<>(preMECs);
+
+		while (!workList.isEmpty()) {
+			LightMEC mec = workList.remove();
+			List<LightMEC> refinedMECs = LightSCCComputerTarjan.refineMEC(model, mec);
+
+			if (!refinedMECs.isEmpty()) {
+				LightMEC refinedMEC = refinedMECs.get(0);
+
+				if (mec.equals(refinedMEC)) {
+					mecs.add(refinedMEC);
+				} else {
+					workList.addAll(refinedMECs);
+				}
+			}
+		}
+
+		return mecs;
+	}
+}
diff --git a/prism/src/explicit/nxt/LightMEC.java b/prism/src/explicit/nxt/LightMEC.java
new file mode 100644
index 0000000..6653a9c
--- /dev/null
+++ b/prism/src/explicit/nxt/LightMEC.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016  (Salomon Sickert)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+import explicit.Distribution;
+import explicit.MDPSimple;
+import explicit.NondetModel;
+import it.unimi.dsi.fastutil.ints.*;
+
+import java.util.*;
+
+/**
+ * LightMEC describes a MEC for a give model. It does not store the model itself. Only the states belonging to the MEC and
+ * the allowed actions.
+ */
+public class LightMEC {
+    public final SparseBitSet states;
+    public final Int2ObjectMap<BitSet> actions;
+    
+    private LightMEC(SparseBitSet s, Int2ObjectMap<BitSet> a) {
+        states = s;
+        actions = a;
+    }
+
+    public static LightMEC createMEC(MDPSimple model, SparseBitSet states) {
+        Int2ObjectMap<BitSet> actions = new Int2ObjectOpenHashMap<>(states.cardinality());
+        boolean changed = true;
+
+        while (changed) {
+            changed = false;
+
+            for (int i = states.nextSetBit(0); i >= 0; i = states.nextSetBit(i + 1)) {
+                BitSet act = actions.get(i);
+
+                Distribution[] distributions = model.trans.get(i);
+
+                if (distributions != null) {
+                    if (act == null) {
+                        act = new BitSet();
+                        act.set(0, distributions.length);
+                        actions.put(i, act);
+                    }
+
+                    for (int j = act.nextSetBit(0); j >= 0; j = act.nextSetBit(j + 1)) {
+                        if (!distributions[j].isSubsetOf(states)) {
+                            act.clear(j);
+                        }
+                    }
+                }
+
+                if (act == null || act.isEmpty()) {
+                    states.clear(i);
+                    actions.remove(i);
+                    changed = true;
+                }
+            }
+        }
+
+        return new LightMEC(states, actions);
+    }
+
+    public static LightMEC createMEC(NondetModel model, SparseBitSet states) {
+        if (model instanceof MDPSimple) {
+            return createMEC((MDPSimple) model, states);
+        }
+
+        Int2ObjectMap<BitSet> actions = new Int2ObjectOpenHashMap<>(states.cardinality());
+        boolean changed = true;
+
+        while (changed) {
+            changed = false;
+
+            for (int i = states.nextSetBit(0); i >= 0; i = states.nextSetBit(i + 1)) {
+                BitSet act = actions.get(i);
+
+                final int choices = model.getNumChoices(i);
+
+                if (act == null) {
+                    act = new BitSet();
+                    act.set(0, choices);
+                    actions.put(i, act);
+                }
+
+                for (int j = act.nextSetBit(0); j >= 0; j = act.nextSetBit(j + 1)) {
+                    if (!model.allSuccessorsInSet(i, j, states)) {
+                        act.clear(j);
+                    }
+                }
+
+                if (act.isEmpty()) {
+                    states.clear(i);
+                    actions.remove(i);
+                    changed = true;
+                }
+            }
+        }
+
+        return new LightMEC(states, actions);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        LightMEC lightMEC = (LightMEC) o;
+        return Objects.equals(states, lightMEC.states);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(states);
+    }
+}
diff --git a/prism/src/explicit/nxt/LightSCCComputerTarjan.java b/prism/src/explicit/nxt/LightSCCComputerTarjan.java
new file mode 100644
index 0000000..d4d2884
--- /dev/null
+++ b/prism/src/explicit/nxt/LightSCCComputerTarjan.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016  (Salomon Sickert)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+import explicit.MDP;
+import it.unimi.dsi.fastutil.ints.*;
+
+import java.util.*;
+
+/**
+ * Tarjan's SCC algorithm operating on an MDP.
+ */
+public class LightSCCComputerTarjan
+{
+	private final MDP model;
+	private final Int2ObjectMap<BitSet> allowedActions;
+	private final List<LightMEC> refinedMECs = new ArrayList<>();
+
+	private int index = 0;
+
+	private final IntStack stack;
+	private final SparseBitSet onStack;
+    private final SparseBitSet todo;
+	private final Int2IntMap lowlinks;
+	private final Int2IntMap indices;
+
+	private LightSCCComputerTarjan(MDP model, LightMEC mec) {
+		this.model = model;
+		this.allowedActions = mec.actions;
+
+        todo = mec.states.clone();
+		stack = new IntArrayList();
+		onStack = new SparseBitSet();
+		lowlinks = new Int2IntOpenHashMap();
+		lowlinks.defaultReturnValue(-1);
+		indices = new Int2IntOpenHashMap();
+		indices.defaultReturnValue(-1);
+	}
+
+	public static List<LightMEC> refineMEC(MDP model, LightMEC mec) {
+		LightSCCComputerTarjan sccc = new LightSCCComputerTarjan(model, mec);
+		sccc.refineMEC();
+		return sccc.refinedMECs;
+	}
+
+	private void refineMEC() {
+		for (int i = todo.nextSetBit(0); i >= 0; i = todo.nextSetBit(i+1)) {
+			tarjan(i);
+		}
+		refinedMECs.removeIf(mecs -> mecs.states.isEmpty());
+	}
+
+	// TODO: Replace recurive calls by while-loop and an int call stack.
+	private void tarjan(int id) {
+		int idIndex = index;
+		indices.put(id, index);
+		lowlinks.put(id, index);
+		index++;
+		stack.push(id);
+		onStack.set(id);
+        todo.clear(id);
+
+		BitSet actions = allowedActions.get(id);
+
+		for (int action = actions.nextSetBit(0); action >= 0; action = actions.nextSetBit(action+1)) {
+			IntIterator successorIterator = model.getSuccessorsIterator(id, action);
+
+			while (successorIterator.hasNext()) {
+				int e = successorIterator.nextInt();
+
+				if (todo.get(e)) {
+					tarjan(e);
+					int eLowlink = lowlinks.get(e);
+					if (eLowlink < lowlinks.get(id)) {
+						lowlinks.put(id, eLowlink);
+					}
+				} else if (onStack.get(e)) {
+                    int eIndex = indices.get(e);
+                    if (eIndex < lowlinks.get(id)) {
+                        lowlinks.put(id, eIndex);
+                    }
+                }
+			}
+		}
+
+		if (lowlinks.get(id) == idIndex) {
+			int n = -1;
+			SparseBitSet scc = new SparseBitSet();
+
+			while (!stack.isEmpty() && n != idIndex) {
+				n = stack.popInt();
+				lowlinks.remove(n);
+                indices.remove(n);
+				onStack.clear(n);
+				scc.set(n);
+			}
+
+			refinedMECs.add(LightMEC.createMEC(model, scc));
+		}
+	}
+}
diff --git a/prism/src/explicit/nxt/NxtCLI.java b/prism/src/explicit/nxt/NxtCLI.java
new file mode 100644
index 0000000..3d8e69a
--- /dev/null
+++ b/prism/src/explicit/nxt/NxtCLI.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016  (Salomon Sickert)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+import explicit.PrismExplicit;
+import parser.ast.ModulesFile;
+import parser.ast.PropertiesFile;
+import parser.ast.Property;
+import prism.*;
+import simulator.ModulesFileModelGenerator;
+
+import java.io.File;
+
+public class NxtCLI {
+
+    private static void printHelp() {
+        System.out.println("Usage: ./ProMoChiBA.sh model.nm property.pctl");
+    }
+
+    public static void main(String... args) throws Exception {
+        try {
+            PrismLog mainLog = new PrismPrintStreamLog(System.out);
+            ModulesFile modulesFile = Prism.parseModelFile(new File(args[0]));
+            modulesFile.setUndefinedConstants(null);
+            PropertiesFile propertiesFile = Prism.parsePropertiesFile(modulesFile, new File(args[1]));
+            propertiesFile.setUndefinedConstants(null);
+            PrismExplicit pe = new PrismExplicit(mainLog, new PrismSettings());
+            explicit.Model modelExpl = pe.buildModel(modulesFile, new ModulesFileModelGenerator(modulesFile, pe));
+
+            for (Property prop : propertiesFile.properties) {
+                System.out.println("-----------------------------------------------------");
+                System.gc();
+                pe.modelCheck(modelExpl, modulesFile, propertiesFile, prop.getExpression());
+            }
+        } catch (Exception ex) {
+            printHelp();
+            System.out.println();
+            System.out.println("Oops, something went terribly wrong! Please have a look at the usage example.");
+            System.out.println("Reason: " + ex.toString());
+            ex.printStackTrace();
+        }
+    }
+}
\ No newline at end of file
diff --git a/prism/src/explicit/nxt/NxtCLITest.java b/prism/src/explicit/nxt/NxtCLITest.java
new file mode 100644
index 0000000..d9df2eb
--- /dev/null
+++ b/prism/src/explicit/nxt/NxtCLITest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016  (Salomon Sickert)
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package explicit.nxt;
+
+public class NxtCLITest {
+
+    /**
+     * Simple test program.
+     */
+    public static void main(String args[]) throws Exception
+    {
+        //Thread.sleep(1000 * 30);
+        NxtCLI.main("/Users/sickert/Applications/prism-4.3-osx64/examples/mutual/mutual5.nm", "/Users/sickert/Documents/workspace/prism/prism-examples/mutual/mutual_paper.pctl");
+    }
+}
\ No newline at end of file
diff --git a/prism/src/explicit/nxt/SparseBitSet.java b/prism/src/explicit/nxt/SparseBitSet.java
new file mode 100644
index 0000000..14ad0e4
--- /dev/null
+++ b/prism/src/explicit/nxt/SparseBitSet.java
@@ -0,0 +1,2965 @@
+// Obtained from: https://github.com/brettwooldridge/SparseBitSet
+// License: Apache License 2.0
+
+// CHECKSTYLE:OFF
+package explicit.nxt;
+
+/*- This software is the work of Paladin Software International, Incorporated,
+ *  based upon previous work done for and by Sun Microsystems, Inc. */
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+// TODO: Make as much as possible static!
+
+/**
+ *  This class implements a set of bits that grows as needed. Each bit of the
+ *  bit set represents a <code>boolean</code> value. The values of a
+ *  <code>SparseBitSet</code> are indexed by nonnegative integers.
+ *  Individual indexed values may be examined, set, cleared, or modified by
+ *  logical operations. One <code>SparseBitSet</code> or logical value may be
+ *  used to modify the contents of (another) <code>SparseBitSet</code> through
+ *  logical <b>AND</b>, logical <b>inclusive OR</b>, logical <b>exclusive
+ *  OR</b>, and <b>And NOT</b> operations over all or part of the bit sets.
+ *  <p>
+ *  All values in a bit set initially have the value <code>false</code>.
+ *  <p>
+ *  Every bit set has a current size, which is the number of bits of space
+ *  <i>nominally</i> in use by the bit set from the first set bit to just after
+ *  the last set bit. The length of the bit set effectively tells the position
+ *  available after the last bit of the SparseBitSet.
+ *  <p>
+ *  The maximum cardinality of a <code>SparseBitSet</code> is
+ *  <code>Integer.MAX_VALUE</code>, which means the bits of a
+ *  <code>SparseBitSet</code> are labelled <code>
+ *  0</code>&nbsp;..&nbsp;<code>Integer.MAX_VALUE&nbsp;&minus;&nbsp;1</code>.
+ *  After the last set bit of a <code>SparseBitSet</code>, any attempt to find
+ *  a subsequent bit (<i>getNextSetBit</i>()), will return an value of &minus;1.
+ *  If an attempt is made to use <i>getNextClearBit</i>(), and all the bits are
+ *  set from the starting postion of the search to the bit labelled
+ *  <code>Integer.MAX_VALUE&nbsp;&minus;&nbsp;1</code>, then similarly &minus;1
+ *  will be returned.
+ *  <p>
+ *  Unless otherwise noted, passing a null parameter to any of the methods in
+ *  a <code>SparseBitSet</code> will result in a
+ *  <code>NullPointerException</code>.
+ *  <p>
+ *  A <code>SparseBitSet</code> is not safe for multithreaded use without
+ *  external synchronization.
+ *
+ * @author      Bruce K. Haddon
+ * @author      Arthur van Hoff
+ * @author      Michael McCloskey
+ * @author      Martin Buchholz
+ * @version     1.0, 2009-03-17
+ * @since       1.6
+ */
+public class SparseBitSet implements Cloneable, Serializable
+{
+    /*  My apologies for listing all the additional authors, but concepts, code,
+        and even comments have been re-used in this class definition from code in
+        the JDK that was written and/or maintained by these people. I owe a debt,
+        which I acknowledge. But they are in no way responsible for what ever
+        misuse I have made of their work.
+                                                        Bruce K. Haddon
+
+        The representation of a SparseBitSet is packed into "words", and the words
+        are stored in arrays which are here referred to as "blocks." Blocks are
+        accessed by two levels of indirection from the master "level 1" (whole)
+        set array through second level arrays called "areas" (making the blocks
+        "level3"). A "word" is a long, consisting of 64 bits, requiring six address
+        bits to select a bit within a word (and this is considered in places to be
+        a "level4"). This choice of a long for "word" is determined purely by
+        performance concerns, and is built into the implementation in a deep way,
+        because the references to blocks are always of the form "long[]." (This
+        does not mean that blocks could not be changed to arrays of ints, but it
+        would take some extensive work.)
+
+        The fact that there are three levels is also deeply involved in the
+        scanning algorithms, meaning that the accesses are always nested three
+        deep. Again, the change this might be a large amount of work. On the
+        other hand, these three levels have proven, so far, to provide adequate
+        speed, and an storage efficient way to deal with sparseness.
+
+        For simplicity, the level3 blocks and the level2 areas are always "full"
+        size, i.e., LENGTH3 and LENGTH2 respectively, and for consistency, the
+        fourth level is of length LENGTH4. The level1 structure is of variable
+        length (as this may save scanning several thousand null pointers, and
+        careful consideration must be taken of this at all times, in particular,
+        when choosing to increase the size (see resize()). The only place where
+        this array is reduced in size is when a clone is made, in which case it is
+        created with the smallest size, and allowed to grow as entries are copied
+        to it. That all the arrays are kept to power-of-2 sizes is a programming
+        convenience (permitting shifts and  masks).
+
+        Whenever possible, a level 3 block that contains no bits (all the words are
+        zero, is discarded, and its reference is replaced by a null value.
+        Similarly, level 2 areas that contain only null pointers (to level 3
+        blocks) are discarded, and their references replaced by null values. This
+        is the "normalized" condition, but does not have to be nor is strictly
+        enforced. The operations still work if the representation is partially or
+        totally "denormalized." In particular, the methods that deal with single
+        bits (setting, flipping, clearing, etc.) do not attempt to normalize the
+        set, in the interests of speed. However, when a set is scanned as the
+        resultant set of some operation, then, in most cases, the set will be
+        normalized--the exception being level2 areas that are not completly scanned
+        in a particular pass.
+
+        The sizes of the blocks and areas has been the result of some investigation
+        with varying sizes, and the sizes selected appear to represent a reasonable
+        "sweet" spot. There is, of course, no guarantee that these are the best
+        for all possible situations, but, given that not having these the same
+        for all bit sets would be hopelessly complex (bad enough as is), these
+        values appear to be a fair compromise. */
+
+    /**
+     *  This value controls for format of the toString() output.
+     * @see #toStringCompaction(int)
+     */
+    protected transient int compactionCount;
+
+    /**
+     *  The compaction count default.
+     */
+    static int compactionCountDefault = 2; // Note: this is not final!
+
+    /**
+     *  The storage for this SparseBitSet. The <i>i</i>th bit is stored in a word
+     *  represented by a long value, and is at bit position <code>i % 64</code>
+     *  within that word (where bit position 0 refers to the least significant bit
+     *  and 63 refers to the most significant bit).
+     *  <p>
+     *  The words are organized into blocks, and the blocks are accessed by two
+     *  additional levels of array indexing.
+     */
+    protected transient long[][][] bits;
+
+    /**
+     *  For the current size of the bits array, this is the maximum possible
+     *  length of the bit set, i.e., the index of the last possible bit, plus one.
+     *  Note: this not the value returned by <i>length</i>().
+     * @see #resize(int)
+     * @see #length()
+     */
+    protected transient int bitsLength;
+
+    //==============================================================================
+    //  The critical parameters. These are set up so that the compiler may
+    //  pre-compute all the values as compile-time constants.
+    //==============================================================================
+    /**
+     *  The number of bits in a long value.
+     */
+    protected static final int LENGTH4 = Long.SIZE;
+
+    /**
+     *  The number of bits in a positive integer, and the size of permitted index
+     *  of a bit in the bit set.
+     */
+    protected static final int INDEX_SIZE = Integer.SIZE - 1;
+
+    /**
+     *  The label (index) of a bit in the bit set is essentially broken into
+     *  4 "levels". Respectively (from the least significant end), level4, the
+     *  address within word, the address within a level3 block, the address within
+     *  a level2 area, and the level1 address of that area within the set.
+     *
+     *  LEVEL4 is the number of bits of the level4 address (number of bits need
+     *  to address the bits in a long)
+     */
+    protected static final int LEVEL4 = 6;
+
+    /**
+     *  LEVEL3 is the number of bits of the level3 address.
+     */
+    protected static final int LEVEL3 = 5; // Do not change!
+    /**
+     *  LEVEL2 is the number of bits of the level2 address.
+     */
+    protected static final int LEVEL2 = 5; // Do not change!
+    /**
+     *  LEVEL1 is the number of bits of the level1 address.
+     */
+    protected static final int LEVEL1 = INDEX_SIZE - LEVEL2 - LEVEL3 - LEVEL4;
+
+    /**
+     *  MAX_LENGTH1 is the maximum number of entries in the level1 set array.
+     */
+    protected static final int MAX_LENGTH1 = 1 << LEVEL1;
+
+    /**
+     *  LENGTH2 is the number of entries in the any level2 area.
+     */
+    protected static final int LENGTH2 = 1 << LEVEL2;
+
+    /**
+     *  LENGTH3 is the number of entries in the any level3 block.
+     */
+    protected static final int LENGTH3 = 1 << LEVEL3;
+
+    /**
+     *  The shift to create the word index. (I.e., move it to the right end)
+     */
+    protected static final int SHIFT3 = LEVEL4;
+
+    /**
+     *  MASK3 is the mask to extract the LEVEL3 address from a word index
+     *  (after shifting by SHIFT3).
+     */
+    protected static final int MASK3 = LENGTH3 - 1;
+
+    /**
+     *  SHIFT2 is the shift to bring the level2 address (from the word index) to
+     *  the right end (i.e., after shifting by SHIFT3).
+     */
+    protected static final int SHIFT2 = LEVEL3;
+
+    /**
+     *  UNIT is the greatest number of bits that can be held in one level1 entry.
+     *  That is, bits per word by words per level3 block by blocks per level2 area.
+     */
+
+    protected static final int UNIT = LENGTH2 * LENGTH3 * LENGTH4;
+
+    /**
+     *  MASK2 is the mask to extract the LEVEL2 address from a word index
+     *  (after shifting by SHIFT3 and SHIFT2).
+     */
+    protected static final int MASK2 = LENGTH2 - 1;
+
+    /**
+     *  SHIFT1 is the shift to bring the level1 address (from the word index) to
+     *  the right end (i.e., after shifting by SHIFT3).
+     */
+    protected static final int SHIFT1 = LEVEL2 + LEVEL3;
+
+    /**
+     *  Holds reference to the cache of statistics values computed by the
+     *  UpdateStrategy
+     * @see SparseBitSet.Cache
+     * @see SparseBitSet.UpdateStrategy
+     */
+    protected transient Cache cache;
+
+    //=============================================================================
+    //  Stack structures used for recycling blocks
+    //=============================================================================
+    // private static final int STACK_SIZE = 1000;
+
+    // private static final long[][] stack = new long[STACK_SIZE][LENGTH3];
+
+    // private static int stackIndex = 0;
+
+    /**
+     *  A spare level 3 block is kept for use when scanning. When a target block
+     *  is needed, and there is not already one in the bit set, the spare is
+     *  provided. If non-zero values are placed into this block, it is moved to the
+     *  resulting set, and a new spare is acquired. Note: a new spare needs to
+     *  be allocated when the set is cloned (so that the spare is not shared
+     *  between two sets).
+     */
+    protected transient long[] spare;
+
+    /** An empty level 3 block is kept for use when scanning. When a source block
+     *  is needed, and there is not already one in the corresponding bit set, the
+     *  ZERO_BLOCK is used (as a read-only block). It is a source of zero values
+     *  so that code does not have to test for a null level3 block. This is a
+     *  static block shared everywhere.
+     */
+    static final long[] ZERO_BLOCK = new long[LENGTH3];
+
+    /*  Programming notes:
+
+        i, j, and k are used to hold values that are actual bit indices (i.e.,
+        the index (label) of the bit within the user's view of the bit set).
+
+        u, v, and w, are used to hold values that refer to the indices of the
+        words in the set array that are used to hold the bits (with 64 bits per
+        word). These variable names, followed by 1, 2, or 3, refer to the component
+        "level" parts of the complete word index.
+
+        word (where used) is a potential entry to or from a block, containing 64
+        bits of the bit set. The prefixes a, b, result, etc., refer to the bit
+        sets from which these are coming or going. Without a prefix, or with the
+        prefix "a," the set in question is "this" set (see next paragraph).
+
+        Operations are conceived to be in the form a.op(b), thus in the discussion
+        (not in the public Javadoc documentation) the two sets are referred to a
+        "a" and "b", where the set referred to by "this" is usually set a.
+        Hence, reference to set a is usually implicit, but set b will usually be
+        explicit. Variables beginning with these letters hold values relevant to
+        the corresponding set, and, in particular, these letters followed by
+        1, 2, and 3 are used to refer to the corresponding (current) level1,
+        level3 area, and level3 block, arrays.
+
+        The resizing of the table takes place as necessary. In this regard, it is
+        worth noting that the table is grown, but never shrunk (except in a new
+        object formed by cloning).
+
+        Similarly, care it taken to ensure that any supplied reference to a bit
+        set (other than this) has an opportunity to fail for being null before
+        any other set (including this) has its state changed. For the most
+        part, this is allowed to happen "naturally," but the Strategies incorporate
+        an explicit check when necessary.
+
+        There is a amount of (almost) repetitive scanning code in many of the
+        "singe bit" methods. The intent is that these methods for SparseBitSet be
+        as small and as fast as possible.
+
+        For the scanning of complete sets, or for ranges within complete sets,
+        all of the scanning logic is built into one (somewhat enormous) method,
+        setScanner(). This contains all the considerations for matching up
+        corresponding level 3 blocks (if they exist), and then uses a Strategy
+        object to do the processing on those level3 blocks. This keeps all
+        the scanning and optimization logic in one place, and the Strategies are
+        reasonably simple (see the definition of AbstractStrategy for a discussion
+        of the tasks that must be defined therein).
+
+        The test for index i (the first index in all cases) being in range is
+        rather perverse, but the idea was to keep the actual number of comparisons
+        to a minimum, hence the check is for "(i + 1) < 1". This is almost but not
+        quite equivalent to "i < 0", although it is for all values of i except
+        i=Integer.MAX_VALUE. In this latter case, (i + 1) "overflows" to
+        -(Integer.MAX_VALUE + 1), and thus appears to be less than 1, and thus the
+        check picks up the other disallowed case. Let us hope the compiler never
+        gets smart enough to try to do the apparent optimisation! */
+
+    /**
+     *  Constructor for a new (sparse) bit set. All bits initially are effectively
+     *  <code>false</code>. This is a internal constructor that collects all the
+     *  needed actions to initialise the bit set.
+     *  <p>
+     *  The capacity is taken to be a <i>suggestion</i> for a size of the bit set,
+     *  in bits. An appropiate table size (a power of two) is then determined and
+     *  used. The size will be grown as needed to accomodate any bits addressed
+     *  during the use of the bit set.
+     *
+     * @param       capacity a size in terms of bits
+     * @param       compactionCount the compactionCount to be inherited (for
+     *              internal generation)
+     * @exception   NegativeArraySizeException if the specified initial size
+     *              is negative
+     * @since       1.6
+     */
+    protected SparseBitSet(int capacity, int compactionCount)
+            throws NegativeArraySizeException
+    {
+        /*  Array size is computed based on this being a capacity given in bits. */
+        if (capacity < 0) // capacity can't be negative -- could only come from
+            throw new NegativeArraySizeException( //  an erroneous user given
+                                                 "(requested capacity=" + capacity + ") < 0"); // nbits value
+        resize(capacity - 1); //  Resize takes last usable index
+        this.compactionCount = compactionCount;
+        /*  Ensure there is a spare level 3 block for the use of the set scanner.*/
+        constructorHelper();
+        statisticsUpdate();
+    }
+
+    /**
+     *  Constructs an empty bit set with the default initial size.
+     *  Initially all bits are effectively <code>false</code>.
+     *
+     * @since       1.6
+     */
+    public SparseBitSet()
+    {
+        /*   By requesting 1 bit, will actually get UNIT number of bits. */
+        this(1, compactionCountDefault);
+    }
+
+    /**
+     *  Creates a bit set whose initial size is large enough to efficiently
+     *  represent bits with indices in the range <code>0</code> through
+     *  at least <code>nbits-1</code>. Initially all bits are effectively
+     *  <code>false</code>.
+     *  <p>
+     *  No guarantees are given for how large or small the actual object will be.
+     *  The setting of bits above the given range is permitted (and will perhaps
+     *  eventually cause resizing).
+     *
+     * @param       nbits the initial provisional length of the SparseBitSet
+     * @throws      java.lang.NegativeArraySizeException if the specified initial
+     *              length is negative
+     * @see         #SparseBitSet()
+     * @since       1.6
+     */
+    public SparseBitSet(int nbits) throws NegativeArraySizeException
+    {
+        this(nbits, compactionCountDefault);
+    }
+
+    /**
+     *  Performs a logical <b>AND</b> of the addressed target bit with the argument
+     *  value. This bit set is modified so that the addressed bit has the value
+     *  <code>true</code> if and only if it both initially had the value
+     *  <code>true</code> and the argument value is also <code>true</code>.
+     *
+     * @param       i a bit index
+     * @param       value a boolean value to <b>AND</b> with that bit
+     * @exception   IndexOutOfBoundsException if the specified index is negative
+     *              or equal to Integer.MAX_VALUE
+     * @since       1.6
+     */
+    public void and(int i, boolean value) throws IndexOutOfBoundsException
+    {
+        if ((i + 1) < 1)
+            throw new IndexOutOfBoundsException("i=" + i);
+        if (!value)
+            clear(i);
+    }
+
+    /**
+     *  Performs a logical <b>AND</b> of this target bit set with the argument bit
+     *  set within the given range of bits. Within the range, this bit set is
+     *  modified so that each bit in it has the value <code>true</code> if and only
+     *  if it both initially had the value <code>true</code> and the corresponding
+     *  bit in the bit set argument also had the value <code>true</code>. Outside
+     *  the range, this set is not changed.
+     *
+     * @param       i index of the first bit to be included in the operation
+     * @param       j index after the last bit to included in the operation
+     * @param       b a SparseBitSet
+     * @exception   IndexOutOfBoundsException if <code>i</code> is negative or
+     *              equal to Integer.MAX_VALUE, or {@code j} is negative,
+     *              or {@code i} is larger than <code>j</code>
+     * @since       1.6
+     */
+
+    public void and(int i, int j, SparseBitSet b) throws IndexOutOfBoundsException
+    {
+        setScanner(i, j, b, andStrategy);
+    }
+
+    /**
+     *  Performs a logical <b>AND</b> of this target bit set with the argument bit
+     *  set. This bit set is modified so that each bit in it has the value
+     *  <code>true</code> if and only if it both initially had the value
+     *  <code>true</code> and the corresponding bit in the bit set argument also
+     *  had the value <code>true</code>.
+     *
+     * @param       b a SparseBitSet
+     * @since       1.6
+     */
+    public void and(SparseBitSet b)
+    {
+        nullify(Math.min(bits.length, b.bits.length)); // Optimisation
+        setScanner(0, Math.min(bitsLength, b.bitsLength), b, andStrategy);
+    }
+
+    /**
+     *  Performs a logical <b>AND</b> of the two given <code>SparseBitSet</code>s.
+     *  The returned <code>SparseBitSet</code> is created so that each bit in it
+     *  has the value <code>true</code> if and only if both the given sets
+     *  initially had the corresponding bits <code>true</code>, otherwise
+     *  <code>false</code>.
+     *
+     * @param       a a SparseBitSet
+     * @param       b another SparseBitSet
+     * @return      a new SparseBitSet representing the <b>AND</b> of the two sets
+     * @since       1.6
+     */
+    public static SparseBitSet and(SparseBitSet a, SparseBitSet b)
+    {
+        final SparseBitSet result = a.clone();
+        result.and(b);
+        return result;
+    }
+
+    /**
+     *  Performs a logical <b>AndNOT</b> of the addressed target bit with the
+     *  argument value. This bit set is modified so that the addressed bit has the
+     *  value <code>true</code> if and only if it both initially had the value
+     *  <code>true</code> and the argument value is <code>false</code>.
+     *
+     * @param       i a bit index
+     * @param       value a boolean value to AndNOT with that bit
+     * @exception   IndexOutOfBoundsException if the specified index is negative
+     *              or equal to Integer.MAX_VALUE
+     * @since       1.6
+     */
+    public void andNot(int i, boolean value)
+    {
+        if ((i + 1) < 1)
+            throw new IndexOutOfBoundsException("i=" + i);
+        if (value)
+            clear(i);
+    }
+
+    /**
+     *  Performs a logical <b>AndNOT</b> of this target bit set with the argument
+     *  bit set within the given range of bits. Within the range, this bit set is
+     *  modified so that each bit in it has the value <code>true</code> if and only
+     *  if it both initially had the value <code>true</code> and the corresponding
+     *  bit in the bit set argument has the value <code>false</code>. Outside
+     *  the range, this set is not changed.
+     *
+     * @param       i index of the first bit to be included in the operation
+     * @param       j index after the last bit to included in the operation
+     * @param       b the SparseBitSet with which to mask this SparseBitSet
+     * @exception   IndexOutOfBoundsException if <code>i</code> is negative or
+     *              equal to Integer.MAX_VALUE, or <code>j</code> is negative,
+     *              or <code>i</code> is larger than <code>j</code>
+     * @since       1.6
+     */
+    public void andNot(int i, int j, SparseBitSet b)
+            throws IndexOutOfBoundsException
+    {
+        setScanner(i, j, b, andNotStrategy);
+    }
+
+    /**
+     *  Performs a logical <b>AndNOT</b> of this target bit set with the argument
+     *  bit set. This bit set is modified so that each bit in it has the value
+     *  <code>true</code> if and only if it both initially had the value
+     *  <code>true</code> and the corresponding bit in the bit set argument has
+     *  the value <code>false</code>.
+     *
+     * @param       b the SparseBitSet with which to mask this SparseBitSet
+     * @since       1.6
+     */
+    public void andNot(SparseBitSet b)
+    {
+        setScanner(0, Math.min(bitsLength, b.bitsLength), b, andNotStrategy);
+    }
+
+    /**
+     *  Creates a bit set from thie first <code>SparseBitSet</code> whose
+     *  corresponding bits are cleared by the set bits of the second
+     *  <code>SparseBitSet</code>. The resulting bit set is created so that a bit
+     *  in it has the value <code>true</code> if and only if the corresponding bit
+     *  in the <code>SparseBitSet</code> of the first is set, and that same
+     *  corresponding bit is not set in the <code>SparseBitSet</code> of the second
+     *  argument.
+     *
+     * @param a     a SparseBitSet
+     * @param b     another SparseBitSet
+     * @return      a new SparseBitSet representing the <b>AndNOT</b> of the
+     *              two sets
+     * @since       1.6
+     */
+    public static SparseBitSet andNot(SparseBitSet a, SparseBitSet b)
+    {
+        final SparseBitSet result = a.clone();
+        result.andNot(b);
+        return result;
+    }
+
+    /**
+     *  Returns the number of bits set to <code>true</code> in this
+     *  <code>SparseBitSet</code>.
+     *
+     * @return      the number of bits set to true in this SparseBitSet
+     * @since       1.6
+     */
+    public int cardinality()
+    {
+        statisticsUpdate(); // Update size, cardinality and length values
+        return cache.cardinality;
+    }
+
+    /**
+     *  Sets the bit at the specified index to <code>false</code>.
+     *
+     * @param       i a bit index.
+     * @exception   IndexOutOfBoundsException if the specified index is negative
+     *              or equal to Integer.MAX_VALUE.
+     * @since       1.6
+     */
+    public void clear(int i)
+    {
+        /*  In the interests of speed, no check is made here on whether the
+            level3 block goes to all zero. This may be found and corrected
+            in some later operation. */
+        if ((i + 1) < 1)
+            throw new IndexOutOfBoundsException("i=" + i);
+        if (i > bitsLength)
+            return;
+        final int w = i >> SHIFT3;
+        long[][] a2;
+        if ((a2 = bits[w >> SHIFT1]) == null)
+            return;
+        long[] a3;
+        if ((a3 = a2[(w >> SHIFT2) & MASK2]) == null)
+            return;
+        a3[w & MASK3] &= ~(1L << i); //  Clear the indicated bit
+        cache.hash = 0; //  Invalidate size, etc., 
+    }
+
+    /**
+     *  Sets the bits from the specified <code>i</code> (inclusive) to the
+     *  specified <code>j</code> (exclusive) to <code>false</code>.
+     *
+     * @param       i index of the first bit to be cleared
+     * @param       j index after the last bit to be cleared
+     * @exception   IndexOutOfBoundsException if <code>i</code> is negative or
+     *              equal to Integer.MAX_VALUE, or <code>j</code> is negative,
+     *              or <code>i</code> is larger than <code>j</code>
+     * @since       1.6
+     */
+    public void clear(int i, int j) throws IndexOutOfBoundsException
+    {
+        setScanner(i, j, null, clearStrategy);
+    }
+
+    /** 
+     *  Sets all of the bits in this <code>SparseBitSet</code> to
+     *  <code>false</code>.
+     *
+     * @since       1.6
+     */
+    public void clear()
+    {
+        /*  This simply resets to null all the entries in the set. */
+        nullify(0);
+    }
+
+    /**
+     *  Cloning this <code>SparseBitSet</code> produces a new
+     *  <code>SparseBitSet</code> that is <i>equal</i>() to it. The clone of the
+     *  bit set is another bit set that has exactly the same bits set to
+     *  <code>true</code> as this bit set.
+     *  <p>
+     *  Note: the actual space allocated to the clone tries to minimise the actual
+     *  amount of storage allocated to hold the bits, while still trying to
+     *  keep access to the bits being a rapid as possible. Since the space
+     *  allocated to a <code>SparseBitSet</code> is not normally decreased,
+     *  replacing a bit set by its clone may be a way of both managing memory
+     *  consumption and improving the rapidity of access.
+     *
+     * @return      a clone of this SparseBitSet
+     * @since       1.6
+     */
+    @Override
+    public SparseBitSet clone()
+    {
+        try
+        {
+            final SparseBitSet result = (SparseBitSet) super.clone();
+            /*  Clear out the shallow copy of the set array (which contains just
+                copies of the references from this set), and then replace these
+                by a deep copy (created by a "copy" from the set being cloned . */
+            result.bits = null;
+            result.resize(1);
+            /*  Ensure the clone is not sharing a copy of a spare block with
+                the cloned set, nor the cache set, nor any of the visitors (which
+                are linked to their parent object) (Not all visitors actually use
+                this link to their containing object, but they are reset here just
+                in case of  future changes). */
+            result.constructorHelper(); //  also creates the copyStrategy
+            result.setScanner(0, bitsLength, this, result.copyStrategy);
+            return result;
+        }
+        catch (CloneNotSupportedException ex)
+        {
+            /*  This code has not been unit tested. Inspection offers hope
+                that is will work, but it likely never to be used. */
+            throw new InternalError(ex.getMessage());
+        }
+    }
+
+    /**
+     *  Compares this object against the specified object. The result is
+     *  <code>true</code> if and only if the argument is not <code>null</code> 
+     *  and is a <code>SparseBitSet</code> object that has exactly the same bits 
+     *  set to <code>true</code> as this bit set. That is, for every nonnegative
+     *  <code>i</code> indexing a bit in the set,
+     *  <pre>((SparseBitSet)obj).get(i) == this.get(i)</pre>
+     *  must be true.
+     *
+     * @param       obj the Object with which to compare
+     * @return      <code>true</code> if the objects are equivalent;
+     *              <code>false</code> otherwise.
+     * @since       1.6
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        /*  Sanity and quick checks. */
+        if (!(obj instanceof SparseBitSet))
+            return false;
+        final SparseBitSet b = (SparseBitSet) obj;
+        if (this == b)
+            return true; // Identity
+
+        /*  Do the real work.  */
+        if (equalsStrategy == null) {
+            equalsStrategy = new EqualsStrategy();
+        }
+
+        setScanner(0, Math.max(bitsLength, b.bitsLength), b, equalsStrategy);
+        return equalsStrategy.result;
+    }
+
+    /**
+     *  Sets the bit at the specified index to the complement of its current value.
+     *
+     * @param       i the index of the bit to flip
+     * @exception   IndexOutOfBoundsException if the specified index is negative
+     *              or equal to Integer.MAX_VALUE
+     * @since       1.6
+     */
+    public void flip(int i)
+    {
+        if ((i + 1) < 1)
+            throw new IndexOutOfBoundsException("i=" + i);
+        final int w = i >> SHIFT3;
+        final int w1 = w >> SHIFT1;
+        final int w2 = (w >> SHIFT2) & MASK2;
+
+        if (i >= bitsLength)
+            resize(i);
+        long[][] a2;
+        if ((a2 = bits[w1]) == null)
+            a2 = bits[w1] = new long[LENGTH2][];
+        long[] a3;
+        if ((a3 = a2[w2]) == null)
+            a3 = a2[w2] = new long[LENGTH3];
+        a3[w & MASK3] ^= 1L << i; //Flip the designated bit
+        cache.hash = 0; //  Invalidate size, etc., values
+    }
+
+    /**
+     *  Sets each bit from the specified <code>i</code> (inclusive) to the
+     *  specified <code>j</code> (exclusive) to the complement of its current
+     *  value.
+     *
+     * @param       i index of the first bit to flip
+     * @param       j index after the last bit to flip
+     * @exception   IndexOutOfBoundsException if <code>i</code> is negative or is
+     *              equal to Integer.MAX_VALUE, or <code>j</code> is negative, or
+     *              <code>i</code> is larger than <code>j</code>
+     * @since       1.6
+     */
+    public void flip(int i, int j) throws IndexOutOfBoundsException
+    {
+        setScanner(i, j, null, flipStrategy);
+    }
+
+    /**
+     *  Returns the value of the bit with the specified index. The value is
+     *  <code>true</code> if the bit with the index <code>i</code> is currently set
+     *  in this <code>SparseBitSet</code>; otherwise, the result is
+     *  <code>false</code>.
+     *
+     * @param       i the bit index
+     * @return      the boolean value of the bit with the specified index.
+     * @exception   IndexOutOfBoundsException if the specified index is negative
+     *              or equal to Integer.MAX_VALUE
+     * @since       1.6
+     */
+    public boolean get(int i)
+    {
+        if ((i + 1) < 1)
+            throw new IndexOutOfBoundsException("i=" + i);
+        final int w = i >> SHIFT3;
+
+        long[][] a2;
+        long[] a3;
+        return i < bitsLength && (a2 = bits[w >> SHIFT1]) != null
+                && (a3 = a2[(w >> SHIFT2) & MASK2]) != null
+                && ((a3[w & MASK3] & (1L << i)) != 0);
+    }
+
+    /**
+     *  Returns a new <code>SparseBitSet</code> composed of bits from this
+     *  <code>SparseBitSet</code> from <code>i</code> (inclusive) to <code>j</code>
+     *  (exclusive).
+     *
+     * @param       i index of the first bit to include
+     * @param       j index after the last bit to include
+     * @return      a new SparseBitSet from a range of this SparseBitSet
+     * @exception   IndexOutOfBoundsException if <code>i</code> is negative or is
+     *              equal to Integer.MAX_VALUE, or <code>j</code> is negative, or
+     *              <code>i</code> is larger than <code>j</code>
+     * @since       1.6
+     */
+    public SparseBitSet get(int i, int j) throws IndexOutOfBoundsException
+    {
+        final SparseBitSet result = new SparseBitSet(j, compactionCount);
+        result.setScanner(i, j, this, result.copyStrategy);
+        return result;
+    }
+
+    /**
+     *  Returns a hash code value for this bit set. The hash code depends only on
+     *  which bits have been set within this <code>SparseBitSet</code>. The
+     *  algorithm used to compute it may be described as follows.
+     *  <p>
+     *  Suppose the bits in the <code>SparseBitSet</code> were to be stored in an
+     *  array of <code>long</code> integers called, say, <code>bits</code>, in such
+     *  a manner that bit <code>i</code> is set in the <code>SparseBitSet</code>
+     *  (for nonnegative values of  <code>i</code>) if and only if the expression
+     *  <pre>
+     *  ((i&gt;&gt;6) &lt; bits.length) &amp;&amp; ((bits[i&gt;&gt;6] &amp; (1L &lt;&lt; (bit &amp; 0x3F))) != 0)
+     *  </pre>
+     *  is true. Then the following definition of the <code>hashCode</code> method
+     *  would be a correct implementation of the actual algorithm:
+     *  <pre>
+     *  public int hashCode()
+     *  {
+     *      long hash = 1234L;
+     *      for( int i = bits.length; --i &gt;= 0; )
+     *          hash ^= bits[i] * (i + 1);
+     *      return (int)((h &gt;&gt; 32) ^ h);
+     *  }</pre>
+     *  Note that the hash code values change if the set of bits is altered.
+     *
+     * @return      a hash code value for this bit set
+     * @since       1.6
+     * @see         Object#equals(Object)
+     * @see         java.util.Hashtable
+     */
+    @Override
+    public int hashCode()
+    {
+        statisticsUpdate();
+        return cache.hash;
+    }
+
+    /**
+     *  Returns true if the specified <code>SparseBitSet</code> has any bits
+     *  within the given range <code>i</code> (inclusive) to <code>j</code>
+     *  (exclusive) set to <code>true</code> that are also set to <code>true</code>
+     *  in the same range of this <code>SparseBitSet</code>.
+     *
+     * @param       i index of the first bit to include
+     * @param       j index after the last bit to include
+     * @param       b the SparseBitSet with which to intersect
+     * @return      the boolean indicating whether this SparseBitSet intersects the
+     *              specified SparseBitSet
+     * @exception   IndexOutOfBoundsException if <code>i</code> is negative or
+     *              equal to Integer.MAX_VALUE, or <code>j</code> is negative,
+     *              or <code>i</code> is larger than <code>j</code>
+     * @since       1.6
+     */
+    public boolean intersects(int i, int j, SparseBitSet b)
+            throws IndexOutOfBoundsException
+    {
+        if (intersectsStrategy == null) {
+            intersectsStrategy = new IntersectsStrategy();
+        }
+
+        setScanner(i, j, b, intersectsStrategy);
+        return intersectsStrategy.result;
+    }
+
+    /**
+     *  Returns true if the specified <code>SparseBitSet</code> has any bits set to
+     *  <code>true</code> that are also set to <code>true</code> in this
+     *  <code>SparseBitSet</code>.
+     *
+     * @param       b a SparseBitSet with which to intersect
+     * @return      boolean indicating whether this SparseBitSet intersects the
+     *              specified SparseBitSet
+     * @since       1.6
+     */
+    public boolean intersects(SparseBitSet b)
+    {
+        if (intersectsStrategy == null) {
+            intersectsStrategy = new IntersectsStrategy();
+        }
+
+        setScanner(0, Math.max(bitsLength, b.bitsLength), b, intersectsStrategy);
+        return intersectsStrategy.result;
+    }
+
+    /**
+     *  Returns true if this <code>SparseBitSet</code> contains no bits that are
+     *  set to <code>true</code>.
+     *
+     * @return      the boolean indicating whether this SparseBitSet is empty
+     * @since       1.6
+     */
+    public boolean isEmpty()
+    {
+        statisticsUpdate();
+        return cache.cardinality == 0;
+    }
+
+    /**
+     *  Returns the "logical length" of this <code>SparseBitSet</code>: the index
+     *  of the highest set bit in the <code>SparseBitSet</code> plus one. Returns
+     *  zero if the <code>SparseBitSet</code> contains no set bits.
+     *
+     * @return      the logical length of this SparseBitSet
+     * @since       1.6
+     */
+    public int length()
+    {
+        statisticsUpdate();
+        return cache.length;
+    }
+
+    /**
+     *  Returns the index of the first bit that is set to <code>false</code> that
+     *  occurs on or after the specified starting index.
+     *
+     * @param       i the index to start checking from (inclusive)
+     * @return      the index of the next clear bit, or -1 if there is no such bit
+     * @exception   IndexOutOfBoundsException if the specified index is negative
+     * @since       1.6
+     */
+    public int nextClearBit(int i)
+    {
+        /*  The index of this method is permitted to be Integer.MAX_VALUE, as this
+            is needed to make this method work together with the method
+            nextSetBit()--as might happen if a search for the next clear bit is
+            started after finding a set bit labelled Integer.MAX_VALUE-1. This
+            case is not optimised, the code will eventually return -1 (since
+            the Integer.MAX_VALUEth bit does "exist," and is 0. */
+
+        if (i < 0)
+            throw new IndexOutOfBoundsException("i=" + i);
+        /*  This is the word from which the search begins. */
+        int w = i >> SHIFT3;
+        int w3 = w & MASK3;
+        int w2 = (w >> SHIFT2) & MASK2;
+        int w1 = w >> SHIFT1;
+
+        long nword = ~0L << i;
+        final int aLength = bits.length;
+
+        long[][] a2;
+        long[] a3;
+        /*  Is the next clear bit in the same word at the nominated beginning bit
+        (including the nominated beginning bit itself). The first check is
+        whether the starting bit is within the structure at all. */
+        if (w1 < aLength && (a2 = bits[w1]) != null
+                && (a3 = a2[w2]) != null
+                && ((nword = ~a3[w3] & (~0L << i))) == 0L)
+        {
+            /*  So now start a search though the rest of the entries for
+                a null area or block, or a clear bit (a set bit in the
+                complemented value). */
+            ++w;
+            w3 = w & MASK3;
+            w2 = (w >> SHIFT2) & MASK2;
+            w1 = w >> SHIFT1;
+            loop: for (; w1 != aLength; ++w1)
+            {
+                if ((a2 = bits[w1]) != null)
+                    for (; w2 != LENGTH2; ++w2)
+                    {
+                        if ((a3 = a2[w2]) != null)
+                            for (; w3 != LENGTH3; ++w3)
+                                if ((nword = ~a3[w3]) != 0)
+                                    break loop;
+                        w3 = 0;
+                    }
+                w2 = w3 = 0;
+            }
+        }
+        final int result = (((w1 << SHIFT1) + (w2 << SHIFT2) + w3) << SHIFT3)
+                + Long.numberOfTrailingZeros(nword);
+        return (result == Integer.MAX_VALUE ? -1 : result);
+    }
+
+    /**
+     *  Returns the index of the first bit that is set to <code>true</code> that
+     *  occurs on or after the specified starting index. If no such it exists then
+     *  -1 is returned.
+     *  <p>
+     *  To iterate over the <code>true</code> bits in a <code>SparseBitSet
+     *  sbs</code>, use the following loop:
+     *
+     *  <pre>
+     *  for( int i = sbbits.nextSetBit(0); i &gt;= 0; i = sbbits.nextSetBit(i+1) )
+     *  {
+     *      // operate on index i here
+     *  }</pre>
+     *
+     * @param       i the index to start checking from (inclusive)
+     * @return      the index of the next set bit
+     * @exception   IndexOutOfBoundsException if the specified index is negative
+     * @since       1.6
+     */
+    public int nextSetBit(int i)
+    {
+        /*  The index value (i) of this method is permitted to be Integer.MAX_VALUE,
+            as this is needed to make the loop defined above work: just in case the
+            bit labelled Integer.MAX_VALUE-1 is set. This case is not optimised:
+            but eventually -1 will be returned, as this will be included with
+            any search that goes off the end of the level1 array. */
+
+        if (i < 0)
+            throw new IndexOutOfBoundsException("i=" + i);
+        /*  This is the word from which the search begins. */
+        int w = i >> SHIFT3;
+        int w3 = w & MASK3;
+        int w2 = (w >> SHIFT2) & MASK2;
+        int w1 = w >> SHIFT1;
+
+        long word = 0L;
+        final int aLength = bits.length;
+
+        long[][] a2;
+        long[] a3;
+        /*  Is the next set bit in the same word at the nominated beginning bit
+        (including the nominated beginning bit itself). The first check is
+        whether the starting bit is within the structure at all. */
+        if (w1 < aLength && ((a2 = bits[w1]) == null
+                || (a3 = a2[w2]) == null
+                || ((word = a3[w3] & (~0L << i)) == 0L)))
+        {
+            /*  So now start a search though the rest of the entries for a bit. */
+            ++w;
+            w3 = w & MASK3;
+            w2 = (w >> SHIFT2) & MASK2;
+            w1 = w >> SHIFT1;
+            major: for (; w1 != aLength; ++w1)
+            {
+                if ((a2 = bits[w1]) != null)
+                    for (; w2 != LENGTH2; ++w2)
+                    {
+                        if ((a3 = a2[w2]) != null)
+                            for (; w3 != LENGTH3; ++w3)
+                                if ((word = a3[w3]) != 0)
+                                    break major;
+                        w3 = 0;
+                    }
+                w2 = w3 = 0;
+            }
+        }
+        return (w1 >= aLength ? -1
+                : (((w1 << SHIFT1) + (w2 << SHIFT2) + w3) << SHIFT3)
+                        + Long.numberOfTrailingZeros(word));
+    }
+
+    /**
+     *  Performs a logical <b>OR</b> of the addressed target bit with the
+     *  argument value. This bit set is modified so that the addressed bit has the
+     *  value <code>true</code> if and only if it both initially had the value
+     *  <code>true</code> or the argument value is <code>true</code>.
+     *
+     * @param       i a bit index
+     * @param       value a boolean value to OR with that bit
+     * @exception   IndexOutOfBoundsException if the specified index is negative
+     *              or equal to Integer.MAX_VALUE
+     * @since       1.6
+     */
+    public void or(int i, boolean value)
+    {
+        if ((i + 1) < 1)
+            throw new IndexOutOfBoundsException("i=" + i);
+        if (value)
+            set(i);
+    }
+
+    /**
+     *  Performs a logical <b>OR</b> of the addressed target bit with the
+     *  argument value within the given range. This bit set is modified so that
+     *  within the range a bit in it has the value <code>true</code> if and only if
+     *  it either already had the value <code>true</code> or the corresponding bit
+     *  in the bit set argument has the value <code>true</code>. Outside the range
+     *  this set is not changed.
+     *
+     * @param       i index of the first bit to be included in the operation
+     * @param       j index after the last bit to included in the operation
+     * @param       b the SparseBitSet with which to perform the <b>OR</b>
+     *              operation with this SparseBitSet
+     * @exception   IndexOutOfBoundsException if <code>i</code> is negative or
+     *              equal to Integer.MAX_VALUE, or <code>j</code> is negative,
+     *              or <code>i</code> is larger than <code>j</code>
+     * @since       1.6
+     */
+    public void or(int i, int j, SparseBitSet b) throws IndexOutOfBoundsException
+    {
+        setScanner(i, j, b, orStrategy);
+    }
+
+    /**
+     *  Performs a logical <b>OR</b> of this bit set with the bit set argument.
+     *  This bit set is modified so that a bit in it has the value <code>true</code>
+     *  if and only if it either already had the value <code>true</code> or the
+     *  corresponding bit in the bit set argument has the value <code>true</code>.
+     *
+     * @param       b the SparseBitSet with which to perform the <b>OR</b>
+     *              operation with this SparseBitSet
+     * @since       1.6
+     */
+    public void or(SparseBitSet b)
+    {
+        setScanner(0, b.bitsLength, b, orStrategy);
+    }
+
+    /**
+     *  Performs a logical <b>OR</b> of the two given <code>SparseBitSet</code>s.
+     *  The returned <code>SparseBitSet</code> is created so that a bit in it has
+     *  the value <code>true</code> if and only if it either had the value
+     *  <code>true</code> in the set given by the first arguemetn or had the value
+     *  <code>true</code> in the second argument, otherwise <code>false</code>.
+     *
+     * @param       a a SparseBitSet
+     * @param       b another SparseBitSet
+     * @return      new SparseBitSet representing the <b>OR</b> of the two sets
+     * @since       1.6
+     */
+    public static SparseBitSet or(SparseBitSet a, SparseBitSet b)
+    {
+        final SparseBitSet result = a.clone();
+        result.or(b);
+        return result;
+    }
+
+    /**
+     *  Sets the bit at the specified index.
+     *
+     * @param       i a bit index
+     * @exception   IndexOutOfBoundsException if the specified index is negative
+     *              or equal to Integer.MAX_VALUE
+     * @since       1.6
+     */
+    public void set(int i)
+    {
+        if ((i + 1) < 1)
+            throw new IndexOutOfBoundsException("i=" + i);
+        final int w = i >> SHIFT3;
+        final int w1 = w >> SHIFT1;
+        final int w2 = (w >> SHIFT2) & MASK2;
+
+        if (i >= bitsLength)
+            resize(i);
+        long[][] a2;
+        if ((a2 = bits[w1]) == null)
+            a2 = bits[w1] = new long[LENGTH2][];
+        long[] a3;
+        if ((a3 = a2[w2]) == null)
+            a3 = a2[w2] = new long[LENGTH3];
+        a3[w & MASK3] |= 1L << i;
+        cache.hash = 0; //Invalidate size, etc., scan
+    }
+
+    /**
+     *  Sets the bit at the specified index to the specified value.
+     *
+     * @param       i a bit index
+     * @param       value a boolean value to set
+     * @exception   IndexOutOfBoundsException if the specified index is negative
+     *              or equal to Integer.MAX_VALUE
+     * @since       1.6
+     */
+    public void set(int i, boolean value)
+    {
+        if (value)
+            set(i);
+        else
+            clear(i);
+    }
+
+    /**
+     *  Sets the bits from the specified <code>i</code> (inclusive) to the specified
+     *  <code>j</code> (exclusive) to <code>true</code>.
+     *
+     * @param       i index of the first bit to be set
+     * @param       j index after the last bit to be se
+     * @exception   IndexOutOfBoundsException if <code>i</code> is negative or is
+     *              equal to Integer.MAX_INT, or <code>j</code> is negative, or
+     *              <code>i</code> is larger than <code>j</code>.
+     * @since       1.6
+     */
+    public void set(int i, int j) throws IndexOutOfBoundsException
+    {
+        setScanner(i, j, null, setStrategy);
+    }
+
+    /**
+     *  Sets the bits from the specified <code>i</code> (inclusive) to the specified
+     *  <code>j</code> (exclusive) to the specified value.
+     *
+     * @param       i index of the first bit to be set
+     * @param       j index after the last bit to be set
+     * @param       value to which to set the selected bits
+     * @exception   IndexOutOfBoundsException if <code>i</code> is negative or is
+     *              equal to Integer.MAX_VALUE, or <code>j</code> is negative, or
+     *              <code>i</code> is larger than <code>j</code>
+     * @since       1.6
+     */
+    public void set(int i, int j, boolean value)
+    {
+        if (value)
+            set(i, j);
+        else
+            clear(i, j);
+    }
+
+    /**
+     *  Returns the number of bits of space nominally in use by this
+     *  <code>SparseBitSet</code> to represent bit values. The count of bits in
+     *  the set is the (label of the last set bit) + 1 - (the label of the first
+     *  set bit).
+     *
+     * @return      the number of bits (true and false) nominally in this bit set
+     *              at this moment
+     * @since       1.6
+     */
+    public int size()
+    {
+        statisticsUpdate();
+        return cache.size;
+    }
+
+    /**
+     *  Convenience method for statistics if the individual results are not needed.
+     *
+     * @return      a String detailing the statistics of the bit set
+     * @see         #statistics(String[])
+     * @since       1.6
+     */
+    public String statistics()
+    {
+        return statistics(null);
+    }
+
+    /**
+     *  Determine, and create a String with the bit set statistics. The statistics
+     *  include: Size, Length, Cardinality, Total words (<i>i.e.</i>, the total
+     *  number of 64-bit "words"), Set array length (<i>i.e.</i>, the number of
+     *  references that can be held by the top level array, Level2 areas in use,
+     *  Level3 blocks in use,, Level2 pool size, Level3 pool size, and the
+     *  Compaction count.
+     *  <p>
+     *  This method is intended for diagnostic use (as it is relatively expensive
+     *  in time), but can be useful in understanding an application's use of a
+     *  <code>SparseBitSet</code>.
+     *
+     * @param       values an array for the individual results (if not null)
+     * @return      a String detailing the statistics of the bit set
+     * @since       1.6
+     */
+    public String statistics(String[] values)
+    {
+        statisticsUpdate(); //  Ensure statistics are up-to-date
+        String[] v = new String[Statistics.values().length];
+
+        /*  Assign the statistics values to the appropriate entry. The order
+            of the assignments does not matter--the ordinal serves to get the
+            values into the matching order with the labels from the enumeration. */
+        v[Statistics.Size.ordinal()] = Integer.toString(size());
+        v[Statistics.Length.ordinal()] = Integer.toString(length());
+        v[Statistics.Cardinality.ordinal()] = Integer.toString(cardinality());
+        v[Statistics.Total_words.ordinal()] = Integer.toString(cache.count);
+        v[Statistics.Set_array_length.ordinal()] = Integer.toString(bits.length);
+        v[Statistics.Set_array_max_length.ordinal()] =
+                Integer.toString(MAX_LENGTH1);
+        v[Statistics.Level2_areas.ordinal()] = Integer.toString(cache.a2Count);
+        v[Statistics.Level2_area_length.ordinal()] = Integer.toString(LENGTH2);
+        v[Statistics.Level3_blocks.ordinal()] = Integer.toString(cache.a3Count);
+        v[Statistics.Level3_block_length.ordinal()] = Integer.toString(LENGTH3);
+        v[Statistics.Compaction_count_value.ordinal()] =
+                Integer.toString(compactionCount);
+
+        /*  Determine the longest label, so that the equal signs may be lined-up. */
+        int longestLabel = 0;
+        for (Statistics s : Statistics.values())
+            longestLabel =
+                    Math.max(longestLabel, s.name().length());
+
+        /*  Build a String that has for each statistic, the name of the statistic,
+            padding, and equals sign, and the value. The "Load_factor_value",
+            "Average_length_value", and "Average_chain_length" are printed as
+            floating point values. */
+        final StringBuilder result = new StringBuilder();
+        for (Statistics s : Statistics.values())
+        {
+            result.append(s.name()); // The name of the statistic
+            for (int i = 0; i != longestLabel - s.name().length(); ++i)
+                result.append(' '); //  Fill out the field
+            result.append(" = "); //  Show an equals sign
+            result.append(v[s.ordinal()]); // and a value
+            result.append('\n');
+        }
+        /*  Remove the underscores. */
+        for (int i = 0; i != result.length(); ++i)
+            if (result.charAt(i) == '_')
+                result.setCharAt(i, ' ');
+
+        if (values != null)
+        {
+            final int len = Math.min(values.length, v.length);
+            System.arraycopy(v, 0, values, 0, len);
+        }
+        return result.toString();
+    }
+
+    /**
+     *  Returns a string representation of this bit set. For every index for which
+     *  this <code>SparseBitSet</code> contains a bit in the set state, the decimal
+     *  representation of that index is included in the result. Such indices are
+     *  listed in order from lowest to highest. If there is a subsequence of set
+     *  bits longer than the value given by toStringCompaction, the subsequence
+     *  is represented by the value for the first and the last values, with ".."
+     *  between them. The individual bits, or the representation of sub-sequences
+     *  are separated by ",&nbsp;" (a comma and a space) and surrounded by braces,
+     *  resulting in a compact string showing (a variant of) the usual mathematical
+     *  notation for a set of integers.
+     *  <br>
+     *  Example (with the default value of 2 for subsequences):
+     *  <pre>
+     *      SparseBitSet drPepper = new SparseBitSet();
+     *  </pre>
+     *  Now <code>drPepper.toString()</code> returns "<code>{}</code>".
+     *  <br>
+     *  <pre>
+     *      drPepper.set(2);
+     *  </pre>
+     *  Now <code>drPepper.toString()</code> returns "<code>{2}</code>".
+     *  <br>
+     *  <pre>
+     *      drPepper.set(3, 4);
+     *      drPepper.set(10);
+     *  </pre>
+     *  Now <code>drPepper.toString()</code> returns "<code>{2..4, 10}</code>".
+     *  <br>
+     *  This method is intended for diagnostic use (as it is relatively expensive
+     *  in time), but can be useful in interpreting problems in an application's use
+     *  of a <code>SparseBitSet</code>.
+     *
+     * @return      a String representation of this SparseBitSet
+     * @see         #toStringCompaction(int length)
+     * @since       1.6
+     */
+    @Override
+    public String toString()
+    {
+        final StringBuilder p = new StringBuilder(200);
+        p.append('{');
+        int i = nextSetBit(0);
+        /*  Loop so long as there is another bit to append to the String. */
+        while (i >= 0)
+        {
+            /*  Append that next bit */
+            p.append(i);
+            /*  Find the position of the next bit to show. */
+            int j = nextSetBit(i + 1);
+            if (compactionCount > 0)
+            {
+                /*  Give up if there is no next bit to show. */
+                if (j < 0)
+                    break;
+                /*  Find the next clear bit is after the current bit, i.e., i */
+                int last = nextClearBit(i);
+                /*  Compute the position of the next clear bit after the current
+                    subsequence of set bits. */
+                last = (last < 0 ? Integer.MAX_VALUE : last);
+                /*  If the subsequence is more than the specified bits long, then
+                    collapse the subsequence into one entry in the String. */
+                if (i + compactionCount < last)
+                {
+                    p.append("..").append(last - 1);
+                    /*  Having accounted for a subsequence of bits that are all set,
+                        recompute the label of the next bit to show. */
+                    j = nextSetBit(last);
+                }
+            }
+            /*  If there is another set bit, put a comma and a space after the
+                last entry in the String.  */
+            if (j >= 0)
+                p.append(", ");
+            /*  Transfer to i the index of the next set bit. */
+            i = j;
+        }
+        /*  Terminate the representational String, and return it. */
+        p.append('}');
+        return p.toString();
+    }
+
+    /** Sequences of set bits longer than this value are shown by
+     *  {@link #toString()} as a "sub-sequence," in the form <code>a..b</code>.
+     *  Setting this value to zero causes each set bit to be listed individually.
+     *  The default default value is 2 (which means sequences of three or more 
+     *  bits set are shown as a subsequence, and all other set bits are listed
+     *  individually).
+     *  <p>
+     *  Note: this value will be passed to <code>SparseBitSet</code>s that
+     *  may be created within or as a result of the operations on this bit set,
+     *  or, for static methods, from the value belonging to the first parameter.
+     *
+     * @param       count the maximum count of a run of bits that are shown as
+     *              individual entries in a <code>toString</code>() conversion.
+     *              If 0, all bits are shown individually.
+     * @since       1.6
+     * @see         #toString()
+     */
+    public void toStringCompaction(int count)
+    {
+        compactionCount = count;
+    }
+
+    /**
+     *  If <i>change</i> is <code>true</code>, the current value of the
+     *  <i>toStringCompaction</i>() value is made the default value for all
+     *  <code>SparseBitSet</code>s created from this point onward in this JVM.
+     *
+     * @param       change if true, change the default value
+     * @since       1.6
+     */
+    public void toStringCompaction(boolean change)
+    {
+        /*  This is an assignment to a static value: the integer value assignement
+            is atomic, so there will not be a partial store. If multiple
+            invocations are made from multiple threads, there is a race
+            condition that cannot be resolved by synchronization. */
+        if (change)
+            compactionCountDefault = compactionCount;
+    }
+
+    /**
+     *  Performs a logical <b>XOR</b> of the addressed target bit with the
+     *  argument value. This bit set is modified so that the addressed bit has the
+     *  value <code>true</code> if and only one of the following statements holds:
+     *  <ul>
+     *  <li>The addressed bit initially had the value <code>true</code>, and the
+     *      value of the argument is <code>false</code>.
+     *  <li>The bit initially had the value <code>false</code>, and the
+     *      value of the argument is <code>true</code>.
+     * </ul>
+     *
+     * @param       i a bit index
+     * @param       value a boolean value to <b>XOR</b> with that bit
+     * @exception   java.lang.IndexOutOfBoundsException if the specified index
+     *              is negative
+     *              or equal to Integer.MAX_VALUE
+     * @since       1.6
+     */
+    public void xor(int i, boolean value)
+    {
+        if ((i + 1) < 1)
+            throw new IndexOutOfBoundsException("i=" + i);
+        if (value)
+            flip(i);
+    }
+
+    /**
+     *  Performs a logical <b>XOR</b> of this bit set with the bit set argument
+     *  within the given range. This resulting bit set is computed so that a bit
+     *  within the range in it has the value <code>true</code> if and only if one
+     *  of the following statements holds:
+     *  <ul>
+     *  <li>The bit initially had the value <code>true</code>, and the
+     *      corresponding bit in the argument set has the value <code>false</code>.
+     *  <li>The bit initially had the value <code>false</code>, and the
+     *      corresponding bit in the argument set has the value <code>true</code>.
+     * </ul>
+     *  Outside the range this set is not changed.
+     *
+     * @param       i index of the first bit to be included in the operation
+     * @param       j index after the last bit to included in the operation
+     * @param       b the SparseBitSet with which to perform the <b>XOR</b>
+     *              operation with this SparseBitSet
+     * @exception   IndexOutOfBoundsException if <code>i</code> is negative or
+     *              equal to Integer.MAX_VALUE, or <code>j</code> is negative,
+     *              or <code>i</code> is larger than <code>j</code>
+     * @since       1.6
+     */
+    public void xor(int i, int j, SparseBitSet b) throws IndexOutOfBoundsException
+    {
+        setScanner(i, j, b, xorStrategy);
+    }
+
+    /**
+     *  Performs a logical <b>XOR</b> of this bit set with the bit set argument.
+     *  This resulting bit set is computed so that a bit in it has the value
+     *  <code>true</code> if and only if one of the following statements holds:
+     *  <ul>
+     *  <li>The bit initially had the value <code>true</code>, and the
+     *      corresponding bit in the argument set has the value <code>false</code>.
+     *  <li>The bit initially had the value <code>false</code>, and the
+     *      corresponding bit in the argument set has the value <code>true</code>.
+     * </ul>
+     *
+     * @param       b the SparseBitSet with which to perform the <b>XOR</b>
+     *              operation with thisSparseBitSet
+     * @since       1.6
+     */
+    public void xor(SparseBitSet b)
+    {
+        setScanner(0, b.bitsLength, b, xorStrategy);
+    }
+
+    /**
+     * Performs a logical <b>XOR</b> of the two given <code>SparseBitSet</code>s.
+     *  The resulting bit set is created so that a bit in it has the value
+     *  <code>true</code> if and only if one of the following statements holds:
+     *  <ul>
+     *  <li>A bit in the first argument has the value <code>true</code>, and the
+     *      corresponding bit in the second argument has the value
+     *      <code>false</code>.</li>
+     *  <li>A bit in the first argument has the value <code>false</code>, and the
+     *      corresponding bit in the second argument has the value
+     *      <code>true</code>.</li></ul>
+     *
+     * @param       a a SparseBitSet
+     * @param       b another SparseBitSet
+     * @return      a new SparseBitSet representing the <b>XOR</b> of the two sets
+     * @since       1.6
+     */
+    public static SparseBitSet xor(SparseBitSet a, SparseBitSet b)
+    {
+        final SparseBitSet result = a.clone();
+        result.xor(b);
+        return result;
+    }
+
+    //==============================================================================
+    //      Internal methods
+    //==============================================================================
+    /**
+     *  Throw the exception to indicate a range error. The <code>String</code>
+     *  constructed reports all the possible errors in one message.
+     *
+     * @param       i lower bound for a operation
+     * @param       j upper bound for a operation
+     * @exception   IndexOutOfBoundsException indicating the range is not valid
+     * @since       1.6
+     */
+    protected static final void throwIndexOutOfBoundsException(int i, int j)
+            throws IndexOutOfBoundsException
+    {
+        String s = "";
+        if (i < 0)
+            s += "(i=" + i + ") < 0";
+        if (i == Integer.MAX_VALUE)
+            s += "(i=" + i + ")";
+        if (j < 0)
+            s += (s.length() == 0 ? "" : ", ") + "(j=" + j + ") < 0";
+        if (i > j)
+            s += (s.length() == 0 ? "" : ", ") + "(i=" + i + ") > (j=" + j + ")";
+        throw new IndexOutOfBoundsException(s);
+    }
+
+    /**
+     *  Intializes all the additional objects required for correct operation.
+     *
+     * @since       1.6
+     */
+    protected final void constructorHelper()
+    {
+        spare = new long[LENGTH3];
+        cache = new Cache();
+    }
+
+    /**
+     *  Clear out a part of the set array with nulls, from the given start to the
+     *  end of the array. If the given parameter is beyond the end of the bits
+     *  array, nothing is changed.
+     *
+     * @param       start word index at which to start (inclusive)
+     * @since       1.6
+     */
+    protected final void nullify(int start)
+    {
+        final int aLength = bits.length;
+        if (start < aLength)
+        {
+            for (int w = start; w != aLength; ++w)
+                bits[w] = null;
+            cache.hash = 0; //  Invalidate size, etc., values
+        }
+    }
+
+    /**
+     *  Resize the bit array. Moves the entries in the the bits array of this
+     *  SparseBitSet into an array whose size (which may be larger or smaller) is
+     *  the given bit size (<i>i.e.</i>, includes the bit whose index is one less
+     *  that the given value). If the new array is smaller, the excess entries in
+     *  the set array are discarded. If the new array is bigger, it is filled with
+     *  nulls.
+     *
+     * @param       index the desired address to be included in the set
+     * @since       1.6
+     */
+    protected final void resize(int index)
+    {
+        /*  Find an array size that is a power of two that is as least as large
+            enough to contain the index requested. */
+        final int w1 = (index >> SHIFT3) >> SHIFT1;
+        int newSize = Integer.highestOneBit(w1);
+        if (newSize == 0)
+            newSize = 1;
+        if (w1 >= newSize)
+            newSize <<= 1;
+        if (newSize > MAX_LENGTH1)
+            newSize = MAX_LENGTH1;
+        final int aLength1 = (bits != null ? bits.length : 0);
+
+        if (newSize != aLength1)
+        { // only if the size needs to be changed
+            final long[][][] temp = new long[newSize][][]; //  Get the new array
+            if (aLength1 != 0)
+            {
+                /*  If it exists, copy old array to the new array. */
+                System.arraycopy(bits, 0, temp, 0, Math.min(aLength1, newSize));
+                nullify(0); //  Don't leave unused pointers around. */
+            }
+            bits = temp; //  Set new array as the set array
+            bitsLength = //  Index of last possible bit, plus one.
+            (newSize == MAX_LENGTH1 ? Integer.MAX_VALUE : newSize * UNIT);
+        }
+    }
+
+    /**
+     *  Scans over the bit set (and a second bit set if part of the operation) are
+     *  all performed by this method. The properties and the operation executed
+     *  are defined by a given strategy, which must be derived from the
+     *  <code>AbstractStrategy</code>. The strategy defines how to operate on a
+     *  single word, and on whole words that may or may not constitute a full
+     *  block of words.
+     *
+     * @param       i the bit (inclusive) at which to start the scan
+     * @param       j the bit (exclusive) at which to stop the scan
+     * @param       b a SparseBitSet, if needed, the second SparseBitSet in the
+     *              operation
+     * @param       op the AbstractStrategy class defining the operation to be
+     *              executed
+     * @exception   IndexOutOfBoundsException
+     * @since       1.6
+     * @see         AbstractStrategy
+     */
+    protected final void setScanner(int i, int j, SparseBitSet b,
+                                    AbstractStrategy op) throws IndexOutOfBoundsException
+    {
+        /*  This method has been assessed as having a McCabe cyclomatic
+            complexity of 47 (i.e., impossibly high). However, given that this
+            method incorporates all the set scanning logic for all methods
+            (with the exception of nextSetBit and nextClearBit, which themselves
+            have high cyclomatic complexities of 13), and is attempting to minimise
+            execution time (hence deals with processing shortcuts), it cannot be
+            expected to be simple. In fact, the work of lining up level3 blocks
+            proceeds step-wise, and each sub-section piece is reasonably
+            straight-forward. Nevertheless, the number of paths is high, and
+            caution is advised in attempting to correct anything. */
+
+        /*  Do whatever the strategy needs to get started, and do whatever initial
+            checking is needed--fail here if needed before much else is done. */
+        if (op.start(b)) {
+            cache.hash = 0;
+        }
+
+        if (j < i || (i + 1) < 1)
+            throwIndexOutOfBoundsException(i, j);
+        if (i == j)
+            return;
+
+        /*  Get the values of all the short-cut options. */
+        final int properties = op.properties();
+        final boolean f_op_f_eq_f = (properties & AbstractStrategy.F_OP_F_EQ_F) != 0;
+        final boolean f_op_x_eq_f = (properties & AbstractStrategy.F_OP_X_EQ_F) != 0;
+        final boolean x_op_f_eq_f = (properties & AbstractStrategy.X_OP_F_EQ_F) != 0;
+        final boolean x_op_f_eq_x = (properties & AbstractStrategy.X_OP_F_EQ_X) != 0;
+
+        /*  Index of the current word, and mask for the first word,
+            to be processed in the bit set. */
+        int u = i >> SHIFT3;
+        final long um = ~0L << i;
+
+        /*  Index of the final word, and mask for the final word,
+            to be processed in the bit set. */
+        final int v = (j - 1) >> SHIFT3;
+        final long vm = ~0L >>> -j;
+
+        /*  Set up the two bit arrays (if the second exists), and their
+            corresponding lengths (if any). */
+        long[][][] a1 = bits; //  Level1, i.e., the bit arrays
+        int aLength1 = bits.length;
+        final long[][][] b1 = (b != null ? b.bits : null);
+        final int bLength1 = (b1 != null ? b.bits.length : 0);
+
+        /*  Calculate the initial values of the parts of the words addresses,
+            as well as the location of the final block to be processed.  */
+        int u1 = u >> SHIFT1;
+        int u2 = (u >> SHIFT2) & MASK2;
+        int u3 = u & MASK3;
+        final int v1 = v >> SHIFT1;
+        final int v2 = (v >> SHIFT2) & MASK2;
+        final int v3 = v & MASK3;
+        final int lastA3Block = (v1 << LEVEL2) + v2;
+
+        /*  Initialize the local copies of the counts of blocks and areas; and
+            whether there is a partial first block.  */
+        int a2CountLocal = 0;
+        int a3CountLocal = 0;
+        boolean notFirstBlock = u == 0 && um == ~0L;
+
+        /*  The first level2 is cannot be judged empty if not being scanned from
+            the beginning. */
+        boolean a2IsEmpty = u2 == 0; //  Presumption
+        while (i < j)
+        {
+            /*  Determine if there is a level2 area in both the a and the b set,
+                and if so, set the references to these areas. */
+            long[][] a2 = null;
+            boolean haveA2 = u1 < aLength1 && (a2 = a1[u1]) != null;
+            long[][] b2 = null;
+            final boolean haveB2 = u1 < bLength1
+                    && b1 != null && (b2 = b1[u1]) != null;
+            /*  Handling of level 2 empty areas: determined by the
+                properties of the strategy. It is necessary to actually visit
+                the first and last blocks of a scan, since not all of the block
+                might participate in the operation, hence making decision based
+                on just the references to the blocks could be wrong. */
+            if ((!haveA2 && !haveB2 && f_op_f_eq_f
+                    || !haveA2 && f_op_x_eq_f || !haveB2 && x_op_f_eq_f)
+                    && notFirstBlock && u1 != v1)
+            {//nested if!
+                if (u1 < aLength1)
+                    a1[u1] = null;
+            }
+            else
+            {
+                final int limit2 = (u1 != v1 ? LENGTH2 : v2 + 1);
+                while (u2 != limit2)
+                {
+                    /*  Similar logic applied here as for the level2 blocks.
+                        The initial and final block must be examined. In other
+                        cases, it may be possible to make a decision based on
+                        the value of the references, as indicated by the
+                        properties of the strategy. */
+                    long[] a3 = null;
+                    final boolean haveA3 = haveA2 && (a3 = a2[u2]) != null;
+                    long[] b3 = null;
+                    final boolean haveB3 = haveB2 && (b3 = b2[u2]) != null;
+                    final int a3Block = (u1 << LEVEL2) + u2;
+                    final boolean notLastBlock = lastA3Block != a3Block;
+                    /*  Handling of level 3 empty areas: determined by the
+                        properties of the strategy. */
+                    if ((!haveA3 && !haveB3 && f_op_f_eq_f
+                            || !haveA3 && f_op_x_eq_f || !haveB3 && x_op_f_eq_f)
+                            && notFirstBlock && notLastBlock)
+                    {
+                        /*  Do not need level3 block, so remove it, and move on. */
+                        if (haveA2)
+                            a2[u2] = null;
+                        u3 = 0;
+                    }
+                    else
+                    {
+                        /*  So what is needed is the level3 block. */
+                        final int base3 = a3Block << SHIFT2;
+                        final int limit3 = (notLastBlock ? LENGTH3 : v3);
+                        if (!haveA3)
+                            a3 = spare;
+                        if (!haveB3)
+                            b3 = ZERO_BLOCK;
+                        boolean isZero;
+                        if (notFirstBlock && notLastBlock)
+                            if (x_op_f_eq_x && !haveB3)
+                                isZero = op.isZeroBlock(a3);
+                            // b block is null, just check a block
+                            else
+                                isZero = op.block(base3, 0, LENGTH3, a3, b3);
+                        // Do the operation on the whole block
+                        else
+                        { /*  Partial block to process. */
+                            if (notFirstBlock)
+                            {
+                                /*  By implication, this is the last block */
+                                isZero = op.block(base3, 0, limit3, a3, b3);
+                                //  Do the whole words
+                                isZero &= op.word(base3, limit3, a3, b3, vm);
+                                //  And then the final word
+                            }
+                            else
+                            { // u, v are correct if first block
+                                if (u == v) //  Scan starts and ends in one word
+                                    isZero = op.word(base3, u3, a3, b3, um & vm);
+                                else
+                                { // Scan starts in this a3 block
+                                    isZero = op.word(base3, u3, a3, b3, um);
+                                    //  First word
+                                    isZero &=
+                                            op.block(base3, u3 + 1, limit3, a3, b3);
+                                    //  Remainder of full words in block
+                                    if (limit3 != LENGTH3)
+                                        isZero &= op.word(base3, limit3, a3, b3, vm);
+                                    //  If there is a partial word left
+                                }
+                                notFirstBlock = true; //  Only one first block
+                            }
+                            if (isZero)
+                                isZero = op.isZeroBlock(a3);
+                            // If not known to have a non-zero
+                            // value, be sure whether all zero.
+                        }
+                        if (isZero) //  The resulting a3 block has no values
+                        {// nested if!
+                            /*  If there is an level 2 area make the entry for this
+                                level3 block be a null (i.e., remove any a3 block ). */
+                            if (haveA2)
+                                a2[u2] = null;
+                        }
+                        else
+                        {
+                            /*  If the a3 block used was the spare block, put it
+                                into current level2 area; get a new spare block. */
+                            if (a3 == spare)
+                            {
+                                if (i >= bitsLength) //Check that the set is large
+                                { //  enough to take the new block
+                                    resize(i); //  Make it large enough
+                                    a1 = bits; //  Update reference and length
+                                    aLength1 = a1.length;
+                                }
+                                if (a2 == null) //  Ensure a level 2 area
+                                {
+                                    a1[u1] = a2 = new long[LENGTH2][];
+                                    haveA2 = true; //  Ensure know level2 not empty
+                                }
+                                a2[u2] = a3; //  Insert the level3 block
+                                spare = new long[LENGTH3]; // Replace the spare
+                            }
+                            ++a3CountLocal; // Count the level 3 block
+                        }
+                        a2IsEmpty &= !(haveA2 && a2[u2] != null);
+                    } //  Keep track of level 2 usage
+                    ++u2;
+                    u3 = 0;
+                } /* end while ( u2 != limit2 ) */
+                /*  If the loop finishes without completing the level 2, it may
+                    be left with a reference but still be all null--this is OK. */
+                if (u2 == LENGTH2 && a2IsEmpty && u1 < aLength1)
+                    a1[u1] = null;
+                else
+                    ++a2CountLocal; //  Count level 2 areas
+            }
+            /*  Advance the value of u based on what happened. */
+            i = (u = (++u1 << SHIFT1)) << SHIFT3;
+            u2 = 0; //  u3 = 0
+            //  Compute next word and bit index
+            if (i < 0)
+                i = Integer.MAX_VALUE; //  Don't go over the end
+        } /* end while( i < j ) */
+
+        /*  Do whatever the strategy needs in order to finish. */
+        op.finish(a2CountLocal, a3CountLocal);
+    }
+
+    /**
+     *  The entirety of the bit set is examined, and the various statistics of
+     *  the bit set (size, length, cardinality, hashCode, etc.) are computed. Level
+     *  arrays that are empty (i.e., all zero at level 3, all null at level 2) are
+     *  replaced by null references, ensuring a normalized representation.
+     *
+     * @since       1.6
+     */
+    protected final void statisticsUpdate()
+    {
+        if (cache.hash != 0) {
+            return;
+        }
+
+        if (updateStrategy == null) {
+            updateStrategy = new UpdateStrategy();
+        }
+
+        setScanner(0, bitsLength, null, updateStrategy);
+    }
+
+    //==============================================================================
+    //  Serialization/Deserialization methods
+    //==============================================================================
+
+    /**
+     *  Save the state of the <code>SparseBitSet</code> instance to a stream 
+     *  (<i>i.e.</i>, serialize it).
+     *
+     * @param       s the ObjectOutputStream to which to write the serialized object
+     * @exception   java.io.IOException if an io error occurs
+     * @exception   java.lang.InternalError if the SparseBitSet representation is
+     *              inconsistent
+     *
+     * @serialData  The default data is emitted, followed by the current
+     *              <i>compactionCount</i> for the bit set, and then the 
+     *              <i>length</i> of the set (the position of the last bit),
+     *              followed by the <i>cache.count</i> value (an <code>int</code>,
+     *              the number of <code>int-&gt;long</code> pairs needed to describe
+     *              the set), followed by the index (<code>int</code>) and word
+     *              (<code>long</code>) for each <code>int-&gt;long</code> pair.
+     *              The mappings need not be emitted in any particular order. This
+     *              is followed by the <i>hashCode</i> for the set that can be used
+     *              as an integrity check when the bit set is read back.
+     *
+     * @since       1.6
+     */
+    private void writeObject(ObjectOutputStream s) throws IOException, InternalError
+    {
+        statisticsUpdate(); //  Update structure and stats if needed.
+        /*  Write any hidden stuff. */
+        s.defaultWriteObject();
+        s.writeInt(compactionCount); //  Needed to preserve value
+        s.writeInt(cache.length); //  Needed to know where last bit is
+
+        /*  This is the number of index/value pairs to be written. */
+        int count = cache.count; //  Minimum number of words to be written
+        s.writeInt(count);
+        final long[][][] a1 = bits;
+        final int aLength1 = a1.length;
+        long[][] a2;
+        long[] a3;
+        long word;
+        for (int w1 = 0; w1 != aLength1; ++w1)
+            if ((a2 = a1[w1]) != null)
+                for (int w2 = 0; w2 != LENGTH2; ++w2)
+                    if ((a3 = a2[w2]) != null)
+                    {
+                        final int base = (w1 << SHIFT1) + (w2 << SHIFT2);
+                        for (int w3 = 0; w3 != LENGTH3; ++w3)
+                            if ((word = a3[w3]) != 0)
+                            {
+                                s.writeInt(base + w3);
+                                s.writeLong(word);
+                                --count;
+                            }
+                    }
+        if (count != 0)
+            throw new InternalError("count of entries not consistent");
+        /*  As a consistency check, write the hash code of the set. */
+        s.writeInt(cache.hash);
+    }
+
+    /**
+     *  serialVersionUID
+     */
+    private static final long serialVersionUID = -6663013367427929992L;
+
+    /**
+     *  Reconstitute the <code>SparseBitSet</code> instance from a stream 
+     *  (<i>i.e.</i>, deserialize it).
+     *
+     * @param       s the ObjectInputStream to use
+     * @exception   IOException if there is an io error
+     * @exception   ClassNotFoundException if the stream contains an unidentified
+     *              class
+     * @since       1.6
+     */
+    private void readObject(ObjectInputStream s) throws IOException,
+            ClassNotFoundException
+    {
+        /* Read in any hidden stuff that is part of the class overhead. */
+        s.defaultReadObject();
+        compactionCount = s.readInt();
+        final int aLength = s.readInt();
+        resize(aLength); // Make sure there is enough space
+
+        /* Read in number of mappings. */
+        final int count = s.readInt();
+        /*  Read the keys and values, them into the set array, areas, and blocks. */
+        long[][] a2;
+        long[] a3;
+        for (int n = 0; n != count; ++n)
+        {
+            final int w = s.readInt();
+            final int w3 = w & MASK3;
+            final int w2 = (w >> SHIFT2) & MASK2;
+            final int w1 = w >> SHIFT1;
+
+            final long word = s.readLong();
+            if ((a2 = bits[w1]) == null)
+                a2 = bits[w1] = new long[LENGTH2][];
+            if ((a3 = a2[w2]) == null)
+                a3 = a2[w2] = new long[LENGTH3];
+            a3[w3] = word;
+        }
+        /* Ensure all the pieces are set up for set scanning. */
+        constructorHelper();
+        statisticsUpdate();
+        if (count != cache.count)
+            throw new InternalError("count of entries not consistent");
+        final int hash = s.readInt(); //  Get the hashcode that was stored
+        if (hash != cache.hash) //  An error of some kind if not the same
+            throw new IOException("deserialized hashCode mis-match");
+    }
+
+    //=============================================================================
+    //  Statistics enumeration
+    //=============================================================================
+
+    /**
+     *  These enumeration values are used as labels for the values in the String
+     *  created by the <i>statistics</i>() method. The values of the corresponding
+     *  statistics are <code>int</code>s, except for the loadFactor and
+     *  Average_chain_length values, which are <code>float</code>s.
+     *  <p>
+     *  An array of <code>String</code>s may be obtained containing a
+     *  representation of each of these values. An element of such an array, say,
+     *  <code>values</code>, may be accessed, for example, by:
+     *  <pre>
+     *      values[SparseBitSet.statistics.Buckets_available.ordinal()]</pre>
+     *
+     * @see         #statistics(String[])
+     */
+    public static enum Statistics
+    {
+        /**
+         *  The size of the bit set, as give by the <i>size</i>() method.
+         */
+        Size, // 0
+        /**
+         *  The length of the bit set, as give by the <i>length</i>() method.
+         */
+        Length, // 1
+        /**
+         *  The cardinality of the bit set, as give by the <i>cardinality</i>() method.
+         */
+        Cardinality, // 2
+        /**
+         *  The total number of non-zero 64-bits "words" being used to hold the
+         *  representation of the bit set.
+         */
+        Total_words, // 3
+        /**
+         *  The length of the bit set array.
+         */
+        Set_array_length, // 4
+        /**
+         *  The maximum permitted length of the bit set array.
+         */
+        Set_array_max_length, // 5
+        /**
+         *  The number of level2 areas.
+         */
+        Level2_areas, // 6
+        /**
+         *  The length of the level2 areas.
+         */
+        Level2_area_length, // 7
+        /**
+         *  The total number of level3 blocks in use.
+         */
+        Level3_blocks, // 8
+        /**
+         *  The length of the level3 blocks.
+         */
+        Level3_block_length, // 9
+        /**
+         *  Is the value that determines how the <i>toString</i>() conversion is
+         *  performed.
+         * @see         #toStringCompaction(int)
+         */
+        Compaction_count_value // 10
+    }
+
+    //=============================================================================
+    //  A set of cached statistics values, recomputed when necessary
+    //=============================================================================
+    /**
+     *  This class holds the values related to various statistics kept about the
+     *  bit set. These values are not kept continuously up-to-date. Whenever the
+     *  values become invalid, the field <i>hash</i> is set to zero, indicating
+     *  that an update is required.
+     *
+     * @see     #statisticsUpdate()
+     */
+    protected class Cache
+    {
+        /**
+         *  <i>hash</i> is updated by the <i>statisticsUpdate</i>() method.
+         *  If the <i>hash</i> value is zero, it is assumed that <b><i>all</i></b>
+         *  the cached values are stale, and must be updated.
+         */
+
+        protected transient int hash;
+
+        /**
+         *  <i>size</i> is updated by the <i>statisticsUpdate</i>() method.
+         *  If the <i>hash</i> value is zero, it is assumed the all the cached
+         *  values are stale, and must be updated.
+         */
+        protected transient int size;
+
+        /**
+         *  <i>cardinality</i> is updated by the <i>statisticsUpdate</i>() method.
+         *  If the <i>hash</i> value is zero, it is assumed the all the cached
+         *  values are stale, and must be updated.
+         */
+        protected transient int cardinality;
+
+        /**
+         *  <i>length</i> is updated by the <i>statisticsUpdate</i>() method.
+         *  If the <i>hash</i> value is zero, it is assumed the all the cached
+         *  values are stale, and must be updated.
+         */
+        protected transient int length;
+
+        /**
+         *  <i>count</i> is updated by the <i>statisticsUpdate</i>() method.
+         *  If the <i>hash</i> value is zero, it is assumed the all the cached
+         *  values are stale, and must be updated.
+         */
+        protected transient int count;
+
+        /**
+         *  <i>a2Count</i> is updated by the <i>statisticsUpdate</i>()
+         *  method, and will only be correct immediately after a full update. The
+         *  <i>hash</i> value is must be zero for all values to be updated.
+         */
+        protected transient int a2Count;
+
+        /**
+         *  <i>a3Count</i> is updated by the <i>statisticsUpdate</i>() method,
+         *  and will only be correct immediately after a full update. The
+         *  <i>hash</i> value is must be zero for all values to be updated.
+         */
+        protected transient int a3Count;
+    }
+
+    //=============================================================================
+    //  Abstract Strategy super-class for Strategies describing logical operations
+    //=============================================================================
+    /**
+     *  This strategy class is used by the setScanner to carry out the a variety
+     *  of operations on this set, and usually a second set. The
+     *  <i>setScanner</i>() method of the main <code>SparseBitSet</code> class
+     *  essentially finds matching level3 blocks, and then calls the strategy to
+     *  do the appropriate operation on each of the elements of the block.
+     *  <p>
+     *  The symbolic constants control optimisation paths in the
+     *  <i>setScanner</i>() method of the main <code>SparseBitSet</code> class.
+     *
+     *  @see SparseBitSet#setScanner(int i, int j,
+     *                          SparseBitSet b, AbstractStrategy op)
+     */
+    protected abstract static class AbstractStrategy
+    {
+        /** If the operation requires that when matching level2 areas or level3
+         *  blocks are null, that no action is required, then this property is
+         *  required. Corresponds to the top-left entry in the logic diagram for the
+         *  operation being 0. For all the defined actual logic operations ('and',
+         *  'andNot', 'or', and 'xor', this will be true, because for all these,
+         *  "false" op "false" = "false".
+         */
+        static final int F_OP_F_EQ_F = 0x1;
+
+        /** If when level2 areas or level3 areas from the this set are null will
+         *  require that area or block to remain null, irrespective of the value of
+         *  the matching structure from the other set, then this property is required.
+         *  Corresponds to the first row in the logic diagram being all zeros. For
+         *  example, this is true for 'and' as well as 'andNot', and for 'clear', since
+         *  false" & "x" = "false", and "false" &! "x" = "false".
+         */
+        static final int F_OP_X_EQ_F = 0x2;
+
+        /** If when level2 areas or level3 areas from the other set are null will
+         *  require the matching area or block in this set to be set to null,
+         *  irrespective of the current values in the matching structure from the
+         *  this, then this property is required. Corresponds to the first column
+         *  in the logic diagram being all zero. For example, this is true for
+         *  'and', since "x" & "false" = "false", as well as for 'clear'.
+         */
+        static final int X_OP_F_EQ_F = 0x4;
+
+        /** If when a level3 area from the other set is null will require the
+         *  matching area or block in this set to be left as it is, then this property
+         *  is required. Corresponds to the first column of the logic diagram being
+         *  equal to the left hand operand column. For example, this is true for 'or',
+         *  'xor', and 'andNot', since for all of these "x" op "false" = "x".
+         */
+        static final int X_OP_F_EQ_X = 0x8;
+
+        /**
+         *  Properties of this strategy.
+         *
+         * @return      the int containing the bits representing the properties of
+         *              this strategy
+         * @since       1.6
+         */
+        protected abstract int properties();
+
+        /**
+         *  Instances of this class are to be serially reusable. To start a
+         *  particular use, an instance is (re-)started by calling this method. It is
+         *  passed the reference to the other bit set (usually to allow a check on
+         *  whether it is null or not, so as to simplify the implementation of the
+         *  <i>block</i>() method.
+         *
+         * @param       b the "other" set, for whatever checking is needed.
+         * @since       1.6
+         * @return      true -> if the cache should be set to zero
+         */
+        protected abstract boolean start(SparseBitSet b);
+
+        /**
+         *  Deal with a scan that include a partial word within a level3 block. All
+         *  that is required is that the result be stored (if needed) into the
+         *  given a set block at the correct position, and that the operation only
+         *  affect those bits selected by 1 bits in the mask.
+         *
+         * @param       base the base index of the block (to be used if needed)
+         * @param       u3 the index of the word within block
+         * @param       a3 the level3 block from the <i>a</i> set.
+         * @param       b3 the (nominal) level3 block from the <i>b</i> set (not null).
+         * @param       mask for the (partial) word
+         * @return      true if the resulting word is zero
+         * @since       1.6
+         */
+
+        protected abstract boolean word(int base, int u3, long[] a3, long[] b3, long mask);
+
+        /**
+         *  Deals with a part of a block that consists of whole words, starting with
+         *  the given first index, and ending with the word before the last index.
+         *  For the words processed, the return value should indicate whether all those
+         *  resulting words were zero, or not.
+         *
+         * @param       base the base index of the block (to be used if needed)
+         * @param       u3 the index of the first word within block to process
+         * @param       v3 the index of the last word, which may be within block
+         * @param       a3 the level3 block from the <i>a</i> set.
+         * @param       b3 the (nominal) level3 block from the <i>b</i> set (not null).
+         * @return      true if the words scanned within the level3 block were all zero
+         * @since       1.6
+         */
+        protected abstract boolean block(int base, int u3, int v3, long[] a3, long[] b3);
+
+        /**
+         *  This is called to finish the processing started by the strategy (if there
+         *  needs to be anything done at all).
+         *
+         * @param       a2Count possible count of level2 areas in use
+         * @param       a3Count possible count of level3 blocks in use
+         * @since       1.6
+         */
+        protected void  finish(int a2Count, int a3Count)
+        {
+        }
+
+        /**
+         *  Check whether a level3 block is all zero.
+         *
+         * @param       a3 the block from the <i>a</i> set
+         * @return      true if the values of the level3 block are all zero
+         *
+         * @since       1.6
+         */
+        protected final boolean isZeroBlock(long[] a3)
+        {
+            for (long word : a3)
+                if (word != 0L)
+                    return false;
+            return true;
+        }
+    }
+
+    //=============================================================================
+    //  Strategies based on the Strategy super-class describing logical operations
+    //=============================================================================
+    /**
+     *  And of two sets. Where the <i>a</i> set is zero, it remains zero (i.e.,
+     *  without entries or with zero words). Similarly, where the <i>b</i> set is
+     *  zero, the <i>a</i> becomes zero (i.e., without entries).
+     *  <p>
+     *  If level1 of the <i>a</i> set is longer than level1 of the bit set
+     *  <i>b</i>, then the unmatched virtual "entries" of the <i>b</i> set (beyond
+     *  the actual length of <i>b</i>) corresponding to these are all false, hence
+     *  the result of the "and" operation will be to make all these entries in this
+     *  set to become false--hence just remove them, and then scan only those
+     *  entries that could match entries in the bit set<i>b</i>. This clearing of
+     *  the remainder of the <i>a</i> set is accomplished by selecting both
+     *  <i>F_OP_X_EQ_F</i> and <i>X_OP_F_EQ_F</i>.
+     *
+     *  <pre>
+     *  and| 0 1
+     *    0| 0 0
+     *    1| 0 1 <pre>
+     */
+    protected static class AndStrategy extends AbstractStrategy
+    {
+        @Override
+        //  AndStrategy
+        protected int properties()
+        {
+            return F_OP_F_EQ_F + F_OP_X_EQ_F + X_OP_F_EQ_F;
+        }
+
+        @Override
+        //  AndStrategy
+        protected boolean start(SparseBitSet b)
+        {
+            if (b == null)
+                throw new NullPointerException();
+            return true;
+        }
+
+        @Override
+        //  AndStrategy
+        protected boolean word(int base, int u3, long[] a3, long[] b3, long mask)
+        {
+            return (a3[u3] &= b3[u3] | ~mask) == 0L;
+        }
+
+        @Override
+        //  AndStrategy
+        protected boolean block(int base, int u3, int v3, long[] a3, long[] b3)
+        {
+            boolean isZero = true; //  Presumption
+            for (int w3 = u3; w3 != v3; ++w3)
+                isZero &= ((a3[w3] &= b3[w3]) == 0L);
+            return isZero;
+        }
+    }
+
+    //-----------------------------------------------------------------------------
+    /**
+     *  AndNot of two sets. Where the <i>a</i> set is zero, it remains zero
+     *  (i.e., without entries or with zero words). On the other hand, where the
+     *  <i>b</i> set is zero, the <i>a</i> remains unchanged.
+     *  <p>
+     *  If level1 of the <i>a</i> set is longer than level1 of the bit set
+     *  <i>b</i>, then the unmatched virtual "entries" of the <i>b</i> set (beyond
+     *  the actual length of <i>b</i>) corresponding to these are all false, hence
+     *  the result of the "and" operation will be to make all these entries in this
+     *  set to become false--hence just remove them, and then scan only those
+     *  entries that could match entries in the bit set<i>b</i>. This clearing of
+     *  the remainder of the <i>a</i> set is accomplished by selecting both
+     *  <i>F_OP_X_EQ_F</i> and <i>X_OP_F_EQ_F</i>.
+     *
+     *  <pre>
+     * andNot| 0 1
+     *      0| 0 0
+     *      1| 1 0 <pre>
+     */
+    protected static class AndNotStrategy extends AbstractStrategy
+    {
+        @Override
+        //  AndNotStrategy
+        protected int properties()
+        {
+            return F_OP_F_EQ_F + F_OP_X_EQ_F + X_OP_F_EQ_X;
+        }
+
+        @Override
+        //  AndNotStrategy
+        protected boolean start(SparseBitSet b)
+        {
+            if (b == null)
+                throw new NullPointerException();
+            return true;
+        }
+
+        @Override
+        //  AndNotStrategy
+        protected boolean word(int base, int u3, long[] a3, long[] b3, long mask)
+        {
+            return (a3[u3] &= ~(b3[u3] & mask)) == 0L;
+        }
+
+        @Override
+        //  AndNotStrategy
+        protected boolean block(int base, int u3, int v3, long[] a3, long[] b3)
+        {
+            boolean isZero = true; //  Presumption
+            for (int w3 = u3; w3 != v3; ++w3)
+                isZero &= (a3[w3] &= ~b3[w3]) == 0L;
+            return isZero;
+        }
+    }
+
+    //-----------------------------------------------------------------------------
+    /**
+     *  Clear clears bits in the <i>a</i> set.
+     *
+     * <pre>
+     * clear| 0 1
+     *     0| 0 0
+     *     1| 0 0 <pre>
+     */
+    protected static class ClearStrategy extends AbstractStrategy
+    {
+        @Override
+        //  ClearStrategy
+        protected int properties()
+        {
+            return F_OP_F_EQ_F + F_OP_X_EQ_F;
+        }
+
+        @Override
+        //  ClearStrategy
+        protected boolean start(SparseBitSet b)
+        {
+            return true;
+        }
+
+        @Override
+        //  ClearStrategy
+        protected boolean word(int base, int u3, long[] a3, long[] b3, long mask)
+        {
+            return (a3[u3] &= ~mask) == 0L;
+        }
+
+        @Override
+        //  ClearStrategy
+        protected boolean block(int base, int u3, int v3, long[] a3, long[] b3)
+        {
+            if (u3 != 0 || v3 != LENGTH3) //  Optimisation
+                for (int w3 = u3; w3 != v3; ++w3)
+                    a3[w3] = 0L;
+            return true;
+        }
+    }
+
+    //-----------------------------------------------------------------------------
+    /**
+     *  Copies the needed parts of the <i>b</i> set to the <i>a</i> set.
+     *
+     * <pre>
+     * get| 0 1
+     *   0| 0 1
+     *   1| 0 1 <pre>
+     */
+    protected static class CopyStrategy extends AbstractStrategy
+    {
+        @Override
+        //  CopyStrategy
+        protected int properties()
+        {
+            return F_OP_F_EQ_F + X_OP_F_EQ_F;
+        }
+
+        @Override
+        //  CopyStrategy
+        protected boolean start(SparseBitSet b)
+        {
+            return true;
+        }
+
+        @Override
+        //  CopyStrategy
+        protected boolean word(int base, int u3, long[] a3, long[] b3, long mask)
+        {
+            return (a3[u3] = b3[u3] & mask) == 0L;
+        }
+
+        @Override
+        //  CopyStrategy
+        protected boolean block(int base, int u3, int v3, long[] a3, long[] b3)
+        {
+            boolean isZero = true;
+            for (int w3 = u3; w3 != v3; ++w3)
+                isZero &= (a3[w3] = b3[w3]) == 0L;
+            return isZero;
+        }
+    }
+
+    //-----------------------------------------------------------------------------
+    /**
+     *  Equals compares bits in the <i>a</i> set with those in the <i>b</i> set.
+     *  None of the values in either set are changed, although the <i>a</i> set
+     *  may have all zero level 3 blocks replaced by null references (and
+     *  similarly at level 2).
+     *
+     * <pre>
+     * equals| 0 1
+     *      0| 0 -
+     *      1| - - <pre>
+     */
+    protected static class EqualsStrategy extends AbstractStrategy
+    {
+        boolean result; // Used to hold result of the comparison
+
+        @Override
+        //  EqualsStrategy
+        protected int properties()
+        {
+            return F_OP_F_EQ_F;
+        }
+
+        @Override
+        //  EqualsStrategy
+        protected boolean start(SparseBitSet b)
+        {
+            if (b == null)
+                throw new InternalError();
+            result = true;
+            return false;
+            /*  Equals does not change the content of the set, hence hash need
+                not be reset. */
+        }
+
+        @Override
+        //  EqualsStrategy
+        protected boolean word(int base, int u3, long[] a3, long[] b3, long mask)
+        {
+            final long word = a3[u3];
+            result &= (word & mask) == (b3[u3] & mask);
+            return word == 0L;
+        }
+
+        @Override
+        //  EqualsStrategy
+        protected boolean block(int base, int u3, int v3, long[] a3, long[] b3)
+        {
+
+            boolean isZero = true; //  Presumption
+            for (int w3 = u3; w3 != v3; ++w3)
+            {
+                final long word = a3[w3];
+                result &= word == b3[w3];
+                isZero &= word == 0L;
+            }
+            return isZero;
+        }
+    }
+
+    //-----------------------------------------------------------------------------
+    /**
+     *  Flip inverts the bits of the <i>a</i> set within the given range.
+     *
+     * <pre>
+     * flip| 0 1
+     *    0| 1 1
+     *    1| 0 0 <pre>
+     */
+    protected static class FlipStrategy extends AbstractStrategy
+    {
+        @Override
+        // FlipStrategy
+        protected int properties()
+        {
+            return 0;
+        }
+
+        @Override
+        // FlipStrategy
+        protected boolean start(SparseBitSet b)
+        {
+            return true;
+        }
+
+        @Override
+        // FlipStrategy
+        protected boolean word(int base, int u3, long[] a3, long[] b3, long mask)
+        {
+            return (a3[u3] ^= mask) == 0L;
+        }
+
+        @Override
+        // FlipStrategy
+        protected boolean block(int base, int u3, int v3, long[] a3, long[] b3)
+        {
+            boolean isZero = true; //  Presumption
+            for (int w3 = u3; w3 != v3; ++w3)
+                isZero &= (a3[w3] ^= ~0L) == 0L;
+            return isZero;
+        }
+    }
+
+    //-----------------------------------------------------------------------------
+    /**
+     *  Intersect has a true result if any word in the <i>a</i> set has a bit
+     *  in common with the <i>b</i> set. During the scan of the <i>a</i> set
+     *  blocks (and areas) that are all zero may be replaced with empty blocks
+     *  and areas (null references), but the value of the set is not changed
+     *  (which is why X_OP_F_EQ_F is not selected, since this would cause
+     *  parts of the <i>a</i> set to be zero-ed out).
+     *
+     * <pre>
+     * intersect| 0 1
+     *         0| 0 0
+     *         1| 1 1 <pre>
+     */
+    protected static class IntersectsStrategy extends AbstractStrategy
+    {
+        /**
+         *  The boolean result of the intersects scan Strategy is kept here.
+         */
+        protected boolean result;
+
+        @Override
+        //  IntersectsStrategy
+        protected int properties()
+        {
+            return F_OP_F_EQ_F + F_OP_X_EQ_F;
+        }
+
+        @Override
+        //  IntersectsStrategy
+        protected boolean start(SparseBitSet b)
+        {
+            if (b == null)
+                throw new NullPointerException();
+            result = false;
+            return false;
+            /*  Intersect does not change the content of the set, hence hash
+                need not be reset. */
+        }
+
+        @Override
+        //  IntersectsStrategy
+        protected boolean word(int base, int u3, long[] a3, long[] b3, long mask)
+        {
+            final long word = a3[u3];
+            result |= (word & b3[u3] & mask) != 0L;
+            return word == 0L;
+        }
+
+        @Override
+        //  IntersectsStrategy
+        protected boolean block(int base, int u3, int v3, long[] a3, long[] b3)
+        {
+            boolean isZero = true; //  Presumption
+            for (int w3 = u3; w3 != v3; ++w3)
+            {
+                final long word = a3[w3];
+                result |= (word & b3[w3]) != 0L;
+                isZero &= word == 0L;
+            }
+            return isZero;
+        }
+    }
+
+    /**
+     *  Or of two sets. Where the <i>a</i> set is one, it remains one. Similarly,
+     *  where the <i>b</i> set is one, the <i>a</i> becomes one. If both sets have
+     *  zeros in corresponding places, a zero results. Whole blocks or areas that
+     *  are or become zero are replaced by null arrays.
+     *  <p>
+     *  If level1 of the <i>a</i> set is longer than level1 of the bit set
+     *  <i>b</i>, then the unmatched entries of the <i>a</i> set (beyond
+     *  the actual length of <i>b</i>) corresponding to these remain unchanged. *
+     *  <pre>
+     *   or| 0 1
+     *    0| 0 1
+     *    1| 1 1 <pre>
+     */
+    protected static class OrStrategy extends AbstractStrategy
+    {
+        @Override
+        //  OrStrategy
+        protected int properties()
+        {
+            return F_OP_F_EQ_F + X_OP_F_EQ_X;
+        }
+
+        @Override
+        //  OrStrategy
+        protected boolean start(SparseBitSet b)
+        {
+            if (b == null)
+                throw new NullPointerException();
+            return true;
+        }
+
+        @Override
+        //  OrStrategy
+        protected boolean word(int base, int u3, long[] a3, long[] b3, long mask)
+        {
+            return (a3[u3] |= b3[u3] & mask) == 0L;
+        }
+
+        @Override
+        //  OrStrategy
+        protected boolean block(int base, int u3, int v3, long[] a3, long[] b3)
+        {
+            boolean isZero = true; //  Presumption
+            for (int w3 = u3; w3 != v3; ++w3)
+                isZero &= (a3[w3] |= b3[w3]) == 0L;
+            return isZero;
+        }
+    }
+
+    //-----------------------------------------------------------------------------
+    /**
+     *  Set creates entries everywhere within the range. Hence no empty level2
+     *  areas or level3 blocks are ignored, and no empty (all zero) blocks are
+     *  returned.
+     *
+     *  <pre>
+     * set| 0 1
+     *   0| 1 1
+     *   1| 1 1 <pre>
+     */
+    protected static class SetStrategy extends AbstractStrategy
+    {
+        @Override
+        //  SetStrategy
+        protected int properties()
+        {
+            return 0;
+        }
+
+        @Override
+        //  SetStrategy
+        protected boolean start(SparseBitSet b)
+        {
+            return true;
+        }
+
+        @Override
+        //  SetStrategy
+        protected boolean word(int base, int u3, long[] a3, long[] b3, long mask)
+        {
+            a3[u3] |= mask;
+            return false;
+        }
+
+        @Override
+        //  SetStrategy
+        protected boolean block(int base, int u3, int v3, long[] a3, long[] b3)
+        {
+            for (int w3 = u3; w3 != v3; ++w3)
+                a3[w3] = ~0L;
+            return false; // set always sets bits
+        }
+    }
+
+    //-----------------------------------------------------------------------------
+    /**
+     *  Update the seven statistics that are computed for each set. These are
+     *  updated by calling <i>statisticsUpdate</i>, which uses this strategy.
+     *
+     *  <pre>
+     *  update| 0 1
+     *       0| 0 0
+     *       1| 1 1 <pre>
+     *
+     * @see SparseBitSet#statisticsUpdate()
+     */
+    protected class UpdateStrategy extends AbstractStrategy
+    {
+        /**
+         *  Working space for find the size and length of the bit set. Holds the
+         *  index of the first non-empty word in the set.
+         */
+        protected transient int wMin;
+
+        /**
+         *  Working space for find the size and length of the bit set. Holds copy of
+         *  the first non-empty word in the set.
+         */
+        protected transient long wordMin;
+
+        /**
+         *  Working space for find the size and length of the bit set. Holds the
+         *  index of the last non-empty word in the set.
+         */
+        protected transient int wMax;
+
+        /**
+         *  Working space for find the size and length of the bit set. Holds a copy
+         *  of the last non-empty word in the set.
+         */
+        protected transient long wordMax;
+
+        /**
+         *  Working space for find the hash value of the bit set. Holds the
+         *  current state of the computation of the hash value. This value is
+         *  ultimately transferred to the Cache object.
+         *
+         * @see SparseBitSet.Cache
+         */
+        protected transient long hash;
+
+        /**
+         *  Working space for keeping count of the number of non-zero words in the
+         *  bit set. Holds the current state of the computation of the count. This
+         *  value is ultimately transferred to the Cache object.
+         *
+         * @see SparseBitSet.Cache
+         */
+        protected transient int count;
+
+        /**
+         *  Working space for counting the number of non-zero bits in the bit set.
+         *  Holds the current state of the computation of the cardinality.This
+         *  value is ultimately transferred to the Cache object.
+         *
+         * @see SparseBitSet.Cache
+         */
+        protected transient int cardinality;
+
+        @Override
+        //  UpdateStrategy
+        protected int properties()
+        {
+            return F_OP_F_EQ_F + F_OP_X_EQ_F;
+        }
+
+        /**
+         *  This method initializes the computations by suitably resetting cache
+         *  fields or working fields.
+         *
+         * @param       b the other SparseBitSet, for checking if needed.
+         *
+         * @since       1.6
+         */
+        @Override
+        protected boolean start(SparseBitSet b)
+        {
+            hash = 1234L; // Magic number
+            wMin = -1; // index of first non-zero word
+            wordMin = 0L; // word at that index
+            wMax = 0; // index of last non-zero word
+            wordMax = 0L; // word at that index
+            count = 0; // count of non-zero words in whole set
+            cardinality = 0; // count of non-zero bits in the whole set
+            return false;
+        }
+
+        @Override
+        protected boolean word(int base, int u3, long[] a3, long[] b3, long mask)
+        {
+            final long word = a3[u3];
+            final long word1 = word & mask;
+            if (word1 != 0L)
+                compute(base + u3, word1);
+            return word == 0L;
+        }
+
+        @Override
+        //  UpdateStrategy
+        protected boolean block(int base, int u3, int v3, long[] a3, long[] b3)
+        {
+            boolean isZero = true; //  Presumption
+            for (int w3 = 0; w3 != v3; ++w3)
+            {
+                final long word = a3[w3];
+                if (word != 0)
+                {
+                    isZero = false;
+                    compute(base + w3, word);
+                }
+            }
+            return isZero;
+        }
+
+        @Override
+        //  UpdateStrategy
+        protected void finish(int a2Count, int a3Count)
+        {
+            cache.a2Count = a2Count;
+            cache.a3Count = a3Count;
+            cache.count = count;
+            cache.cardinality = cardinality;
+            cache.length = (wMax + 1) * LENGTH4 - Long.numberOfLeadingZeros(wordMax);
+            cache.size = cache.length - wMin * LENGTH4
+                    - Long.numberOfTrailingZeros(wordMin);
+            cache.hash = (int) ((hash >> Integer.SIZE) ^ hash);
+        }
+
+        /**
+         *  This method does the accumulation of the statistics. It must be called
+         *  in sequential order of the words in the set for which the statistics
+         *  are being accumulated, and only for non-null values of the second
+         *  parameter.
+         *
+         *  Two of the values (a2Count and a3Count) are not updated here,
+         *  but are done in the code near where this method is called.
+         *
+         * @param       index the word index of the word supplied
+         * @param       word the long non-zero word from the set
+         * @since       1.6
+         */
+        private void compute(final int index, final long word)
+        {
+            /*  Count the number of actual words being used. */
+            ++count;
+            /*  Contine to accumulate the hash value of the set. */
+            hash ^= word * (long) (index + 1);
+            /*  The first non-zero word contains the first actual bit of the
+                set. The location of this bit is used to compute the set size. */
+            if (wMin < 0)
+            {
+                wMin = index;
+                wordMin = word;
+            }
+            /*  The last non-zero word contains the last actual bit of the set.
+                The location of this bit is used to compute the set length. */
+            wMax = index;
+            wordMax = word;
+            /*  Count the actual bits, so as to get the cardinality of the set. */
+            cardinality += Long.bitCount(word);
+        }
+    }
+
+    //-----------------------------------------------------------------------------
+    /**
+     *  The XOR of level3 blocks is computed.
+     *
+     * <pre>
+     * xor| 0 1
+     *   0| 0 1
+     *   1| 1 0 <pre>
+     */
+    protected static class XorStrategy extends AbstractStrategy
+    {
+        @Override
+        //  XorStrategy
+        protected int properties()
+        {
+            return F_OP_F_EQ_F + X_OP_F_EQ_X;
+        }
+
+        @Override
+        //  XorStrategy
+        protected boolean start(SparseBitSet b)
+        {
+            if (b == null)
+                throw new NullPointerException();
+            return true;
+        }
+
+        @Override
+        protected boolean word(int base, int u3, long[] a3, long[] b3, long mask)
+        {
+            return (a3[u3] ^= b3[u3] & mask) == 0;
+        }
+
+        @Override
+        //  XorStrategy
+        protected boolean block(int base, int u3, int v3, long[] a3, long[] b3)
+        {
+            boolean isZero = true; //  Presumption
+            for (int w3 = u3; w3 != v3; ++w3)
+                isZero &= (a3[w3] ^= b3[w3]) == 0;
+            return isZero;
+
+        }
+    }
+
+    //-----------------------------------------------------------------------------
+    /**
+     *  Word and block <b>and</b> strategy.
+     */
+    protected static final transient AndStrategy andStrategy = new AndStrategy();
+    /**
+     *  Word and block <b>andNot</b> strategy.
+     */
+    protected static final transient AndNotStrategy andNotStrategy = new AndNotStrategy();
+    /**
+     *  Word and block <b>clear</b> strategy.
+     */
+    protected static final transient ClearStrategy clearStrategy = new ClearStrategy();
+    /**
+     *  Word and block <b>copy</b> strategy.
+     */
+    protected static final transient CopyStrategy copyStrategy = new CopyStrategy();
+    /**
+     *  Word and block <b>equals</b> strategy.
+     */
+    protected transient EqualsStrategy equalsStrategy;
+    /**
+     *  Word and block <b>flip</b> strategy.
+     */
+    protected static final transient FlipStrategy flipStrategy = new FlipStrategy();
+    /**
+     *  Word and block <b>intersects</b> strategy.
+     */
+    protected transient IntersectsStrategy intersectsStrategy;
+    /**
+     *  Word and block <b>or</b> strategy.
+     */
+    protected static final transient OrStrategy orStrategy = new OrStrategy();
+    /**
+     *  Word and block <b>set</b> strategy.
+     */
+    protected static final transient SetStrategy setStrategy = new SetStrategy();
+    /**
+     *  Word and block <b>update</b> strategy.
+     */
+    protected transient UpdateStrategy updateStrategy;
+    /**
+     *  Word and block <b>xor</b> strategy.
+     */
+    protected static final transient XorStrategy xorStrategy = new XorStrategy();
+}
diff --git a/prism/src/param/ParamModel.java b/prism/src/param/ParamModel.java
index 2e9363c..e0d1bf3 100644
--- a/prism/src/param/ParamModel.java
+++ b/prism/src/param/ParamModel.java
@@ -27,11 +27,9 @@
 package param;
 
 import java.io.File;
-import java.util.BitSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.TreeSet;
+import java.util.*;
 
+import it.unimi.dsi.fastutil.ints.IntIterator;
 import parser.Values;
 import prism.ModelType;
 import prism.PrismException;
@@ -76,8 +74,8 @@ final class ParamModel extends ModelExplicit
 		numStates = 0;
 		numTotalChoices = 0;
 		numTotalTransitions = 0;
-		initialStates = new LinkedList<Integer>();
-		deadlocks = new TreeSet<Integer>();
+		initialStates = new ArrayList<>();
+		deadlocks = new TreeSet<>();
 	}
 
 	/**
@@ -111,7 +109,7 @@ final class ParamModel extends ModelExplicit
 	}
 
 	@Override
-	public Iterator<Integer> getSuccessorsIterator(int s)
+	public IntIterator getSuccessorsIterator(int s)
 	{
 		throw new UnsupportedOperationException();
 	}
@@ -423,7 +421,7 @@ final class ParamModel extends ModelExplicit
 	{
 		ParamModel result = new ParamModel();
 		result.reserveMem(numStates, numTotalChoices, numTotalTransitions);
-		result.initialStates = new LinkedList<Integer>(this.initialStates);
+		result.initialStates = new ArrayList<>(this.initialStates);
 		for (int state = 0; state < numStates; state++) {
 			for (int choice = stateBegin(state); choice < stateEnd(state); choice++) {
 				for (int succ = choiceBegin(choice); succ < choiceEnd(choice); succ++) {
diff --git a/prism/src/parser/State.java b/prism/src/parser/State.java
index 48fec35..a327b51 100644
--- a/prism/src/parser/State.java
+++ b/prism/src/parser/State.java
@@ -64,11 +64,11 @@ public class State implements Comparable<State>
 	 */
 	public State(State s1, State s2)
 	{
-		Object[] arr1 = (Object[]) s1.varValues;
-		Object[] arr2 = (Object[]) s2.varValues;
+		Object[] arr1 = s1.varValues;
+		Object[] arr2 = s2.varValues;
 		varValues = new Object[arr1.length + arr2.length];
 		int i;
-		for (i = 0; i < arr1.length; i++)
+		for (i = 0; i < arr1.length; i++) // System.arrayCopy
 			varValues[i] = arr1[i];
 		for (i = 0; i < arr2.length; i++)
 			varValues[arr1.length + i] = arr2[i];
diff --git a/prism/src/parser/ast/PropertiesFile.java b/prism/src/parser/ast/PropertiesFile.java
index 050bf4a..59608d9 100644
--- a/prism/src/parser/ast/PropertiesFile.java
+++ b/prism/src/parser/ast/PropertiesFile.java
@@ -47,7 +47,7 @@ public class PropertiesFile extends ASTElement
 	private LabelList labelList;
 	private LabelList combinedLabelList; // Labels from both model/here
 	private ConstantList constantList;
-	private Vector<Property> properties; // Properties
+	public Vector<Property> properties; // Properties
 
 	// list of all identifiers used
 	private Vector<String> allIdentsUsed;
diff --git a/prism/src/prism/ExplicitModel2MTBDD.java b/prism/src/prism/ExplicitModel2MTBDD.java
index 5eab016..bb6074d 100644
--- a/prism/src/prism/ExplicitModel2MTBDD.java
+++ b/prism/src/prism/ExplicitModel2MTBDD.java
@@ -28,6 +28,7 @@ package prism;
 
 import java.util.*;
 
+import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
 import jdd.*;
 import parser.*;
 import parser.ast.*;
@@ -391,7 +392,7 @@ public class ExplicitModel2MTBDD
 		if (modelType == ModelType.DTMC || modelType == ModelType.CTMC) {
 			DTMC dtmc = (DTMC) modelExpl;
 			for (r = 0; r < numStates; r++) {
-				Iterator<Map.Entry<Integer, Double>> iter = dtmc.getTransitionsIterator(r);
+				Iterator<Int2DoubleMap.Entry> iter = dtmc.getTransitionsIterator(r);
 				while (iter.hasNext()) {
 					Map.Entry<Integer, Double> e = iter.next();
 					c = (Integer) e.getKey();
@@ -438,9 +439,7 @@ public class ExplicitModel2MTBDD
 			for (r = 0; r < numStates; r++) {
 				int nc = mdp.getNumChoices(r);
 				for (k = 0; k < nc; k++) {
-					Iterator<Map.Entry<Integer, Double>> iter = mdp.getTransitionsIterator(r, k);
-					while (iter.hasNext()) {
-						Map.Entry<Integer, Double> e = iter.next();
+					for (Map.Entry<Integer, Double> e : mdp.getTransitions(r, k)) {
 						c = (Integer) e.getKey();
 						d = (Double) e.getValue();
 						a = "";
diff --git a/prism/src/prism/Prism.java b/prism/src/prism/Prism.java
index 34b8cde..2160dd9 100644
--- a/prism/src/prism/Prism.java
+++ b/prism/src/prism/Prism.java
@@ -39,7 +39,6 @@ import jdd.JDD;
 import jdd.JDDNode;
 import jdd.JDDVars;
 import mtbdd.PrismMTBDD;
-import odd.ODDUtils;
 import param.BigRational;
 import param.ModelBuilder;
 import param.ParamModelChecker;
@@ -62,7 +61,6 @@ import simulator.SimulatorEngine;
 import simulator.method.SimulationMethod;
 import sparse.PrismSparse;
 import strat.Strategy;
-import dv.DoubleVector;
 import explicit.CTMC;
 import explicit.CTMCModelChecker;
 import explicit.ConstructModel;
@@ -1289,21 +1287,21 @@ public class Prism extends PrismComponent implements PrismSettingsListener
 		JDD.SetOutputStream(mainLog.getFilePointer());
 
 		// initialise libraries/engines
-		PrismNative.initialise(this);
-		PrismMTBDD.initialise(mainLog, mainLog);
-		PrismSparse.initialise(mainLog, mainLog);
-		PrismHybrid.initialise(mainLog, mainLog);
+		//PrismNative.initialise(this);
+		//PrismMTBDD.initialise(mainLog, mainLog);
+		//PrismSparse.initialise(mainLog, mainLog);
+		//PrismHybrid.initialise(mainLog, mainLog);
 
 		// set cudd manager in other packages
-		DoubleVector.setCUDDManager();
-		ODDUtils.setCUDDManager();
+		//DoubleVector.setCUDDManager();
+		//ODDUtils.setCUDDManager();
 	}
 
 	/**
 	 * Parse a PRISM model from a file.
 	 * @param file File to read in
 	 */
-	public ModulesFile parseModelFile(File file) throws FileNotFoundException, PrismLangException
+	public static ModulesFile parseModelFile(File file) throws FileNotFoundException, PrismLangException
 	{
 		return parseModelFile(file, null);
 	}
@@ -1313,7 +1311,7 @@ public class Prism extends PrismComponent implements PrismSettingsListener
 	 * @param file File to read in
 	 * @param typeOverride Optionally, override model type here (null if unused)
 	 */
-	public ModulesFile parseModelFile(File file, ModelType typeOverride) throws FileNotFoundException, PrismLangException
+	public static ModulesFile parseModelFile(File file, ModelType typeOverride) throws FileNotFoundException, PrismLangException
 	{
 		FileInputStream strModel;
 		PrismParser prismParser;
@@ -1400,24 +1398,6 @@ public class Prism extends PrismComponent implements PrismSettingsListener
 	}
 
 	/**
-	 * Import a PRISM model from an SBML model in a file
-	 * @param file File to read in
-	 */
-	public ModulesFile importSBMLFile(File file) throws PrismException, PrismLangException
-	{
-		return importModelFile("sbml", file);
-	}
-
-	/**
-	 * Import a PRISM model from an SBML model in a string
-	 * @param file File to read in
-	 */
-	public ModulesFile importSBMLString(String s) throws PrismException, PrismLangException
-	{
-		return importModelString("sbml", s);
-	}
-
-	/**
 	 * Import a PRISM model by translating from another language
 	 */
 	public ModulesFile importModelFile(String lang, File file) throws PrismException, PrismLangException
@@ -1485,7 +1465,7 @@ public class Prism extends PrismComponent implements PrismSettingsListener
 	 * @param modelInfo Accompanying model info (null if not needed)
 	 * @param file File to read in
 	 */
-	public PropertiesFile parsePropertiesFile(ModelInfo modelInfo, File file) throws FileNotFoundException, PrismLangException
+	public static PropertiesFile parsePropertiesFile(ModelInfo modelInfo, File file) throws FileNotFoundException, PrismLangException
 	{
 		return parsePropertiesFile(modelInfo, file, true);
 	}
@@ -1499,7 +1479,7 @@ public class Prism extends PrismComponent implements PrismSettingsListener
 	 * @param file File to read in
 	 * @param tidy Whether or not to do "tidy" (post-parse checks and processing)
 	 */
-	public PropertiesFile parsePropertiesFile(ModelInfo modelInfo, File file, boolean tidy) throws FileNotFoundException, PrismLangException
+	public static PropertiesFile parsePropertiesFile(ModelInfo modelInfo, File file, boolean tidy) throws FileNotFoundException, PrismLangException
 	{
 		FileInputStream strProperties;
 		PrismParser prismParser;
@@ -1535,7 +1515,7 @@ public class Prism extends PrismComponent implements PrismSettingsListener
 	 * @param modelInfo Accompanying model info (null if not needed)
 	 * @param s String to parse
 	 */
-	public PropertiesFile parsePropertiesString(ModelInfo modelInfo, String s) throws PrismLangException
+	public static PropertiesFile parsePropertiesString(ModelInfo modelInfo, String s) throws PrismLangException
 	{
 		PrismParser prismParser;
 		PropertiesFile propertiesFile = null;
@@ -1564,7 +1544,7 @@ public class Prism extends PrismComponent implements PrismSettingsListener
 	 * Parse a single PRISM expression from a string.
 	 * @param s String to parse
 	 */
-	public Expression parseSingleExpressionString(String s) throws PrismLangException
+	public static Expression parseSingleExpressionString(String s) throws PrismLangException
 	{
 		PrismParser prismParser;
 		Expression expr;
diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java
index 72aab5d..2fd8aac 100644
--- a/prism/src/prism/PrismCL.java
+++ b/prism/src/prism/PrismCL.java
@@ -583,7 +583,7 @@ public class PrismCL implements PrismModelListener
 			}
 			// if properties were given on command line...
 			else if (!propertyString.equals("")) {
-				propertiesFile = prism.parsePropertiesString(modulesFile, propertyString);
+				propertiesFile = Prism.parsePropertiesString(modulesFile, propertyString);
 			} else {
 				propertiesFile = null;
 			}
@@ -892,7 +892,7 @@ public class PrismCL implements PrismModelListener
 
 				// Parse time specification, store as UndefinedConstant for constant T
 				String timeType = modelType.continuousTime() ? "double" : "int";
-				UndefinedConstants ucTransient = new UndefinedConstants(null, prism.parsePropertiesString(null, "const " + timeType + " T; T;"));
+				UndefinedConstants ucTransient = new UndefinedConstants(null, Prism.parsePropertiesString(null, "const " + timeType + " T; T;"));
 				try {
 					ucTransient.defineUsingConstSwitch("T=" + transientTime);
 				} catch (PrismException e) {
diff --git a/prism/src/prism/PrismNG.java b/prism/src/prism/PrismNG.java
deleted file mode 100644
index 2be47bf..0000000
--- a/prism/src/prism/PrismNG.java
+++ /dev/null
@@ -1,81 +0,0 @@
-//==============================================================================
-//	
-//	Copyright (c) 2015-
-//	Authors:
-//	* Joachim Klein <klein@tcs.inf.tu-dresden.de (TU Dresden)
-//	* Dave Parker <d.a.parker@cs.bham.ac.uk> (University of Birmingham/Oxford)
-//	
-//------------------------------------------------------------------------------
-//	
-//	This file is part of PRISM.
-//	
-//	PRISM is free software; you can redistribute it and/or modify
-//	it under the terms of the GNU General Public License as published by
-//	the Free Software Foundation; either version 2 of the License, or
-//	(at your option) any later version.
-//	
-//	PRISM is distributed in the hope that it will be useful,
-//	but WITHOUT ANY WARRANTY; without even the implied warranty of
-//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//	GNU General Public License for more details.
-//	
-//	You should have received a copy of the GNU General Public License
-//	along with PRISM; if not, write to the Free Software Foundation,
-//	Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-//	
-//==============================================================================
-
-package prism;
-
-import java.util.Map;
-import java.util.Map.Entry;
-
-import com.martiansoftware.nailgun.NGContext;
-
-/**
- * Entry point for Nailgun.
- */
-public class PrismNG
-{
-	/** The current NailGun context */
-	private static NGContext currentNailGunContext = null;
-
-	/**
-	 * This method is called on the Nailgun server to start a new PRISM instance
-	 * to serve as the counterpart for a client invocation.
-	 * <br/>
-	 * This method is synchronized to ensure that there can only be a single
-	 * running instance of PRISM in the Nailgun server VM.
-	 * <br/>
-	 * Sets the working directory to that of the client.
-	 */
-	public synchronized static void nailMain(NGContext context) throws InterruptedException
-	{
-		currentNailGunContext = context;
-		if (PrismNative.setWorkingDirectory(context.getWorkingDirectory()) != 0) {
-			System.err.println("Nailgun: Can not change working directory to " + context.getWorkingDirectory());
-			System.exit(1);
-		}
-
-		PrismCL.main(context.getArgs());
-	}
-
-	/**
-	 * Modifies the environment of the ProcessBuilder by adding the environment
-	 * variables passed by the Nailgun client.
-	 * <br/>
-	 * If we are not running in a Nailgun context, this is a no-op.
-	 * */
-	public static void setupChildProcessEnvironment(ProcessBuilder builder)
-	{
-		if (currentNailGunContext == null)
-			return;
-
-		Map<String, String> env = builder.environment();
-		for (Entry<Object, Object> entry : currentNailGunContext.getEnv().entrySet()) {
-			if (entry.getKey() instanceof String && entry.getValue() instanceof String) {
-				env.put((String) entry.getKey(), (String) entry.getValue());
-			}
-		}
-	}
-}
diff --git a/prism/src/prism/PrismSettings.java b/prism/src/prism/PrismSettings.java
index 6120cbf..9d8e09e 100644
--- a/prism/src/prism/PrismSettings.java
+++ b/prism/src/prism/PrismSettings.java
@@ -540,7 +540,7 @@ public class PrismSettings implements Observer
 		
 		//populate a hash table with the keys
 		populateHashTable(counter);
-		settingsListeners = new ArrayList<PrismSettingsListener>();
+		settingsListeners = new ArrayList<>();
 	}
 	
 	/**
@@ -564,7 +564,7 @@ public class PrismSettings implements Observer
 	
 	private void populateHashTable(int size)
 	{
-		data = new Hashtable<String,Setting>(size);
+		data = new Hashtable<>(size);
 		
 		for(int i = 0; i < optionOwners.length; i++)
 		{
diff --git a/prism/src/prism/PrismTest.java b/prism/src/prism/PrismTest.java
index 110635c..072d6eb 100644
--- a/prism/src/prism/PrismTest.java
+++ b/prism/src/prism/PrismTest.java
@@ -44,7 +44,7 @@ public class PrismTest
 		new PrismTest().go(args);
 	}
 	
-	public void go(String args[])
+	public static void go(String args[])
 	{
 		try {
 			PrismLog mainLog;
@@ -64,12 +64,12 @@ public class PrismTest
 			prism.loadPRISMModel(modulesFile);
 			
 			// Parse a prop, check on model 1
-			propertiesFile = prism.parsePropertiesString(modulesFile, "P=?[F<=0.1 s1=1]");
+			propertiesFile = Prism.parsePropertiesString(modulesFile, "P=?[F<=0.1 s1=1]");
 			result = prism.modelCheck(propertiesFile, propertiesFile.getPropertyObject(0));
 			System.out.println(result.getResult());
 			
 			// Parse another prop, check on model 1
-			propertiesFile = prism.parsePropertiesString(modulesFile, "P=?[F<=0.1 s1=1]");
+			propertiesFile = Prism.parsePropertiesString(modulesFile, "P=?[F<=0.1 s1=1]");
 			result = prism.modelCheck(propertiesFile, propertiesFile.getPropertyObject(0));
 			System.out.println(result.getResult());
 			
@@ -78,7 +78,7 @@ public class PrismTest
 			prism.loadPRISMModel(modulesFile);
 			
 			// Parse a prop, check on model 2
-			propertiesFile = prism.parsePropertiesString(modulesFile, "P=?[F<=0.1 s1=1]");
+			propertiesFile = Prism.parsePropertiesString(modulesFile, "P=?[F<=0.1 s1=1]");
 			result = prism.modelCheck(propertiesFile, propertiesFile.getPropertyObject(0));
 			System.out.println(result.getResult());
 			
diff --git a/prism/src/prism/TestModelGenerator.java b/prism/src/prism/TestModelGenerator.java
index a454d90..d6e12b8 100644
--- a/prism/src/prism/TestModelGenerator.java
+++ b/prism/src/prism/TestModelGenerator.java
@@ -201,7 +201,7 @@ public class TestModelGenerator extends DefaultModelGenerator
 				explicit.Model model = constructModel.constructModel(modelGen);
 				model.exportToDotFile(new PrismFileLog("test.dot"), null, true);
 				DTMCModelChecker mc = new DTMCModelChecker(prism);
-				PropertiesFile pf = prism.parsePropertiesString(modelGen, "P=? [F \"goal\"]");
+				PropertiesFile pf = Prism.parsePropertiesString(modelGen, "P=? [F \"goal\"]");
 				Expression expr = pf.getProperty(0);
 				Result res = mc.check(model, expr);
 				System.out.println(res);
@@ -210,7 +210,7 @@ public class TestModelGenerator extends DefaultModelGenerator
 				TestModelGenerator modelGen2 = new TestModelGenerator(10);
 				prism.loadModelGenerator(modelGen2);
 				prism.exportTransToFile(true, Prism.EXPORT_DOT_STATES, new File("test2.dot"));
-				PropertiesFile pf = prism.parsePropertiesString(modelGen2, "P=? [F x=10]");
+				PropertiesFile pf = Prism.parsePropertiesString(modelGen2, "P=? [F x=10]");
 				Expression expr = pf.getProperty(0);
 				Result res = prism.modelCheck(pf, expr);
 				System.out.println(res);
diff --git a/prism/src/settings/DefaultSettingOwner.java b/prism/src/settings/DefaultSettingOwner.java
index 93546a2..06050f6 100644
--- a/prism/src/settings/DefaultSettingOwner.java
+++ b/prism/src/settings/DefaultSettingOwner.java
@@ -44,7 +44,7 @@ public class DefaultSettingOwner extends Observable implements SettingOwner
 		this.name = name;
 		this.id = id;
 		display = null;
-		settings = new ArrayList<Setting>();
+		settings = new ArrayList<>();
 	}
 
 	public int compareTo(Object o)
diff --git a/prism/src/settings/FileEditor.java b/prism/src/settings/FileEditor.java
deleted file mode 100644
index 1c5e8f5..0000000
--- a/prism/src/settings/FileEditor.java
+++ /dev/null
@@ -1,195 +0,0 @@
-//==============================================================================
-//	
-//	Copyright (c) 2002-
-//	Authors:
-//	* Andrew Hinton <ug60axh@cs.bham.ac.uk> (University of Birmingham)
-//	
-//------------------------------------------------------------------------------
-//	
-//	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 settings;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.*;
-import javax.swing.event.*;
-import java.util.*;
-import java.io.*;
-
-import userinterface.GUIPrism;
-
-public class FileEditor implements SettingEditor, ActionListener, FocusListener
-{
-	private JLabel renderer;
-	private JPanel panel;
-	private ImageIcon warningIcon = GUIPrism.getIconFromImage("smallError.png");
-	private JButton button;
-	private JTable lastTable = null;
-	private int tableRow = -1;
-	private int tableCol = -1;
-	private JPanel blank1;
-	private FileSetting lastSetting;
-	private boolean dialogFocus = false;
-	private boolean modified = false;
-
-	public FileEditor()
-	{
-		panel = new JPanel();
-		panel.setLayout(new BorderLayout());
-		renderer = new JLabel();
-		renderer = new javax.swing.JLabel();
-		blank1 = new javax.swing.JPanel();
-		button = new javax.swing.JButton("...");
-		button.setFont(new Font("serif", Font.PLAIN, 7));
-		panel.add(renderer, BorderLayout.CENTER);
-		blank1.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
-		blank1.setPreferredSize(new Dimension(18, 2));
-		button.setPreferredSize(new Dimension(16, 12));
-		blank1.add(button);
-		panel.add(blank1, BorderLayout.EAST);
-		button.addActionListener(this);
-		button.addFocusListener(this);
-	}
-
-	public Object getEditorValue()
-	{
-		if (modified) {
-			modified = false;
-			return new File(renderer.getText());
-		} else
-			return NOT_CHANGED_VALUE;
-	}
-
-	public Component getTableCellEditorComponent(JTable table, Setting setting, Object value, boolean isSelected, int row, int column)
-	{
-		if (isSelected) {
-			renderer.setForeground(table.getSelectionForeground());
-			renderer.setBackground(table.getSelectionBackground());
-			panel.setBackground(table.getSelectionBackground());
-			blank1.setBackground(table.getSelectionBackground());
-			button.setBackground(table.getSelectionBackground());
-		} else {
-			renderer.setForeground(table.getForeground());
-			renderer.setBackground(table.getBackground());
-			panel.setBackground(table.getBackground());
-			blank1.setBackground(table.getBackground());
-			button.setBackground(table.getBackground());
-		}
-
-		panel.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
-
-		if (setting instanceof FileSetting) {
-			lastSetting = (FileSetting) setting;
-			if (!lastSetting.isValidFile()) {
-				renderer.setIcon(warningIcon);
-			} else {
-				renderer.setIcon(null);
-			}
-		}
-		if (value instanceof File) {
-			File file = (File) value;
-			renderer.setText(file.toString());
-			//renderer.setEnabled(isEnabled);
-		} else if (value instanceof ArrayList) {
-			ArrayList<?> values = (ArrayList<?>) value;
-			if (values.size() > 0) {
-				//if we have multiple properties selected.
-				File last = null;
-				boolean allSame = true;
-				for (int i = 0; i < values.size(); i++) {
-					if (values.get(i) instanceof File) {
-						File str = (File) values.get(i);
-						if (last != null) {
-							if (!str.equals(last)) {
-								allSame = false;
-								break;
-							}
-							last = str;
-						} else {
-							last = str;
-						}
-					}
-				}
-				if (allSame) {
-					renderer.setText(last.toString());
-					;
-				} else {
-					renderer.setText("(Different values)");
-					renderer.setFont(new Font("monospaced", Font.ITALIC, 12));
-				}
-			}
-
-			if (setting instanceof FileSetting) {
-				lastSetting = (FileSetting) setting;
-				if (!lastSetting.isValidFile()) {
-					renderer.setIcon(warningIcon);
-				} else {
-					renderer.setIcon(null);
-				}
-			}
-		}
-
-		lastTable = table;
-		tableRow = row;
-		tableCol = column;
-		return panel;
-	}
-
-	public void stopEditing()
-	{
-	}
-
-	public void actionPerformed(ActionEvent e)
-	{
-		dialogFocus = true;
-
-		if (lastSetting == null) {
-			System.out.println("lastSetting null");
-			return;
-		}
-
-		File newFile = lastSetting.getFileSelector().getFile(null, new File(renderer.getText()));
-		if (newFile != null) {
-			renderer.setText(newFile.toString());
-			if (dialogFocus) {
-				dialogFocus = false;
-				if (lastTable != null)
-					lastTable.editingStopped(new ChangeEvent(this));
-			} else //must have lost the focus during editing
-			{
-				if (lastTable != null) {
-					lastTable.setValueAt(getEditorValue(), tableRow, tableCol);
-				}
-			}
-			modified = true;
-		}
-		if (lastTable != null)
-			lastTable.editingStopped(new ChangeEvent(this));
-	}
-
-	public void focusGained(FocusEvent e)
-	{
-	}
-
-	public void focusLost(FocusEvent e)
-	{
-		dialogFocus = false;
-	}
-}
diff --git a/prism/src/settings/FileRenderer.java b/prism/src/settings/FileRenderer.java
deleted file mode 100644
index 85e82ac..0000000
--- a/prism/src/settings/FileRenderer.java
+++ /dev/null
@@ -1,128 +0,0 @@
-//==============================================================================
-//	
-//	Copyright (c) 2002-
-//	Authors:
-//	* Andrew Hinton <ug60axh@cs.bham.ac.uk> (University of Birmingham)
-//	
-//------------------------------------------------------------------------------
-//	
-//	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 settings;
-
-import javax.swing.*;
-import java.awt.*;
-import javax.swing.border.*;
-import java.util.*;
-import java.io.*;
-
-import userinterface.GUIPrism;
-
-public class FileRenderer implements SettingRenderer
-{
-	private JLabel renderer;
-	private JPanel panel;
-	private ImageIcon warningIcon = GUIPrism.getIconFromImage("smallError.png");
-
-	public FileRenderer()
-	{
-		panel = new JPanel(new BorderLayout());
-		renderer = new JLabel();
-		panel.add(renderer, BorderLayout.CENTER);
-	}
-
-	public Component getTableCellRendererComponent(JTable table, Setting owner, Object value, boolean isSelected, boolean hasFocus, boolean isEnabled, int row,
-			int column)
-	{
-		//renderer.setMargin(new Insets(0, 2, 4, 2));
-
-		if (isSelected) {
-			renderer.setForeground(table.getSelectionForeground());
-			panel.setBackground(table.getSelectionBackground());
-		} else {
-			renderer.setForeground(table.getForeground());
-			panel.setBackground(table.getBackground());
-		}
-
-		if (hasFocus) {
-			renderer.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
-		} else {
-			renderer.setBorder(new EmptyBorder(1, 2, 2, 1));
-		}
-
-		if (value instanceof File) {
-			File file = (File) value;
-			renderer.setText(file.toString());
-			renderer.setEnabled(isEnabled);
-			if (owner instanceof FileSetting) {
-				FileSetting fset = (FileSetting) owner;
-				if (!fset.isValidFile()) {
-					renderer.setIcon(warningIcon);
-				} else {
-					renderer.setIcon(null);
-				}
-			}
-		} else if (value instanceof ArrayList) {
-			ArrayList<?> values = (ArrayList<?>) value;
-			if (values.size() > 0) {
-				//if we have multiple properties selected.
-				File last = null;
-				boolean allSame = true;
-				for (int i = 0; i < values.size(); i++) {
-					if (values.get(i) instanceof File) {
-						File str = (File) values.get(i);
-						if (last != null) {
-							if (!str.equals(last)) {
-								allSame = false;
-								break;
-							}
-							last = str;
-						} else {
-							last = str;
-						}
-					}
-				}
-				if (allSame) {
-					File file = last;
-					renderer.setText(file.toString());
-					renderer.setEnabled(isEnabled);
-					if (owner instanceof FileSetting) {
-						FileSetting fset = (FileSetting) owner;
-						if (!fset.isValidFile()) {
-							renderer.setIcon(warningIcon);
-						} else {
-							renderer.setIcon(null);
-						}
-					}
-				} else {
-					renderer.setText("(Different values)");
-					renderer.setEnabled(isEnabled);
-					renderer.setBackground(Color.lightGray);
-					panel.setBackground(Color.lightGray);
-				}
-
-			}
-		}
-
-		//sort out the height
-
-		return panel;
-	}
-
-}
diff --git a/prism/src/settings/FileSetting.java b/prism/src/settings/FileSetting.java
index db1da84..f9456db 100644
--- a/prism/src/settings/FileSetting.java
+++ b/prism/src/settings/FileSetting.java
@@ -32,15 +32,6 @@ import javax.swing.*;
 
 public class FileSetting extends Setting 
 {
-    private static FileRenderer renderer;
-    private static FileEditor editor;
-    
-    static
-    {
-        renderer = new FileRenderer();
-        editor = new FileEditor();
-    }
-	
 	private static FileSelector defaultSelector()
 	{
 		try {
@@ -103,17 +94,17 @@ public class FileSetting extends Setting
 		else 
 			validFile = false;
 	}
-    
+
     public SettingEditor getSettingEditor()
     {
-        return editor;
+        return null;
     }
-    
+
     public SettingRenderer getSettingRenderer()
     {
-        return renderer;
+        return null;
     }
-    
+
     public Class getValueClass()
     {
         return File.class;
diff --git a/prism/src/userinterface/properties/GUIMultiProperties.java b/prism/src/userinterface/properties/GUIMultiProperties.java
index 978a6d0..c01df25 100644
--- a/prism/src/userinterface/properties/GUIMultiProperties.java
+++ b/prism/src/userinterface/properties/GUIMultiProperties.java
@@ -272,7 +272,7 @@ public class GUIMultiProperties extends GUIPlugin implements MouseListener, List
 			// Get valid/selected properties
 			String propertiesString = getLabelsString() + "\n" + getConstantsString() + "\n" + propList.getValidSelectedAndReferencedString();
 			// Get PropertiesFile for valid/selected properties
-			parsedProperties = getPrism().parsePropertiesString(parsedModel, propertiesString);
+			parsedProperties = Prism.parsePropertiesString(parsedModel, propertiesString);
 			// And get list of corresponding GUIProperty objects
 			validGUIProperties = propList.getValidSelectedProperties();
 			// Query user for undefined constant values (if required)
@@ -313,7 +313,7 @@ public class GUIMultiProperties extends GUIPlugin implements MouseListener, List
 		ArrayList<Expression> simulatableExprs;
 		UndefinedConstants uCon;
 		try {
-			parsedProperties = getPrism().parsePropertiesString(parsedModel,
+			parsedProperties = Prism.parsePropertiesString(parsedModel,
 					getLabelsString() + "\n" + getConstantsString() + "\n" + propList.getValidSelectedAndReferencedString());
 			validGUIProperties = propList.getValidSelectedProperties();
 			if (validGUIProperties.size() == 0) {
@@ -406,7 +406,7 @@ public class GUIMultiProperties extends GUIPlugin implements MouseListener, List
 			}*/
 
 			// parse property to be used for experiment
-			parsedProperties = getPrism().parsePropertiesString(parsedModel,
+			parsedProperties = Prism.parsePropertiesString(parsedModel,
 					getLabelsString() + "\n" + getConstantsString() + "\n" + propList.getValidSelectedAndReferencedString());
 			if (parsedProperties.getNumProperties() <= 0) {
 				error("There are no properties selected");
@@ -1119,7 +1119,7 @@ public class GUIMultiProperties extends GUIPlugin implements MouseListener, List
 		exportLabelsAfterReceiveParseNotification = false;
 		try {
 			// Parse labels/constants
-			parsedProperties = getPrism().parsePropertiesString(parsedModel, getLabelsString() + "\n" + getConstantsString());
+			parsedProperties = Prism.parsePropertiesString(parsedModel, getLabelsString() + "\n" + getConstantsString());
 			// Query user for undefined constant values (if required)
 			UndefinedConstants uCon = new UndefinedConstants(parsedModel, parsedProperties, true);
 			if (uCon.getMFNumUndefined() + uCon.getPFNumUndefined() > 0) {
diff --git a/prism/src/userinterface/properties/GUIPropConstantList.java b/prism/src/userinterface/properties/GUIPropConstantList.java
index 70cc1d5..542f57a 100644
--- a/prism/src/userinterface/properties/GUIPropConstantList.java
+++ b/prism/src/userinterface/properties/GUIPropConstantList.java
@@ -358,7 +358,7 @@ public class GUIPropConstantList extends JTable
 		{
 			try {
 				error = null;
-				parent.getPrism().parsePropertiesString(parent.getParsedModel(), parseableToString());
+				Prism.parsePropertiesString(parent.getParsedModel(), parseableToString());
 			}
 			catch (PrismException e) {
 				error = e;
diff --git a/prism/src/userinterface/properties/GUIPropLabelList.java b/prism/src/userinterface/properties/GUIPropLabelList.java
index 6735efe..9dccb44 100644
--- a/prism/src/userinterface/properties/GUIPropLabelList.java
+++ b/prism/src/userinterface/properties/GUIPropLabelList.java
@@ -331,7 +331,7 @@ public class GUIPropLabelList extends JTable
 		{
 			try {
 				error = null;
-				parent.getPrism().parsePropertiesString(parent.getParsedModel(), parent.getConstantsString()+"\n"+parseableToString());
+				Prism.parsePropertiesString(parent.getParsedModel(), parent.getConstantsString()+"\n"+parseableToString());
 			}
 			catch (PrismException e) {
 				error = e;
diff --git a/prism/src/userinterface/properties/GUIProperty.java b/prism/src/userinterface/properties/GUIProperty.java
index 3c53c7b..01bdb0f 100644
--- a/prism/src/userinterface/properties/GUIProperty.java
+++ b/prism/src/userinterface/properties/GUIProperty.java
@@ -37,7 +37,6 @@ import userinterface.GUIPrism;
 import param.BigRational;
 import parser.*;
 import parser.ast.*;
-import parser.type.TypeVoid;
 import prism.*;
 
 /**
@@ -369,7 +368,7 @@ public class GUIProperty
 			boolean couldBeNoConstantsOrLabels = false;
 			PropertiesFile fConLab = null;
 			try {
-				fConLab = prism.parsePropertiesString(m, constantsString + "\n" + labelString);
+				fConLab = Prism.parsePropertiesString(m, constantsString + "\n" + labelString);
 			} catch (PrismException e) {
 				couldBeNoConstantsOrLabels = true;
 			}
@@ -388,7 +387,7 @@ public class GUIProperty
 			
 			//Parse all together
 			String withConsLabs = constantsString + "\n" + labelString + "\n" + namedString + propString;
-			PropertiesFile ff = prism.parsePropertiesString(m, withConsLabs);
+			PropertiesFile ff = Prism.parsePropertiesString(m, withConsLabs);
 			
 			//Validation of number of properties
 			if (ff.getNumProperties() <= namedCount)
diff --git a/prism/src/userinterface/properties/GUIPropertyEditor.java b/prism/src/userinterface/properties/GUIPropertyEditor.java
index 6b1496d..a2660ef 100644
--- a/prism/src/userinterface/properties/GUIPropertyEditor.java
+++ b/prism/src/userinterface/properties/GUIPropertyEditor.java
@@ -808,7 +808,7 @@ public class GUIPropertyEditor extends javax.swing.JDialog implements ActionList
 		try
 		{
 			//Parse constants and labels
-			PropertiesFile fConLab = props.getPrism().parsePropertiesString(parsedModel,  props.getLabelsString()+"\n"+props.getConstantsString());
+			PropertiesFile fConLab = Prism.parsePropertiesString(parsedModel,  props.getLabelsString()+"\n"+props.getConstantsString());
 			noConstants = fConLab.getConstantList().size();
 			noLabels = fConLab.getLabelList().size();
 			
@@ -824,7 +824,7 @@ public class GUIPropertyEditor extends javax.swing.JDialog implements ActionList
 			
 			//Parse all together
 			String withConsLabs = props.getConstantsString()+"\n"+props.getLabelsString()+namedString+propertyText.getText();
-			PropertiesFile ff = props.getPrism().parsePropertiesString(parsedModel, withConsLabs);
+			PropertiesFile ff = Prism.parsePropertiesString(parsedModel, withConsLabs);
 			
 			//Validation of number of properties
 			if(ff.getNumProperties() <= namedCount) throw new PrismException("Empty property");
diff --git a/prism/src/userinterface/simulator/GUISimulator.java b/prism/src/userinterface/simulator/GUISimulator.java
index fa59531..93a4c64 100644
--- a/prism/src/userinterface/simulator/GUISimulator.java
+++ b/prism/src/userinterface/simulator/GUISimulator.java
@@ -63,12 +63,7 @@ import parser.Values;
 import parser.ast.LabelList;
 import parser.ast.ModulesFile;
 import parser.ast.PropertiesFile;
-import prism.PrismException;
-import prism.PrismLangException;
-import prism.PrismSettings;
-import prism.PrismSettingsListener;
-import prism.PrismUtils;
-import prism.UndefinedConstants;
+import prism.*;
 import simulator.PathFullInfo;
 import simulator.SimulatorEngine;
 import simulator.networking.SimulatorNetworkHandler;
@@ -386,7 +381,7 @@ public class GUISimulator extends GUIPlugin implements MouseListener, ListSelect
 			// get properties constants/labels
 			PropertiesFile pf;
 			try {
-				pf = getPrism().parsePropertiesString(parsedModel, guiProp.getConstantsString().toString() + guiProp.getLabelsString());
+				pf = Prism.parsePropertiesString(parsedModel, guiProp.getConstantsString().toString() + guiProp.getLabelsString());
 			} catch (PrismLangException e) {
 				// ignore properties if they don't parse
 				pf = null; //if any problems
@@ -696,7 +691,7 @@ public class GUISimulator extends GUIPlugin implements MouseListener, ListSelect
 			// get properties constants/labels
 			PropertiesFile pf;
 			try {
-				pf = getPrism().parsePropertiesString(parsedModel, guiProp.getConstantsString().toString() + guiProp.getLabelsString());
+				pf = Prism.parsePropertiesString(parsedModel, guiProp.getConstantsString().toString() + guiProp.getLabelsString());
 			} catch (PrismLangException e) {
 				// ignore properties if they don't parse
 				pf = null; //if any problems
