diff --git a/.idea/libraries/org_apache_commons_commons_math3_3_6_1.xml b/.idea/libraries/org_apache_commons_commons_math3_3_6_1.xml
new file mode 100644
index 0000000..c2abc6c
--- /dev/null
+++ b/.idea/libraries/org_apache_commons_commons_math3_3_6_1.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Equations.iml b/Equations.iml
index d139d41..b065992 100644
--- a/Equations.iml
+++ b/Equations.iml
@@ -60,5 +60,6 @@
+
\ No newline at end of file
diff --git a/src/io/github/ethankelly/Equations.java b/src/io/github/ethankelly/Equations.java
index a17b3fd..8e5636c 100644
--- a/src/io/github/ethankelly/Equations.java
+++ b/src/io/github/ethankelly/Equations.java
@@ -3,16 +3,18 @@
import io.github.ethankelly.graph.Graph;
import io.github.ethankelly.graph.GraphGenerator;
import io.github.ethankelly.graph.Vertex;
-import io.github.ethankelly.graph.VertexState;
import io.github.ethankelly.symbols.Greek;
import io.github.ethankelly.symbols.Maths;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.ode.FirstOrderDifferentialEquations;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings("DuplicatedCode")
-public class Equations {
+public class Equations implements FirstOrderDifferentialEquations {
public static List generateEquations(Tuple tuples) {
List equations = new ArrayList<>();
@@ -26,15 +28,15 @@ public static List generateEquations(Tuple tuples) {
Arrays.sort(sir);
Arrays.sort(sip);
Arrays.sort(sirp);
- int numVertices = tuples.getGraph().getNumVertices();
-
if (Arrays.equals(states, sir)) states = si;
else if (Arrays.equals(states, sirp)) states = sip;
- for (List tuple : tuples.getTuples()) {
+ int numVertices = tuples.getGraph().getNumVertices();
+
+ for (List tuple : tuples.getTuples()) {
StringBuilder eqn = new StringBuilder(); // The String representation of the equation of the tuples
eqn.append(tuple.toString()).append(" = ");
- for (VertexState vs : tuple) {
+ for (Vertex vs : tuple) {
switch (Character.toUpperCase(vs.getState())) {
case 'S' -> {
if (new String(states).contains("P")) {
@@ -136,4 +138,14 @@ public static void main(String[] args) {
System.out.println(subGraph);
}
}
+
+ @Override
+ public int getDimension() {
+ return 1;
+ }
+
+ @Override
+ public void computeDerivatives(double v, double[] doubles, double[] doubles1) throws MaxCountExceededException, DimensionMismatchException {
+
+ }
}
diff --git a/src/io/github/ethankelly/Tuple.java b/src/io/github/ethankelly/Tuple.java
index 3170a73..5c90618 100644
--- a/src/io/github/ethankelly/Tuple.java
+++ b/src/io/github/ethankelly/Tuple.java
@@ -1,12 +1,9 @@
package io.github.ethankelly;
import io.github.ethankelly.graph.Graph;
-import io.github.ethankelly.graph.VertexState;
+import io.github.ethankelly.graph.Vertex;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
+import java.util.*;
/**
* The {@code Tuple} class is used to determine and represent the number of differential equations required to fully
@@ -18,7 +15,7 @@
*/
@SuppressWarnings("DuplicatedCode")
public class Tuple {
- private List> tuples;
+ private List> tuples;
private final Graph graph;
private final char[] states;
private boolean closures;
@@ -36,12 +33,12 @@ public Tuple(Graph graph, char[] states, boolean closures) {
this.closures = closures;
}
- public List> getTuples() {
+ public List> getTuples() {
return this.tuples;
}
@SuppressWarnings("unused")
- public void setTuples(List> tuples) {
+ public void setTuples(List> tuples) {
this.tuples = tuples;
}
@@ -52,7 +49,7 @@ public void setTuples(List> tuples) {
* @param equations the equations to find the numbers of each length.
* @return An array with the number of equations of each size required to exactly describe the SIR system.
*/
- public int[] findNumbers(List> equations) {
+ public int[] findNumbers(List> equations) {
// The array needs as many elements as there are different sizes in the equations list,
// So initialise to (1 less than) the size of the largest (final) sub-list.
int[] sizes = new int[equations.get(equations.size() - 1).size()];
@@ -63,13 +60,31 @@ public int[] findNumbers(List> equations) {
return sizes;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Tuple tuple = (Tuple) o;
+ return closures == tuple.closures &&
+ Objects.equals(getTuples(), tuple.getTuples()) &&
+ Objects.equals(getGraph(), tuple.getGraph()) &&
+ Arrays.equals(getStates(), tuple.getStates());
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(getTuples(), getGraph(), closures);
+ result = 31 * result + Arrays.hashCode(getStates());
+ return result;
+ }
+
/**
* Using the states required in the model and the number of vertices in the graph, this method determines the full
* list of single termed equations that are involved in describing the model exactly.
*
* @return the number of single-probability differential equations required by the model.
*/
- public List findSingles() {
+ public List findSingles() {
// Get states, graph and the vertices in the associated graph for the model
char[] states = this.getStates();
Arrays.sort(states);
@@ -91,20 +106,20 @@ public List findSingles() {
Arrays.setAll(vertices, i -> i);
// We need an equation for the probability of each vertex being in each state
- List verticesWeNeed = new ArrayList<>();
+ List verticesWeNeed = new ArrayList<>();
for (char c : states) {
for (int v : vertices) {
- verticesWeNeed.add(new VertexState(c, v));
+ verticesWeNeed.add(new Vertex(c, v));
}
}
return verticesWeNeed;
}
// Recursive helper method to generate the total equations we need
- private void generateTuples(List items,
- List selected,
+ private void generateTuples(List items,
+ List selected,
int index,
- List> result) {
+ List> result) {
if (index >= items.size()) {
result.add(new ArrayList<>(selected));
} else {
@@ -121,10 +136,10 @@ private void generateTuples(List items,
*
* @return a list of each n-tuple we are required to express to exactly detail the model system dynamics.
*/
- public List> generateTuples(boolean closures) {
- List items = this.findSingles();
- List> result = new ArrayList<>();
- List selected = new ArrayList<>();
+ public List> generateTuples(boolean closures) {
+ List items = this.findSingles();
+ List> result = new ArrayList<>();
+ List selected = new ArrayList<>();
generateTuples(items, selected, 0, result);
// The helper method gives us all combinations, so remove the ones that don't
@@ -144,11 +159,11 @@ public List> generateTuples(boolean closures) {
* @param toAdd the potential tuple we are considering adding to the system dynamics.
* @return true if the tuple is essential to expressing the system dynamics, false otherwise.
*/
- private boolean isValidTuple(List toAdd, boolean closures) {
+ private boolean isValidTuple(List toAdd, boolean closures) {
Graph g = this.getGraph();
- return VertexState.areStatesDifferent(toAdd, closures)
- && VertexState.areLocationsDifferent(toAdd)
- && VertexState.areAllConnected(toAdd, g);
+ return Vertex.areStatesDifferent(toAdd, closures)
+ && Vertex.areLocationsDifferent(toAdd)
+ && Vertex.areAllConnected(toAdd, g);
}
/**
diff --git a/src/io/github/ethankelly/graph/Vertex.java b/src/io/github/ethankelly/graph/Vertex.java
index 527a0b1..088d47a 100644
--- a/src/io/github/ethankelly/graph/Vertex.java
+++ b/src/io/github/ethankelly/graph/Vertex.java
@@ -1,36 +1,75 @@
package io.github.ethankelly.graph;
+import io.github.ethankelly.symbols.Maths;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+
/**
- * The {@code Vertex} class represents a vertex of a graph.
+ * The {@code VertexState} class represents an instance of a vertex being in a particular state in a compartmental
+ * epidemiological model. Each vertex object has a state (for instance, {@code 'S'} for "Susceptible") and a numerical
+ * location in the graph we are interested in.
*/
public class Vertex implements Comparable {
private int location;
+ private char state;
public Vertex(int location) {
this.location = location;
+ this.state = ' ';
}
- public int getLocation() {
- return location;
+ public Vertex(char state, int location) {
+ this.state = state;
+ this.location = location;
}
- public void setLocation(int newLocation) {
- this.location = newLocation;
+ /**
+ * Given a list of vertices (some tuple), this method checks whether all of the states are the same. If they are the
+ * same, we do not have to consider the associated equation of the tuple in the final system of equations that
+ * describes our compartmental model.
+ *
+ * @param toCheck the tuple we wish to verify has at least two different states in its elements.
+ * @return true if at least one vertex state is different to that of the others, false if they are all the same.
+ */
+ public static boolean areStatesDifferent(List toCheck, boolean reqClosures) {
+ boolean statesDifferent = false;
+ // If there's only one vertex in the list, by definition we require it
+ // in the system of equations so we ensure this method returns true.
+ if (toCheck.size() == 1) statesDifferent = true;
+ else {
+ // We only need to find two different states to know that the states are
+ // at least not all the same, returning true.
+ for (Vertex v : toCheck) {
+ for (Vertex w : toCheck) {
+ if (v.getState() != w.getState()) {
+ statesDifferent = true;
+ break;
+ } else if (reqClosures) {
+ if ((v.getState() == 'S') || (w.getState() == 'S')) {
+ statesDifferent = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return statesDifferent;
}
- @Override
- public String toString() {
- return String.valueOf(location);
+ public char getState() {
+ return this.state;
}
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Vertex vertex = (Vertex) o;
+ public int getLocation() {
+ return location;
+ }
- return this.location == vertex.location;
+ public void setLocation(int newLocation) {
+ this.location = newLocation;
}
@Override
@@ -42,11 +81,6 @@ public Vertex clone() {
}
}
- @Override
- public int hashCode() {
- return location;
- }
-
/**
* Gives a method to compare two vertices. This comparison is done using the numerical index locations of the two
* vertices we wish to compare.
@@ -74,4 +108,82 @@ public int compareTo(Vertex that) {
return EQUAL;
}
+ /**
+ * Given some list of vertices (some tuple), this method checks whether the index locations of every element of the
+ * tuple are all distinct. If even two of the vertices have the same location, it is not a valid tuple for our
+ * purposes and we will not hve to consider the associated equation in the final system of equations that describes
+ * our compartmental model.
+ *
+ * @param vertices the tuple we wish to verify has no repeated vertex locations.
+ * @return true if no index location is repeated in the vertices, false otherwise.
+ */
+ public static boolean areLocationsDifferent(List vertices) {
+ boolean locationsDifferent = true;
+ // Only need to find two vertices in the list with same index location
+ // to know we don't have a required tuple, returning false.
+ for (Vertex v : vertices) {
+ if (vertices.stream().anyMatch(w -> v.getLocation() == w.getLocation() && v != w)) {
+ locationsDifferent = false;
+ break;
+ }
+ }
+ return locationsDifferent;
+ }
+
+ /**
+ * Given some list of vertices (tuple), this method verifies that all of the vertices in the graph given are in fact
+ * connected. That is, we are only interested in a tuple of vertices that constitute some manner of path (cycle or
+ * such like) - if not, then we do not have to consider the associated equation in the final system of equations
+ * that describes our compartmental model and we can discount the given tuple.
+ *
+ * @param toCheck the tuple we wish to check constitutes some kind of path.
+ * @param g the graph in which the tuple exists.
+ * @return true if the tuple forms a path in some way, false otherwise.
+ */
+ public static boolean areAllConnected(List toCheck, Graph g) {
+ // If there's only one vertex in the list, required in the system of equations - ensure this returns true.
+ // If more than one vertex, return whether they all have some path between them.
+ return toCheck.size() == 1 || IntStream.range(0, toCheck.size() - 1).allMatch(
+ i -> g.hasEdge(new Vertex(toCheck.get(i).getLocation()),
+ new Vertex(toCheck.get(i + 1).getLocation()))
+ );
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+ Vertex that = (Vertex) o;
+ return getState() == that.getState() && getLocation() == that.getLocation();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), getState(), getLocation());
+ }
+
+// @Override
+// public int compareTo(VertexState that) {
+// int comparison = 0;
+// int stateComparison = String.valueOf(this.getState()).compareTo(String.valueOf(that.getState()));
+// int locationComparison = Integer.compare(this.getLocation(), that.getLocation());
+//
+// if (stateComparison == locationComparison) comparison = stateComparison;
+// else if (stateComparison == 0) comparison = locationComparison;
+// else if (locationComparison == 0) comparison = stateComparison;
+//
+// return comparison;
+// }
+
+
+ /**
+ * Represents a given vertex textually, for printing to standard output.
+ *
+ * @return a String representation of the vertex.
+ */
+ @Override
+ public String toString() {
+ return Maths.LANGLE.uni() + this.getState() + this.getLocation() + Maths.RANGLE.uni();
+ }
}
diff --git a/src/io/github/ethankelly/graph/VertexState.java b/src/io/github/ethankelly/graph/VertexState.java
deleted file mode 100644
index b5c81ca..0000000
--- a/src/io/github/ethankelly/graph/VertexState.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package io.github.ethankelly.graph;
-
-import io.github.ethankelly.symbols.Maths;
-
-import java.util.List;
-import java.util.stream.IntStream;
-
-/**
- * The {@code VertexState} class represents an instance of a vertex being in a particular state in a compartmental
- * epidemiological model. Each vertex object has a state (for instance, {@code 'S'} for "Susceptible") and a numerical
- * location in the graph we are interested in.
- */
-public class VertexState extends Vertex {
- private final char state;
- private final int location;
-
- /**
- * Class constructor, assigning a state and location to a vertex instance object.
- *
- * @param state the state for which we are interested in studying the probability of the vertex being in.
- * @param location the numerical index that represents the location of the vertex in the associated graph.
- */
- public VertexState(char state, int location) {
- super(location);
- this.state = state;
- this.location = location;
- }
-
- /**
- * Given a list of vertices (some tuple), this method checks whether all of the states are the same. If they are the
- * same, we do not have to consider the associated equation of the tuple in the final system of equations that
- * describes our compartmental model.
- *
- * @param toCheck the tuple we wish to verify has at least two different states in its elements.
- * @return true if at least one vertex state is different to that of the others, false if they are all the same.
- */
- public static boolean areStatesDifferent(List toCheck, boolean reqClosures) {
- boolean statesDifferent = false;
- // If there's only one vertex in the list, by definition we require it
- // in the system of equations so we ensure this method returns true.
- if (toCheck.size() == 1) statesDifferent = true;
- else {
- // We only need to find two different states to know that the states are
- // at least not all the same, returning true.
- for (VertexState v : toCheck) {
- for (VertexState w : toCheck) {
- if (v.getState() != w.getState()) {
- statesDifferent = true;
- break;
- } else if (reqClosures) {
- if ((v.getState() == 'S') || (w.getState() == 'S')) {
- statesDifferent = true;
- break;
- }
- }
- }
- }
- }
- return statesDifferent;
- }
-
- /**
- * Given some list of vertices (some tuple), this method checks whether the index locations of every element of the
- * tuple are all distinct. If even two of the vertices have the same location, it is not a valid tuple for our
- * purposes and we will not hve to consider the associated equation in the final system of equations that describes
- * our compartmental model.
- *
- * @param vertices the tuple we wish to verify has no repeated vertex locations.
- * @return true if no index location is repeated in the vertices, false otherwise.
- */
- public static boolean areLocationsDifferent(List vertices) {
- boolean locationsDifferent = true;
- // Only need to find two vertices in the list with same index location
- // to know we don't have a required tuple, returning false.
- for (VertexState v : vertices) {
- if (vertices.stream().anyMatch(w -> v.getLocation() == w.getLocation() && v != w)) {
- locationsDifferent = false;
- break;
- }
- }
- return locationsDifferent;
- }
-
- /**
- * Given some list of vertices (tuple), this method verifies that all of the vertices in the graph given are in fact
- * connected. That is, we are only interested in a tuple of vertices that constitute some manner of path (cycle or
- * such like) - if not, then we do not have to consider the associated equation in the final system of equations
- * that describes our compartmental model and we can discount the given tuple.
- *
- * @param toCheck the tuple we wish to check constitutes some kind of path.
- * @param g the graph in which the tuple exists.
- * @return true if the tuple forms a path in some way, false otherwise.
- */
- public static boolean areAllConnected(List toCheck, Graph g) {
- // If there's only one vertex in the list, required in the system of equations - ensure this returns true.
- // If more than one vertex, return whether they all have some path between them.
- return toCheck.size() == 1 || IntStream.range(0, toCheck.size() - 1).allMatch(
- i -> g.hasEdge(new Vertex(toCheck.get(i).getLocation()),
- new Vertex(toCheck.get(i + 1).getLocation()))
- );
- }
-
- /**
- * @return the state of the vertex.
- */
- public char getState() {
- return this.state;
- }
-
- /**
- * @return the index location of the vertex.
- */
- public int getLocation() {
- return this.location;
- }
-
- /**
- * Represents a given vertex textually, for printing to standard output.
- *
- * @return a String representation of the vertex.
- */
- @Override
- public String toString() {
- return Maths.LANGLE.uni() + this.getState() + this.getLocation() + Maths.RANGLE.uni();
- }
-}
diff --git a/test/io/github/ethankelly/TupleTest.java b/test/io/github/ethankelly/TupleTest.java
new file mode 100644
index 0000000..11c2ee2
--- /dev/null
+++ b/test/io/github/ethankelly/TupleTest.java
@@ -0,0 +1,104 @@
+package io.github.ethankelly;
+
+import io.github.ethankelly.graph.GraphGenerator;
+import io.github.ethankelly.graph.Vertex;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TupleTest {
+ public static char[] si = new char[]{'S', 'I'};
+ public static char[] sir = new char[]{'S', 'I', 'R'};
+ public static char[] sip = new char[]{'S', 'I', 'P'};
+ public static char[] sirp = new char[]{'S', 'I', 'R', 'P'};
+
+ public static Tuple triangleTuplesClosuresSIR = new Tuple(GraphGenerator.getTriangle(), sir, true);
+ public static Tuple triangleTuplesNoClosuresSIR = new Tuple(GraphGenerator.getTriangle(), sir, false);
+ public static Tuple triangleTuplesClosuresSIRP = new Tuple(GraphGenerator.getTriangle(), sirp, true);
+ public static Tuple triangleTuplesNoClosuresSIRP = new Tuple(GraphGenerator.getTriangle(), sirp, false);
+
+ public static List expectedTriangleSIRPSingles = new ArrayList<>();
+ public static List> expectedTriangleSIRPTuples = new ArrayList<>();
+ public static List expectedTriangleSIRSingles = new ArrayList<>();
+ public static List> expectedTriangleSIRTuples = new ArrayList<>();
+
+ @BeforeAll
+ static void setUp() {
+ // Set up expected singles and tuples for SIR triangle
+ for (int i = 0; i < 3; i++) {
+ expectedTriangleSIRSingles.add(new Vertex('S', i));
+ expectedTriangleSIRSingles.add(new Vertex('I', i));
+
+ expectedTriangleSIRTuples.add(Collections.singletonList(new Vertex('S', i)));
+ expectedTriangleSIRTuples.add(Collections.singletonList(new Vertex('I', i)));
+ List newVerticesSI = new ArrayList<>();
+ List newVerticesIS = new ArrayList<>();
+ for (int j = 0; j < 3; j++) {
+ newVerticesSI.clear();
+ newVerticesIS.clear();
+ if (i != j) {
+ newVerticesSI.add(new Vertex('S', i));
+ newVerticesSI.add(new Vertex('I', j));
+ newVerticesIS.add(new Vertex('I', i));
+ newVerticesIS.add(new Vertex('S', j));
+ expectedTriangleSIRTuples.add(newVerticesSI);
+ expectedTriangleSIRTuples.add(newVerticesIS);
+ }
+ }
+ }
+ System.out.println(expectedTriangleSIRTuples);
+ System.out.println(expectedTriangleSIRTuples.size());
+ expectedTriangleSIRSingles.sort(null);
+
+ // Set up expected singles for SIRP triangle
+ for (int i = 0; i < 3; i++) {
+ expectedTriangleSIRPSingles.add(new Vertex('S', i));
+ expectedTriangleSIRPSingles.add(new Vertex('I', i));
+ expectedTriangleSIRPSingles.add(new Vertex('P', i));
+ }
+ expectedTriangleSIRPSingles.sort(null);
+ }
+
+ @Test
+ void testTriangleGenerateSingles() {
+ // Check expected and actual singles for SIR
+ // With closures
+ List actualClosedSIR = triangleTuplesClosuresSIR.findSingles();
+ actualClosedSIR.sort(null);
+ Assertions.assertEquals(expectedTriangleSIRSingles, actualClosedSIR,
+ "SIR Triangle singles with closures not as expected");
+
+ // Without closures (shouldn't matter for singles)
+ List actualNotClosedSIR = triangleTuplesNoClosuresSIR.findSingles();
+ actualNotClosedSIR.sort(null);
+ Assertions.assertEquals(expectedTriangleSIRSingles, actualNotClosedSIR,
+ "SIR Triangle singles without closures not as expected");
+
+ // Same again, for SIRP
+ // With closures
+ List actualClosedSIRP = triangleTuplesClosuresSIRP.findSingles();
+ actualClosedSIRP.sort(null);
+ Assertions.assertEquals(expectedTriangleSIRPSingles, actualClosedSIRP,
+ "SIRP triangle singles with closures not as expected");
+
+ // Without closures (shouldn't matter for singles)
+ List actualNotClosedSIRP = triangleTuplesNoClosuresSIRP.findSingles();
+ actualNotClosedSIRP.sort(null);
+ Assertions.assertEquals(expectedTriangleSIRPSingles, actualNotClosedSIRP,
+ "SIRP triangle singles without closures not as expected");
+ }
+
+ @Test
+ void testTriangleTuples() {
+ Assertions.assertTrue(expectedTriangleSIRTuples.containsAll(triangleTuplesNoClosuresSIR.getTuples()) &&
+ triangleTuplesNoClosuresSIR.getTuples().containsAll(expectedTriangleSIRTuples),
+ "Triangle tuples without closures not as expected");
+ Assertions.assertTrue(expectedTriangleSIRTuples.containsAll(triangleTuplesClosuresSIR.getTuples()) &&
+ triangleTuplesClosuresSIR.getTuples().containsAll(expectedTriangleSIRTuples),
+ "Triangle tuples with closures not as expected");
+ }
+}