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"); + } +}