diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/artifacts/TCellSim_jar.xml b/.idea/artifacts/TCellSim_jar.xml new file mode 100644 index 0000000..68721a0 --- /dev/null +++ b/.idea/artifacts/TCellSim_jar.xml @@ -0,0 +1,15 @@ + + + $PROJECT_DIR$/out/artifacts/TCellSim_jar + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/artifacts/TCellSim_jar2.xml b/.idea/artifacts/TCellSim_jar2.xml new file mode 100644 index 0000000..0c29e4c --- /dev/null +++ b/.idea/artifacts/TCellSim_jar2.xml @@ -0,0 +1,15 @@ + + + $PROJECT_DIR$/out/artifacts/TCellSim_jar2 + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..c3728c7 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/apache_commons_csv.xml b/.idea/libraries/apache_commons_csv.xml new file mode 100644 index 0000000..91cd631 --- /dev/null +++ b/.idea/libraries/apache_commons_csv.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/jgrapht_core.xml b/.idea/libraries/jgrapht_core.xml new file mode 100644 index 0000000..96adbc8 --- /dev/null +++ b/.idea/libraries/jgrapht_core.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..82dbec8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..797acea --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b4eb9b7 --- /dev/null +++ b/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + org.example + TCellSim + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 14 + 14 + + + + + + + org.jetbrains + annotations + RELEASE + compile + + + + + 11 + 11 + + + \ No newline at end of file diff --git a/src/main/java/CellFileReader.java b/src/main/java/CellFileReader.java new file mode 100644 index 0000000..5a3697a --- /dev/null +++ b/src/main/java/CellFileReader.java @@ -0,0 +1,50 @@ +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class CellFileReader { + + private List distinctCells = new ArrayList<>(); + + public CellFileReader(String filename) { + + if(!filename.matches(".*\\.csv")){ + filename = filename + ".csv"; + } + + CSVFormat cellFileFormat = CSVFormat.Builder.create() + .setHeader("Alpha", "Beta") + .setSkipHeaderRecord(true) + .build(); + + try(//don't need to close reader bc of try-with-resources auto-closing + BufferedReader reader = Files.newBufferedReader(Path.of(filename)); + CSVParser parser = new CSVParser(reader, cellFileFormat); + ){ + for(CSVRecord record: parser.getRecords()) { + Integer[] cell = new Integer[2]; + cell[0] = Integer.valueOf(record.get("Alpha")); + cell[1] = Integer.valueOf(record.get("Beta")); + distinctCells.add(cell); + } + } catch(IOException ex){ + System.out.println("cell file " + filename + " not found."); + System.err.println(ex); + } + } + + public List getCells(){ + return distinctCells; + } + + public Integer getCellCount() { + return distinctCells.size(); + } +} diff --git a/src/main/java/CellFileWriter.java b/src/main/java/CellFileWriter.java new file mode 100644 index 0000000..dddc2d4 --- /dev/null +++ b/src/main/java/CellFileWriter.java @@ -0,0 +1,38 @@ +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; + +public class CellFileWriter { + + private String[] headers = {"Alpha", "Beta"}; + List cells; + String filename; + + public CellFileWriter(String filename, CellSample cells) { + if(!filename.matches(".*\\.csv")){ + filename = filename + ".csv"; + } + this.filename = filename; + this.cells = cells.getCells(); + } + + public void writeCellsToFile() { + CSVFormat cellFileFormat = CSVFormat.Builder.create() + .setHeader(headers) + .build(); + try(BufferedWriter writer = Files.newBufferedWriter(Path.of(filename), StandardOpenOption.CREATE_NEW); + CSVPrinter printer = new CSVPrinter(writer, cellFileFormat); + ){ + printer.printRecords(cells); + } catch(IOException ex){ + System.out.println("Could not make new file named "+filename); + System.err.println(ex); + } + } +} diff --git a/src/main/java/CellSample.java b/src/main/java/CellSample.java new file mode 100644 index 0000000..411936d --- /dev/null +++ b/src/main/java/CellSample.java @@ -0,0 +1,19 @@ +import java.util.List; + +public class CellSample { + + private List cells; + + public CellSample(List cells){ + this.cells = cells; + } + + public List getCells(){ + return cells; + } + + public Integer population(){ + return cells.size(); + } + +} diff --git a/src/main/java/MatchingFileWriter.java b/src/main/java/MatchingFileWriter.java new file mode 100644 index 0000000..d1de122 --- /dev/null +++ b/src/main/java/MatchingFileWriter.java @@ -0,0 +1,52 @@ +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; + + +public class MatchingFileWriter { + + private String filename; + private List comments; + private List headers; + private List> results; + + public MatchingFileWriter(String filename, List comments, List headers, List> results){ + if(!filename.matches(".*\\.csv")){ + filename = filename + ".csv"; + } + this.filename = filename; + this.comments = comments; + this.headers = headers; + this.results = results; + } + + public void writeResultsToFile(){ + String[] headerStrings = new String[headers.size()]; + for(int i = 0; i < headers.size(); i++){ + headerStrings[i] = headers.get(i); + } + CSVFormat resultsFileFormat = CSVFormat.Builder.create() + .setCommentMarker('#') + //.setHeader(headerStrings) + .build(); + try(BufferedWriter writer = Files.newBufferedWriter(Path.of(filename), StandardOpenOption.CREATE_NEW); + CSVPrinter printer = new CSVPrinter(writer, resultsFileFormat); + ){ + for(String comment: comments){ + printer.printComment(comment); + } + results.add(0, headers); + printer.printRecords(results); + + } catch(IOException ex){ + System.out.println("Could not make new file named "+filename); + System.err.println(ex); + } + } +} diff --git a/src/main/java/Plate.java b/src/main/java/Plate.java index c3f7a91..27877c7 100644 --- a/src/main/java/Plate.java +++ b/src/main/java/Plate.java @@ -8,14 +8,23 @@ public class Plate { private Random rand = new Random(); private int size; private double error; + private Integer[] concentrations; + private double stdDev; - public Plate (int size, double error) { + public Plate (int size, double error, Integer[] concentrations, double stdDev) { this.size = size; this.error = error; + this.concentrations = concentrations; + this.stdDev = stdDev; wells = new ArrayList<>(); } - public void fillWells(List cells, int[] concentrations, double stdDev) { + public Plate(List> wells){ + this.wells = wells; + this.size = wells.size(); + } + + public void fillWells(List cells) { int numSections = concentrations.length; int section = 0; double m; @@ -26,8 +35,8 @@ public class Plate { List well = new ArrayList<>(); for (int j = 0; j < concentrations[section]; j++) { do { - m = Math.abs(rand.nextGaussian()) * stdDev; - } while (m >= cells.size()); + m = (rand.nextGaussian() * stdDev) + (cells.size() / 2); + } while (m >= cells.size() || m < 0); n = (int) Math.floor(m); Integer[] cellToAdd = cells.get(n).clone(); drop = Math.abs(rand.nextDouble()) < error; @@ -46,6 +55,31 @@ public class Plate { } } + public void writePlateToFile(String filename) { + + + } + + public Integer[] getConcentrations(){ + return concentrations; + } + + public int getSize(){ + return size; + } + + public double getStdDev() { + return stdDev; + } + + public double getError() { + return error; + } + + public List> getWells() { + return wells; + } + public Map assayWellsAlpha() { return this.assayWellsAlpha(0, size); } diff --git a/src/main/java/PlateFileReader.java b/src/main/java/PlateFileReader.java new file mode 100644 index 0000000..cbafcf2 --- /dev/null +++ b/src/main/java/PlateFileReader.java @@ -0,0 +1,61 @@ +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Array; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class PlateFileReader { + + private List> wells = new ArrayList<>(); + + public PlateFileReader(String filename){ + + if(!filename.matches(".*\\.csv")){ + filename = filename + ".csv"; + } + + CSVFormat plateFileFormat = CSVFormat.Builder.create() + .setCommentMarker('#') + .build(); + + try(//don't need to close reader bc of try-with-resources auto-closing + BufferedReader reader = Files.newBufferedReader(Path.of(filename)); + CSVParser parser = new CSVParser(reader, plateFileFormat); + ){ + for(CSVRecord record: parser.getRecords()) { + List well = new ArrayList<>(); + for(String s: record) { + if(!"".equals(s)) { + String[] intString = s.replaceAll("\\[", "") + .replaceAll("]", "") + .replaceAll(" ", "") + .split(","); + //System.out.println(intString); + Integer[] arr = new Integer[intString.length]; + for (int i = 0; i < intString.length; i++) { + arr[i] = Integer.valueOf(intString[i]); + } + well.add(arr); + } + } + wells.add(well); + } + } catch(IOException ex){ + System.out.println("plate file " + filename + " not found."); + System.err.println(ex); + } + + } + + public List> getWells() { + return wells; + } + +} \ No newline at end of file diff --git a/src/main/java/PlateFileWriter.java b/src/main/java/PlateFileWriter.java new file mode 100644 index 0000000..ad68fd4 --- /dev/null +++ b/src/main/java/PlateFileWriter.java @@ -0,0 +1,93 @@ +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.*; +import java.util.regex.Pattern; + +public class PlateFileWriter { + private int size; + private List> wells; + private double stdDev; + private Double error; + private String filename; + private String[] headers; + private List concentrations; + + public PlateFileWriter(String filename, Plate plate) { + if(!filename.matches(".*\\.csv")){ + filename = filename + ".csv"; + } + this.filename = filename; + this.size = plate.getSize(); + this.stdDev = plate.getStdDev(); + this.error = plate.getError(); + this.wells = plate.getWells(); + this.concentrations = Arrays.asList(plate.getConcentrations()); + concentrations.sort(Comparator.reverseOrder()); + } + + public void writePlateFile(){ + //works as is, but too many columns in csv, need to make them all rows. + + //will now redo it so that every column is a well, with well names as headers + //need to give plate error, sample pop size, stdDev, num sections, concentration per section as comments + Comparator> listLengthDescending = Comparator.comparingInt(List::size); + wells.sort(listLengthDescending.reversed()); + int maxLength = wells.get(0).size(); + List> wellsAsStrings = new ArrayList<>(); + for (List w: wells){ + List tmp = new ArrayList<>(); + for(Integer[] c: w) { + tmp.add(Arrays.toString(c)); + } + wellsAsStrings.add(tmp); + } + for(List w: wellsAsStrings){ + while(w.size() < maxLength){ + w.add(""); + } + } + + //this took forever + List> rows = new ArrayList<>(); + List tmp = new ArrayList<>(); + for(int i = 0; i < wellsAsStrings.size(); i++){//List w: wells){ + tmp.add("well " + (i+1)); + } + rows.add(tmp); + for(int row = 0; row < maxLength; row++){ + tmp = new ArrayList<>(); + for(List c: wellsAsStrings){ + tmp.add(c.get(row)); + } + rows.add(tmp); + } + StringBuilder concen = new StringBuilder(); + for(Integer i: concentrations){ + concen.append(i.toString()); + concen.append(" "); + } + String concenString = concen.toString(); + + CSVFormat plateFileFormat = CSVFormat.Builder.create().setCommentMarker('#').build(); + + try(BufferedWriter writer = Files.newBufferedWriter(Path.of(filename), StandardOpenOption.CREATE_NEW); + CSVPrinter printer = new CSVPrinter(writer, plateFileFormat); + ){ + printer.printComment("Each row represents one well on the plate."); + printer.printComment("Plate size: " + size); + printer.printComment("Error rate: " + error); + printer.printComment("Concentrations: " + concenString); + printer.printComment("Std. dev.: " + stdDev); + printer.printRecords(wellsAsStrings); + } catch(IOException ex){ + System.out.println("Could not make new file named "+filename); + System.err.println(ex); + } + } +} diff --git a/src/main/java/Simulation.java b/src/main/java/Simulator.java similarity index 62% rename from src/main/java/Simulation.java rename to src/main/java/Simulator.java index 6aeff78..bb2c5c8 100644 --- a/src/main/java/Simulation.java +++ b/src/main/java/Simulator.java @@ -13,21 +13,196 @@ import java.time.Instant; import java.util.*; import java.util.stream.IntStream; -public class Simulation { - private static Integer numDistinctCells = 15_000_000; - private static double stdDeviation = 1000; //square root of numDistCells would approximate poisson dist, supposedly +public class Simulator { + private static Integer numDistinctCells = 2_000_000; + private static double stdDeviation = 200; //square root of numDistCells would approximate poisson dist private static int numWells = 96; private static int numConcentrations = 1; private static double errorRate = 0.1; - private static int[] concentrations = {500}; + private static Integer[] concentrations = {500}; private static int lowThreshold = 2; //min number of shared wells to attempt pairing private static int highThreshold = numWells - 3; //max number of shared wells to attempt pairing private static boolean use2DArrayForGraph = true; //Doing this is much faster for larger graphs private static boolean useJGraphTGraphMatrixGenerator = true; //fastest option - public static void main(String[] args) { - Instant start = Instant.now(); + public static CellSample generateCellSample(Integer numDistinctCells) { + List numbers = new ArrayList<>(); + IntStream.range(1, (2 * numDistinctCells) + 1).forEach(i -> numbers.add(i)); + Collections.shuffle(numbers); + + //Each cell represented by two numbers from the random permutation + //These represent unique alpha and beta peptides + List distinctCells = new ArrayList<>(); + for(int i = 0; i < numbers.size() - 1; i = i + 2) { + Integer tmp1 = numbers.get(i); + Integer tmp2 = numbers.get(i+1); + Integer[] tmp = {tmp1, tmp2}; + distinctCells.add(tmp); + } + return new CellSample(distinctCells); + } + + public static void matchCells(String filename, List distinctCells, Plate samplePlate, Integer lowThreshold, Integer highThreshold){ + System.out.println("Cells: " + distinctCells.size()); + + System.out.println("Making cell maps"); + //HashMap keyed to Alphas, values Betas + Map distCellsMapAlphaKey = new HashMap<>(); + for (Integer[] cell : distinctCells) { + distCellsMapAlphaKey.put(cell[0], cell[1]); + } + //HashMap keyed to Betas, values Alphas + Map distCellsMapBetaKey = new HashMap<>(); + for (Integer[] cell : distinctCells) { + distCellsMapBetaKey.put(cell[1], cell[0]); + } + System.out.println("Cell maps made"); + + System.out.println("Making well maps"); + Map allAlphas = samplePlate.assayWellsAlpha(); + Map allBetas = samplePlate.assayWellsBeta(); + int alphaCount = allAlphas.size(); + System.out.println("all alphas count: " + alphaCount); + int betaCount = allBetas.size(); + System.out.println("all betas count: " + betaCount); + + System.out.println("Well maps made"); + + System.out.println("Making vertex maps"); + //Using Integers instead of Strings to label vertices so I can do clever stuff with indices if I need to + // when I refactor to use a 2d array to make the graph + //For the autogenerator, all vertices must have distinct numbers associated with them + Integer vertexStartValue = 0; + //keys are sequential integer vertices, values are alphas + Map plateVtoAMap = getVertexToPeptideMap(allAlphas, vertexStartValue); + //New start value for vertex to beta map should be one more than final vertex value in alpha map + vertexStartValue += plateVtoAMap.size(); + //keys are sequential integers vertices, values are betas + Map plateVtoBMap = getVertexToPeptideMap(allBetas, vertexStartValue); + //keys are alphas, values are sequential integer vertices from previous map + Map plateAtoVMap = invertVertexMap(plateVtoAMap); + System.out.println(plateAtoVMap.size()); + //keys are betas, values are sequential integer vertices from previous map + Map plateBtoVMap = invertVertexMap(plateVtoBMap); + System.out.println(plateAtoVMap.size()); + System.out.println("Vertex maps made"); + + System.out.println("Creating Graph"); + //Count how many wells each alpha appears in + Map alphaWellCounts = new HashMap<>(); + //count how many wells each beta appears in + Map betaWellCounts = new HashMap<>(); + //add edges, where weights are number of wells the peptides share in common. + //If this is too slow, can make a 2d array and use the SimpleWeightedGraphMatrixGenerator class + Map wellNAlphas = null; + Map wellNBetas = null; + SimpleWeightedGraph graph = + new SimpleWeightedGraph<>(DefaultWeightedEdge.class); + double[][] weights = new double[plateVtoAMap.size()][plateVtoBMap.size()]; + for (int n = 0; n < numWells; n++) { + wellNAlphas = samplePlate.assayWellsAlpha(n); + for (Integer a : wellNAlphas.keySet()) { + alphaWellCounts.merge(a, 1, (oldValue, newValue) -> oldValue + newValue); + } + wellNBetas = samplePlate.assayWellsBeta(n); + for (Integer b : wellNBetas.keySet()) { + betaWellCounts.merge(b, 1, (oldValue, newValue) -> oldValue + newValue); + } + for (Integer i : wellNAlphas.keySet()) { + for (Integer j : wellNBetas.keySet()) { + weights[plateAtoVMap.get(i)][plateBtoVMap.get(j) - vertexStartValue] += 1.0; + } + } + } + SimpleWeightedBipartiteGraphMatrixGenerator graphGenerator = new SimpleWeightedBipartiteGraphMatrixGenerator(); + List alphaVertices = new ArrayList<>(); + alphaVertices.addAll(plateVtoAMap.keySet()); //This will work because LinkedHashMap preserves order of entry + graphGenerator.first(alphaVertices); + List betaVertices = new ArrayList<>(); + betaVertices.addAll(plateVtoBMap.keySet()); + graphGenerator.second(betaVertices); //This will work because LinkedHashMap preserves order of entry + graphGenerator.weights(weights); + graphGenerator.generateGraph(graph); + System.out.println("Graph created"); + + System.out.println("Finding maximum weighted matching"); + MaximumWeightBipartiteMatching maxWeightMatching = + new MaximumWeightBipartiteMatching(graph, plateVtoAMap.keySet(), plateVtoBMap.keySet()); + MatchingAlgorithm.Matching graphMatching = maxWeightMatching.getMatching(); + System.out.println("Matching completed"); + + //Header for CSV file + List header = new ArrayList<>(); + header.add("Alpha"); + header.add("Alpha well count"); + header.add("Beta"); + header.add("Beta well count"); + header.add("Overlap well count"); + header.add("Matched correctly?"); + header.add("P-value"); + + + //Results for csv file + List> allResults = new ArrayList<>(); + int size = samplePlate.getSize(); + Iterator weightIter = graphMatching.iterator(); + DefaultWeightedEdge e = null; + int trueCount = 0; + int falseCount = 0; + boolean check = false; + while(weightIter.hasNext()) { + e = weightIter.next(); + if(graph.getEdgeWeight(e) < lowThreshold || graph.getEdgeWeight(e) > highThreshold) { + continue; + } + Integer source = graph.getEdgeSource(e); + Integer target = graph.getEdgeTarget(e); + check = plateVtoBMap.get(target).equals(distCellsMapAlphaKey.get(plateVtoAMap.get(source))); + if(check) { + trueCount++; + } + else { + falseCount++; + } + List result = new ArrayList<>(); + result.add(plateVtoAMap.get(source).toString()); + //alpha well count + result.add(alphaWellCounts.get(plateVtoAMap.get(source)).toString()); + result.add(plateVtoBMap.get(target).toString()); + //beta well count + result.add(betaWellCounts.get(plateVtoBMap.get(target)).toString()); + //overlap count + result.add(Double.toString(graph.getEdgeWeight(e))); + result.add(Boolean.toString(check)); + result.add(Double.toString(Equations.pValue(size, alphaWellCounts.get(plateVtoAMap.get(source)), + betaWellCounts.get(plateVtoBMap.get(target)), graph.getEdgeWeight(e)))); + allResults.add(result); + } + + //Metadate comments for CSV file + int min = alphaCount > betaCount ? betaCount : alphaCount; + double attemptRate = (double) (trueCount + falseCount) / min; + double pairingErrorRate = (double) falseCount / (trueCount + falseCount); + + List comments = new ArrayList<>(); + comments.add("Total alphas found: " + alphaCount); + comments.add("Total betas found: " + betaCount); + comments.add("Pairing attempt rate: " + attemptRate); + comments.add("Correct pairings: " + trueCount); + comments.add("Incorrect pairings: " + falseCount); + comments.add("Pairing error rate: " + pairingErrorRate); + + //result writer + MatchingFileWriter writer = new MatchingFileWriter(filename, comments, header, allResults); + writer.writeResultsToFile(); + } + + + + + public static void Simulate() { + Instant start = Instant.now(); //Four things to try to improve this //1. Run it on hardware with more memory //2. implement p-values and just check exhaustively for strictly-bounded weights @@ -76,8 +251,8 @@ public class Simulation { distCellsMapBetaKey.put(cell[1], cell[0]); } - Plate samplePlate = new Plate(numWells, errorRate); - samplePlate.fillWells(distinctCells, concentrations, stdDeviation); + Plate samplePlate = new Plate(numWells, errorRate, concentrations, stdDeviation); + samplePlate.fillWells(distinctCells); //OUTPUT System.out.println("Wells filled"); diff --git a/src/main/java/UserInterface.java b/src/main/java/UserInterface.java new file mode 100644 index 0000000..7feebc8 --- /dev/null +++ b/src/main/java/UserInterface.java @@ -0,0 +1,192 @@ +import java.util.List; +import java.util.Scanner; +import java.util.ArrayList; +import java.util.InputMismatchException; + +// +public class UserInterface { + + final static Scanner sc = new Scanner(System.in); + static int input; + static boolean quit = false; + + public static void main(String args[]) { + while(!quit) { + System.out.println("\nALPHA/BETA T-CELL RECEPTOR MATCHING SIMULATOR"); + System.out.println("Please select and option:"); + System.out.println("1) Generate a population of distinct cells"); + System.out.println("2) Generate a sample plate of T-cells"); + System.out.println("3) Simulate T-Cell matching"); + System.out.println("4) Acknowledgements"); + System.out.println("0) Exit"); + try { + input = sc.nextInt(); + switch(input){ + case 1 -> makeCells(); + case 2 -> makePlate(); + case 3 -> matchCells(); + //case 4 -> //method call goes here + case 0 -> quit = true; + default -> throw new InputMismatchException("Invalid input."); + } + }catch(InputMismatchException ex){ + System.out.println(ex); + sc.next(); + } + } + sc.close(); + } + + private static void makeCells() { + String filename = null; + Integer numCells = 0; + try { + System.out.println("\nSimulated T-Cells consist of matched pairs of alpha and beta peptides, " + + "represented by unique integer values."); + System.out.println("(Note: peptide values are unique within a simulated population, " + + "but repeated between simulated populations.)"); + System.out.println("\nThe cells will be written to a file."); + System.out.print("Please enter a file name: "); + filename = sc.next(); + System.out.print("Please enter the number of T-cells to generate: "); + numCells = sc.nextInt(); + if(numCells <= 0){ + throw new InputMismatchException("Number of cells must be a positive integer."); + } + } catch (InputMismatchException ex) { + System.out.println(ex); + sc.next(); + } + CellSample sample = Simulator.generateCellSample(numCells); + CellFileWriter writer = new CellFileWriter(filename, sample); + writer.writeCellsToFile(); + } + + //method to output a CSV of + private static void makePlate() { + String cellFile = null; + String filename = null; + Double stdDev = 0.0; + Integer numWells = 0; + Integer numSections = 0; + Integer[] concentrations = {1}; + Double dropOutRate = 0.0; + boolean poisson = false; + try { + System.out.println("\nMaking a sample plate requires a population of distinct cells"); + System.out.println("Please enter name of an existing cell sample file: "); + cellFile = sc.next(); + System.out.println("\nThe sample plate will be written to file"); + System.out.print("Please enter a name for the output file: "); + filename = sc.next(); + System.out.println("Select T-cell frequency distribution function"); + System.out.println("1) Poisson"); + System.out.println("2) Gaussian"); + System.out.println("(Note: wider distributions are more memory intensive to match)"); + System.out.print("Enter selection value: "); + input = sc.nextInt(); + switch(input) { + case 1: + poisson = true; + break; + case 2: + System.out.println("How many distinct T-cells within one standard deviation of peak frequency?"); + System.out.println("(Note: wider distributions are more memory intensive to match)"); + stdDev = sc.nextDouble(); + if(stdDev <= 0.0){ + throw new InputMismatchException("Value must be positive."); + } + break; + default: + System.out.println("Invalid input. Defaulting to Poisson."); + poisson = true; + } + System.out.print("Number of wells on plate: "); + numWells = sc.nextInt(); + if(numWells < 1){ + throw new InputMismatchException("No wells on plate"); + } + System.out.println("The plate can be evenly sectioned to allow multiple concentrations of T-cells/well"); + System.out.println("How many sections would you like to make (minimum 1)?"); + numSections = sc.nextInt(); + if(numSections < 1) { + throw new InputMismatchException("Too few sections."); + } + else if (numSections > numWells) { + throw new InputMismatchException("Cannot have more sections than wells."); + } + int i = 1; + concentrations = new Integer[numSections]; + while(numSections > 0) { + System.out.print("Enter number of T-cells per well in section " + i +": "); + concentrations[i - 1] = sc.nextInt(); + i++; + numSections--; + } + System.out.println("Errors in amplification can induce a well dropout rate for peptides"); + System.out.print("Enter well dropout rate (0.0 to 1.0): "); + dropOutRate = sc.nextDouble(); + if(dropOutRate < 0.0 || dropOutRate > 1.0) { + throw new InputMismatchException("The well dropout rate must be in the range [0.0, 1.0]"); + } + }catch(InputMismatchException ex){ + System.out.println(ex); + sc.next(); + } + CellFileReader cellReader = new CellFileReader(cellFile); + if(poisson) { + stdDev = Math.sqrt(cellReader.getCellCount()); //gaussian with square root of elements approximates poisson + } + Plate samplePlate = new Plate(numWells, dropOutRate, concentrations, stdDev); + samplePlate.fillWells(cellReader.getCells()); + PlateFileWriter writer = new PlateFileWriter(filename, samplePlate); + writer.writePlateFile(); + } + + private static void matchCells() { + String filename = null; + String cellFile = null; + String plateFile = null; + Integer lowThreshold = 0; + Integer highThreshold = Integer.MAX_VALUE; + try { + System.out.println("\nSimulated experiment requires a cell sample file and a sample plate file."); + System.out.print("Please enter name of an existing cell sample file: "); + cellFile = sc.next(); + System.out.print("Please enter name of an existing sample plate file: "); + plateFile = sc.next(); + System.out.println("The matching results will be written to a file."); + System.out.print("Please enter a name for the output file: "); + filename = sc.next(); + System.out.println("What is the minimum number of alpha/beta overlap wells to attempt matching?"); + lowThreshold = sc.nextInt(); + if(lowThreshold < 1){ + throw new InputMismatchException("Minimum value for low threshold is 1"); + } + System.out.println("What is the maximum number of alpha/beta overlap wells to attempt matching?"); + highThreshold = sc.nextInt(); + } catch (InputMismatchException ex) { + System.out.println(ex); + sc.next(); + } + CellFileReader cellReader = new CellFileReader(cellFile); + PlateFileReader plateReader = new PlateFileReader(plateFile); + Plate plate = new Plate(plateReader.getWells()); + if (cellReader.getCells().size() == 0){ + System.out.println("No cell sample found."); + System.out.println("Returning to main menu."); + } + else if(plate.getWells().size() == 0){ + System.out.println("No sample plate found."); + System.out.println("Returning to main menu."); + + } + else{ + if(highThreshold > plate.getSize()){ + highThreshold = plate.getSize(); + } + List cells = cellReader.getCells(); + Simulator.matchCells(filename, cells, plate, lowThreshold, highThreshold); + } + } +}