diff --git a/vcell-cli/src/main/java/org/vcell/cli/CLIStandalone.java b/vcell-cli/src/main/java/org/vcell/cli/CLIStandalone.java index 566ac3c65b..ca85e49af4 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/CLIStandalone.java +++ b/vcell-cli/src/main/java/org/vcell/cli/CLIStandalone.java @@ -11,6 +11,7 @@ import org.apache.logging.log4j.Logger; import cbit.vcell.mongodb.VCMongoMessage; +import org.vcell.cli.run.RunUtils; import org.vcell.util.VCellUtilityHub; import org.vcell.util.document.KeyValue; import org.vcell.util.document.User; @@ -36,6 +37,7 @@ public class CLIStandalone { private final static Logger logger = LogManager.getLogger(CLIStandalone.class); public static void main(String[] args) { int exitCode = -1; + RunUtils.drawBreakLine("-", 100); try{ logger.info("Starting Vcell..."); if (logger.isDebugEnabled()) logger.debug("!!!DEBUG Mode Active!!!"); diff --git a/vcell-cli/src/main/java/org/vcell/cli/messaging/CLIRecordable.java b/vcell-cli/src/main/java/org/vcell/cli/messaging/CLIRecordable.java index 8988ffd2f6..96b234b5a4 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/messaging/CLIRecordable.java +++ b/vcell-cli/src/main/java/org/vcell/cli/messaging/CLIRecordable.java @@ -5,16 +5,16 @@ public interface CLIRecordable { - public void writeDetailedErrorList(Exception e, String message) throws IOException; + void writeDetailedErrorList(Exception e, String message) throws IOException; - public void writeFullSuccessList(String message) throws IOException; + void writeFullSuccessList(String message) throws IOException; - public void writeErrorList(Exception e, String message) throws IOException; + void writeErrorList(Exception e, String message) throws IOException; - public void writeDetailedResultList(String message) throws IOException; + void writeDetailedResultList(String message) throws IOException; // we make a list with the omex files that contain (some) spatial simulations (FVSolverStandalone solver) - public void writeSpatialList(String message) throws IOException; +// void writeSpatialList(String message) throws IOException; - public void writeImportErrorList(Exception e, String message) throws IOException; + void writeImportErrorList(Exception e, String message) throws IOException; } diff --git a/vcell-cli/src/main/java/org/vcell/cli/messaging/CLIRecorder.java b/vcell-cli/src/main/java/org/vcell/cli/messaging/CLIRecorder.java index 389d8f0aec..c2eef55f20 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/messaging/CLIRecorder.java +++ b/vcell-cli/src/main/java/org/vcell/cli/messaging/CLIRecorder.java @@ -17,7 +17,7 @@ public class CLIRecorder extends Recorder implements CLIRecordable { protected final static boolean DEFAULT_SHOULD_PRINT_LOG_FILES = false, DEFAULT_SHOULD_FLUSH_LOG_FILES = false; protected boolean shouldPrintLogFiles, shouldFlushLogFiles; - protected TextFileRecord detailedErrorLog, fullSuccessLog, errorLog, detailedResultsLog, spatialLog, importErrorLog; + protected TextFileRecord detailedErrorLog, fullSuccessLog, errorLog, detailedResultsLog, /*spatialLog,*/ importErrorLog; protected File outputDirectory; // Note: this constructor is not public @@ -86,7 +86,7 @@ public CLIRecorder(File outputDirectory, boolean forceLogFiles, boolean shouldFl this.fullSuccessLog = logManager.requestNewRecord(Paths.get(this.outputDirectory.getAbsolutePath(), "fullSuccessLog.txt").toString()); this.errorLog = logManager.requestNewRecord(Paths.get(this.outputDirectory.getAbsolutePath(), "errorLog.txt").toString()); this.detailedResultsLog = logManager.requestNewRecord(Paths.get(this.outputDirectory.getAbsolutePath(), "detailedResultLog.txt").toString()); - this.spatialLog = logManager.requestNewRecord(Paths.get(this.outputDirectory.getAbsolutePath(), "hasSpatialLog.txt").toString()); + //this.spatialLog = logManager.requestNewRecord(Paths.get(this.outputDirectory.getAbsolutePath(), "hasSpatialLog.txt").toString()); this.importErrorLog = logManager.requestNewRecord(Paths.get(this.outputDirectory.getAbsolutePath(), "importErrorLog.txt").toString()); this.createHeader(); @@ -162,10 +162,10 @@ public void writeDetailedResultList(String message) throws IOException { * * @param message string to write to file */ - public void writeSpatialList(String message) throws IOException { - // we make a list with the omex files that contain (some) spatial simulations (FVSolverStandalone solver) - this.writeToFileLog(this.spatialLog, message); - } +// public void writeSpatialList(String message) throws IOException { +// // we make a list with the omex files that contain (some) spatial simulations (FVSolverStandalone solver) +// this.writeToFileLog(this.spatialLog, message); +// } /** * Write to `importErrorLog.txt` diff --git a/vcell-cli/src/main/java/org/vcell/cli/messaging/CliTracer.java b/vcell-cli/src/main/java/org/vcell/cli/messaging/CliTracer.java index f626968f19..aa2cc775d2 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/messaging/CliTracer.java +++ b/vcell-cli/src/main/java/org/vcell/cli/messaging/CliTracer.java @@ -20,7 +20,7 @@ public void writeErrorList(Exception e, String message) { public void writeDetailedResultList(String message) { Tracer.log("writeDetailedResultList(): "+message); } - @Override + //@Override public void writeSpatialList(String message) { Tracer.log("writeSpatialList(): "+message); } diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/CLISolverListener.java b/vcell-cli/src/main/java/org/vcell/cli/run/CLISolverListener.java new file mode 100644 index 0000000000..fcb561a7a5 --- /dev/null +++ b/vcell-cli/src/main/java/org/vcell/cli/run/CLISolverListener.java @@ -0,0 +1,106 @@ +package org.vcell.cli.run; + +import cbit.vcell.parser.DivideByZeroException; +import cbit.vcell.solver.SolverException; +import cbit.vcell.solver.server.SolverEvent; +import cbit.vcell.solver.server.SolverListener; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.vcell.util.Triplet; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; + +public class CLISolverListener implements SolverListener { + private static final Logger lg = LogManager.getLogger(CLISolverListener.class); + + public final CompletableFuture> solverResult; + private final StringBuilder solverLog; + private final String solverLabel; + + /** + * Tracks a CLI execution of a solver, and offers a completable future for the result! + * @param solverLabel what to call the solver this class listens to + */ + public CLISolverListener(String solverLabel) { + this.solverResult = new CompletableFuture<>(); + this.solverLog = new StringBuilder(); + this.solverLabel = solverLabel; + } + + /** + * Invoked when the solver aborts a calculation (abnormal termination). + * + * @param event indicates the solver and the event type + */ + @Override + public void solverAborted(SolverEvent event) { + Double progress = event.getProgress(); + Exception fault = this.determineFault(event); + this.solverResult.complete(new Triplet<>(progress, SolverEvent.SOLVER_ABORTED, fault)); + } + + private Exception determineFault(SolverEvent event){ + String sourceString = event.getSource().toString(); + String errorString = event.getSimulationMessage().getDisplayMessage(); + String formattedString = String.format("%s(%s): %s", this.solverLabel, sourceString, errorString); + if (errorString.toLowerCase().contains("timed out")) return new TimeoutException(formattedString); + else if (errorString.toLowerCase().contains("divide by zero")) return new DivideByZeroException(formattedString); + else return new SolverException(formattedString); + } + + /** + * Invoked when the solver finishes a calculation (normal termination). + * + * @param event indicates the solver and the event type + */ + @Override + public void solverFinished(SolverEvent event) { + Double progress = event.getProgress(); + String sourceString = event.getSource().toString(); + String successString = event.getSimulationMessage().toString(); + lg.info("{}({}) Success Message: {}", this.solverLabel, sourceString, successString); + this.solverResult.complete(new Triplet<>(progress, SolverEvent.SOLVER_FINISHED, null)); + } + + /** + * Invoked when the solver stores values in the result set. + * + * @param event indicates the solver and the event type + */ + @Override + public void solverPrinted(SolverEvent event) { + this.solverLog.append(event.getSimulationMessage()).append("\n"); + } + + /** + * Invoked when the solver stores values in the result set. + * + * @param event indicates the solver and the event type + */ + @Override + public void solverProgress(SolverEvent event) { + lg.info("{}({}) progress: {}\t(\"{}\")", this.solverLabel, event.getSource().toString(), event.getProgress(), event.getSimulationMessage()); + } + + /** + * Invoked when the solver begins a calculation. + * + * @param event indicates the solver and the event type + */ + @Override + public void solverStarting(SolverEvent event) { + lg.info("{}({}) Beginning Simulation (\"{}\")", this.solverLabel, event.getSource().toString(), event.getSimulationMessage()); + } + + /** + * Invoked when the solver stops a calculation, usually because + * of a user-initiated stop call. + * + * @param event indicates the solver and the event type + */ + @Override + public void solverStopped(SolverEvent event) { + lg.info("{}({}) Ending Simulation: (\"{}\")", this.solverLabel, event.getSource().toString(), event.getSimulationMessage()); + } +} diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/ExecutionJob.java b/vcell-cli/src/main/java/org/vcell/cli/run/ExecutionJob.java index 9e811434e8..22d786e4f6 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/run/ExecutionJob.java +++ b/vcell-cli/src/main/java/org/vcell/cli/run/ExecutionJob.java @@ -35,7 +35,6 @@ public class ExecutionJob { private OmexHandler omexHandler = null; private List sedmlLocations; - private Path sedmlPath2d3d; private File inputFile; private CLIRecordable cliRecorder; @@ -59,7 +58,7 @@ public ExecutionJob(File inputFile, File rootOutputDir, CLIRecordable cliRecorde this.bioModelBaseName = FileUtils.getBaseName(inputFile.getName()); // input file without the path String outputBaseDir = rootOutputDir.getAbsolutePath(); - this.outputDir = bEncapsulateOutput ? Paths.get(outputBaseDir, bioModelBaseName).toString() : outputBaseDir; + this.outputDir = bEncapsulateOutput ? Paths.get(outputBaseDir, this.bioModelBaseName).toString() : outputBaseDir; this.bKeepTempFiles = bKeepTempFiles; this.bExactMatchOnly = bExactMatchOnly; this.bSmallMeshOverride = bSmallMeshOverride; @@ -86,7 +85,6 @@ public void preprocessArchive() throws IOException { // Unpack the Omex Archive try { // It's unlikely, but if we get errors here they're fatal. - this.sedmlPath2d3d = Paths.get(this.outputDir, "temp"); this.omexHandler = new OmexHandler(this.inputFile.getAbsolutePath(), this.outputDir); this.omexHandler.extractOmex(); this.sedmlLocations = this.omexHandler.getSedmlLocationsAbsolute(); @@ -136,17 +134,21 @@ public void executeArchive(boolean isBioSimSedml) throws BiosimulationsHdfWriter } catch (IOException e){ logger.error("System IO encountered a fatal error"); throw new ExecutionException(e); + } finally { + RunUtils.drawBreakLine("-", 100); } + } private void executeSedmlDocument(String sedmlLocation, HDF5ExecutionResults cumulativeHdf5Results) throws IOException, PreProcessingException, ExecutionException { BiosimulationLog.instance().updateSedmlDocStatusYml(sedmlLocation, BiosimulationLog.Status.QUEUED); - SedmlJob job = new SedmlJob(sedmlLocation, this.omexHandler, this.inputFile, this.outputDir, this.sedmlPath2d3d.toString(), this.cliRecorder, this.bKeepTempFiles, this.bExactMatchOnly, this.bSmallMeshOverride, this.logOmexMessage); + SedmlJob job = new SedmlJob(sedmlLocation, this.omexHandler.getOutputPathFromSedml(sedmlLocation), this.inputFile, this.outputDir, this.cliRecorder, this.bKeepTempFiles, this.bExactMatchOnly, this.bSmallMeshOverride, this.logOmexMessage); SedmlStatistics stats = job.preProcessDoc(); boolean hasSucceeded = job.simulateSedml(cumulativeHdf5Results); this.anySedmlDocumentHasSucceeded |= hasSucceeded; this.anySedmlDocumentHasFailed &= hasSucceeded; logger.log(hasSucceeded ? Level.INFO : Level.ERROR, "Processing of SedML ({}) {}", stats.getSedmlName(), hasSucceeded ? "succeeded." : "failed!"); + RunUtils.drawBreakLine("-", 100); } /** @@ -162,23 +164,23 @@ public void postProcessessArchive() throws IOException { this.endTime_ms = System.currentTimeMillis(); long elapsedTime_ms = this.endTime_ms - this.startTime_ms; double duration_s = elapsedTime_ms / 1000.0; - logger.info("Omex `" + inputFile.getName() + "` processing completed (" + duration_s + "s)"); + logger.info("Omex `" + this.inputFile.getName() + "` processing completed (" + duration_s + "s)"); // // failure if at least one of the documents in the omex archive fails // - if (anySedmlDocumentHasFailed) { + if (this.anySedmlDocumentHasFailed) { String error = " All sedml documents in this archive failed to execute"; - if (anySedmlDocumentHasSucceeded) { // some succeeded, some failed + if (this.anySedmlDocumentHasSucceeded) { // some succeeded, some failed error = " At least one document in this archive failed to execute"; } BiosimulationLog.instance().updateOmexStatusYml(BiosimulationLog.Status.FAILED, duration_s); logger.error(error); - logOmexMessage.append(error); - cliRecorder.writeErrorList(new Exception("exception not recorded"), bioModelBaseName); + this.logOmexMessage.append(error); + this.cliRecorder.writeErrorList(new Exception("exception not recorded"), this.bioModelBaseName); } else { BiosimulationLog.instance().updateOmexStatusYml(BiosimulationLog.Status.SUCCEEDED, duration_s); - cliRecorder.writeFullSuccessList(bioModelBaseName); - logOmexMessage.append(" Done"); + this.cliRecorder.writeFullSuccessList(this.bioModelBaseName); + this.logOmexMessage.append(" Done"); } BiosimulationLog.instance().setOutputMessage("null", "null", "omex", logOmexMessage.toString()); diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/OmexHandler.java b/vcell-cli/src/main/java/org/vcell/cli/run/OmexHandler.java index 1bb3f4ace2..99487ea680 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/run/OmexHandler.java +++ b/vcell-cli/src/main/java/org/vcell/cli/run/OmexHandler.java @@ -94,29 +94,6 @@ private void replaceMetadataRdfFiles(Path zipFilePath) throws IOException { } } - public static void rename(String zipFileName, String entryOldName, String entryNewName) throws Exception { - - /* Define ZIP File System Properties in HashMap */ - Map zip_properties = new HashMap<>(); - /* We want to read an existing ZIP File, so we set this to False */ - zip_properties.put("create", "false"); - - /* Specify the path to the ZIP File that you want to read as a File System */ - URI zip_disk = URI.create("jar:file:/" + zipFileName); - - /* Create ZIP file System */ - try (FileSystem zipfs = FileSystems.newFileSystem(zip_disk, zip_properties)) { - /* Access file that needs to be renamed */ - Path pathInZipfile = zipfs.getPath(entryOldName); - /* Specify new file name */ - Path renamedZipEntry = zipfs.getPath(entryNewName); - logger.trace("About to rename an entry from ZIP File" + pathInZipfile.toUri()); - /* Execute rename */ - Files.move(pathInZipfile, renamedZipEntry, StandardCopyOption.ATOMIC_MOVE); - logger.trace("File successfully renamed"); - } - } - public List getSedmlLocationsRelative(){ Collection entries = this.archive.getEntries(); @@ -135,26 +112,16 @@ public List getSedmlLocationsRelative(){ sedmlMap.get(isMaster ? MASTER : REGULAR).add(entry); } + if (!sedmlMap.get(MASTER).isEmpty()) return sedmlMap.get(MASTER).stream().map(ArchiveEntry::getFilePath).toList(); + // Test corner cases - if (sedmlMap.get(MASTER).isEmpty()){ - if (sedmlMap.get(REGULAR).isEmpty()) { - throw new RuntimeException("There are no SED-MLs in the archive to execute"); - } - if (masterCount > 0) { - logger.warn("No SED-MLs are marked as master, so will run them all"); - } - return sedmlMap.get(REGULAR).stream().map(ArchiveEntry::getFilePath).toList(); - } + if (sedmlMap.get(REGULAR).isEmpty()) throw new RuntimeException("There are no SED-MLs in the archive to execute"); - return sedmlMap.get(MASTER).stream().map(ArchiveEntry::getFilePath).toList(); - } + if (masterCount > 0) logger.warn("No SED-MLs are marked as master, so will run them all"); - private boolean isSedmlFormat(ArchiveEntry entry) { - URI format = entry.getFormat(); - return format.getPath().contains("sedml") || format.getPath().contains("sed-ml"); + return sedmlMap.get(REGULAR).stream().map(ArchiveEntry::getFilePath).toList(); } - public ArrayList getSedmlLocationsAbsolute(){ ArrayList sedmlListAbsolute = new ArrayList<>(); List sedmlListRelative = this.getSedmlLocationsRelative(); @@ -165,9 +132,13 @@ public ArrayList getSedmlLocationsAbsolute(){ return sedmlListAbsolute; } + private boolean isSedmlFormat(ArchiveEntry entry) { + URI format = entry.getFormat(); + return format.getPath().contains("sedml") || format.getPath().contains("sed-ml"); + } + public String getOutputPathFromSedml(String absoluteSedmlPath) { String outputPath = ""; - //String sedmlName = absoluteSedmlPath.substring(absoluteSedmlPath.lastIndexOf(File.separator) + 1); List sedmlListRelative = this.getSedmlLocationsRelative(); for (String sedmlFileRelative : sedmlListRelative) { boolean check = absoluteSedmlPath.contains(Paths.get(sedmlFileRelative).normalize().toString()); diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/RunUtils.java b/vcell-cli/src/main/java/org/vcell/cli/run/RunUtils.java index 31171de00c..61d79acf93 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/run/RunUtils.java +++ b/vcell-cli/src/main/java/org/vcell/cli/run/RunUtils.java @@ -17,7 +17,9 @@ import org.jlibsedml.DataSet; import org.jlibsedml.*; import org.vcell.cli.run.results.ValueHolder; +import org.vcell.sbml.vcell.lazy.LazySBMLDataAccessor; import org.vcell.sbml.vcell.lazy.LazySBMLNonSpatialDataAccessor; +import org.vcell.sbml.vcell.lazy.LazySBMLSpatialDataAccessor; import org.vcell.util.DataAccessException; import org.vcell.util.GenericExtensionFilter; import org.vcell.util.document.User; @@ -38,113 +40,7 @@ public class RunUtils { private RunUtils(){} // Static class, no instances allowed - public static ODESolverResultSet interpolate(ODESolverResultSet odeSolverResultSet, UniformTimeCourse sedmlSim) throws ExpressionException { - double outputStart = sedmlSim.getOutputStartTime(); - double outputEnd = sedmlSim.getOutputEndTime(); - - int numPoints = sedmlSim.getNumberOfSteps() + 1; - - - ColumnDescription[] columnDescriptions = odeSolverResultSet.getColumnDescriptions(); - String[] columnNames = new String[columnDescriptions.length]; - - for (int i = 0; i < columnDescriptions.length; i++) { - columnNames[i] = columnDescriptions[i].getDisplayName(); - } - - // need to construct a new RowColumnResultSet instance - ODESolverResultSet finalResultSet = new ODESolverResultSet(); - - - // use same column descriptions - for (ColumnDescription cd : columnDescriptions) { - finalResultSet.addDataColumn(cd); - } - - - double deltaTime = ((outputEnd - outputStart) / (numPoints - 1)); - double[] timepoints = new double[numPoints]; - - timepoints[0] = outputStart; - for (int i = 1; i < numPoints; i++) { - timepoints[i] = timepoints[i - 1] + deltaTime; - } - - double[] originalTimepoints = odeSolverResultSet.extractColumn(0); - - - double[][] columnValues = new double[columnDescriptions.length][]; - columnValues[0] = timepoints; - for (int i = 1; i < columnDescriptions.length; i++) { - // each row uses the time index based on the params above and for each column descriptions interpolate the value from the original result set - columnValues[i] = interpLinear(originalTimepoints, odeSolverResultSet.extractColumn(i), timepoints); - } - - - double[][] rowValues = new double[numPoints][columnDescriptions.length]; - - for (int rowCount = 0; rowCount < numPoints; rowCount++) { - for (int colCount = 0; colCount < columnDescriptions.length; colCount++) { - rowValues[rowCount][colCount] = columnValues[colCount][rowCount]; - } - } - - - // add a numPoints number of rows one by one as double[] - for (int rowCount = 0; rowCount < numPoints; rowCount++) { - finalResultSet.addRow(rowValues[rowCount]); - } - - return finalResultSet; - } - - public static double[] interpLinear(double[] x, double[] y, double[] xi) throws IllegalArgumentException { - - if (x.length != y.length) { - throw new IllegalArgumentException("X and Y must be the same length"); - } - if (x.length == 1) { - throw new IllegalArgumentException("X must contain more than one value"); - } - double[] dx = new double[x.length - 1]; - double[] dy = new double[x.length - 1]; - double[] slope = new double[x.length - 1]; - double[] intercept = new double[x.length - 1]; - - // Calculate the line equation (i.e. slope and intercept) between each point - for (int i = 0; i < x.length - 1; i++) { - dx[i] = x[i + 1] - x[i]; - if (dx[i] == 0) { - throw new IllegalArgumentException("X must be montotonic. A duplicate " + "x-value was found"); - } - if (dx[i] < 0) { - throw new IllegalArgumentException("X must be sorted"); - } - dy[i] = y[i + 1] - y[i]; - slope[i] = dy[i] / dx[i]; - intercept[i] = y[i] - x[i] * slope[i]; - } - - // Perform the interpolation here - double[] yi = new double[xi.length]; - for (int i = 0; i < xi.length; i++) { - if ((xi[i] > x[x.length - 1]) || (xi[i] < x[0])) { - yi[i] = Double.NaN; - } else { - int loc = Arrays.binarySearch(x, xi[i]); - if (loc < -1) { - loc = -loc - 2; - yi[i] = slope[loc] * xi[i] + intercept[loc]; - } else { - yi[i] = y[loc]; - } - } - } - - return yi; - } - - public static HashMap generateReportsAsCSV(SedML sedml, Map> organizedNonSpatialResults, File outDirForCurrentSedml) { + public static HashMap generateReportsAsCSV(SedML sedml, Map> organizedNonSpatialResults, File outDirForCurrentSedml) { // finally, the real work HashMap reportsHash = new HashMap<>(); for (Output sedmlOutput : sedml.getOutputs()) { @@ -183,7 +79,7 @@ public static HashMap generateReportsAsCSV(SedML sedml, Map dataHolder = organizedNonSpatialResults.get(referencedGenerator); + ValueHolder dataHolder = organizedNonSpatialResults.get(referencedGenerator); boolean timeAlreadyIncluded = false; @@ -191,7 +87,9 @@ public static HashMap generateReportsAsCSV(SedML sedml, Map changes = rt.getChanges(); if(changes == null || changes.isEmpty()) continue; - this.DOC_STATISTICS.setHasScans(this.hasScans = true); + this.DOC_STATISTICS.setHasScans(true); break; } @@ -211,22 +211,25 @@ private void runSimulations(SolverHandler solverHandler) throws ExecutionExcepti ExternalDocInfo externalDocInfo = new ExternalDocInfo(this.MASTER_OMEX_ARCHIVE, true); Span span = null; - String str = "Building solvers and starting simulation of all tasks... "; + logger.info("SedML Parsing complete."); + RunUtils.drawBreakLine("-", 100); + String str = "Queuing all tasks for simulation... "; logger.info(str); this.logDocumentMessage += str; - RunUtils.drawBreakLine("-", 100); try { span = Tracer.startSpan(Span.ContextType.SIMULATIONS_RUN, "runSimulations", null); - Map taskResults = solverHandler.simulateAllTasks(externalDocInfo, this.sedml, this.CLI_RECORDER, + Map> taskResults = solverHandler.simulateAllTasks(externalDocInfo, this.sedml, this.CLI_RECORDER, this.OUTPUT_DIRECTORY_FOR_CURRENT_SEDML, this.RESULTS_DIRECTORY_PATH, - this.SEDML_LOCATION, this.SHOULD_KEEP_TEMP_FILES, - this.ACCEPT_EXACT_MATCH_ONLY, this.SHOULD_OVERRIDE_FOR_SMALL_MESH); + this.SEDML_LOCATION, this.ACCEPT_EXACT_MATCH_ONLY, this.SHOULD_OVERRIDE_FOR_SMALL_MESH); int numSimulationsUnsuccessful = 0; + logger.info("Finished all solver executions for {}", this.SEDML_NAME); StringBuilder executionSummary = new StringBuilder("Summary of Task Results\n"); for (AbstractTask sedmlTask : taskResults.keySet()){ String sedmlTaskName = (sedmlTask.getName() == null || sedmlTask.getName().isBlank()) ? sedmlTask.getId() : sedmlTask.getName() + " (" + sedmlTask.getId() + ")" ; - executionSummary.append("\t> ").append(sedmlTaskName).append("::").append(taskResults.get(sedmlTask).name()).append("\n"); - if (!taskResults.get(sedmlTask).equals(BiosimulationLog.Status.SUCCEEDED)) numSimulationsUnsuccessful++; + executionSummary.append("\t> ").append(sedmlTaskName).append("::").append(taskResults.get(sedmlTask).one.name()).append("\n"); + if (!taskResults.get(sedmlTask).one.equals(BiosimulationLog.Status.SUCCEEDED)){ + logger.error("Unsuccessful run #{} detected: Status :: {}\n", ++numSimulationsUnsuccessful, taskResults.get(sedmlTask).one, taskResults.get(sedmlTask).three); + } } logger.info(executionSummary.toString()); if (numSimulationsUnsuccessful > 0) throw new ExecutionException(numSimulationsUnsuccessful + " simulation" + (numSimulationsUnsuccessful == 1 ? "s" : "") + " were unsuccessful."); @@ -260,11 +263,11 @@ private void processOutputs(SolverHandler solverHandler, HDF5ExecutionResults ma try { span = Tracer.startSpan(Span.ContextType.PROCESSING_SIMULATION_OUTPUTS, "processOutputs", null); ///////////////////////////////////////////////////// - Map> organizedNonSpatialResults = + Map> organizedNonSpatialResults = NonSpatialResultsConverter.organizeNonSpatialResultsBySedmlDataGenerator( this.sedml, solverHandler.nonSpatialResults, solverHandler.taskToTempSimulationMap); - Map> organizedSpatialResults = + Map> organizedSpatialResults = SpatialResultsConverter.organizeSpatialResultsBySedmlDataGenerator( this.sedml, solverHandler.spatialResults, solverHandler.taskToTempSimulationMap); @@ -282,10 +285,8 @@ private void processOutputs(SolverHandler solverHandler, HDF5ExecutionResults ma this.logDocumentError += e.getMessage(); try { this.reportProblem(e); - org.apache.commons.io.FileUtils.deleteDirectory(this.PLOTS_DIRECTORY); // removing temp path generated from python } catch (IOException ioe){ - throw new RuntimeException(String.format("Encountered IOException while trying to delete '%s':{%s}", - this.PLOTS_DIRECTORY.getName(), e.getMessage()), ioe); + throw new RuntimeException("Encountered IOException while trying to report problem", ioe); } throw new ExecutionException("error while processing outputs: " + e.getMessage(), e); } finally { @@ -312,10 +313,10 @@ private boolean declareFailedResult(){ this.logDocumentError += e.getMessage(); try{ this.reportProblem(e); - org.apache.commons.io.FileUtils.deleteDirectory(this.PLOTS_DIRECTORY); // removing temp path generated from python } catch (IOException ioe){ - Tracer.failure(ioe, "Deletion of " + this.PLOTS_DIRECTORY.getName() + " failed"); - logger.warn("Deletion of " + this.PLOTS_DIRECTORY.getName() + " failed", ioe); + String failureMessage = "Encountered IOException while trying to declare failed result"; + Tracer.failure(ioe, failureMessage); + logger.warn(failureMessage, ioe); } logger.warn(this.logDocumentError); return false; @@ -325,7 +326,6 @@ private boolean declarePassedResult() throws IOException { // archiving result files if (logger.isDebugEnabled()) logger.info("Archiving result files"); RunUtils.zipResFiles(new File(this.RESULTS_DIRECTORY_PATH)); - org.apache.commons.io.FileUtils.deleteDirectory(this.PLOTS_DIRECTORY); // removing sedml dir which stages results. // Declare success! BiosimulationLog biosimLog = BiosimulationLog.instance(); @@ -335,7 +335,7 @@ private boolean declarePassedResult() throws IOException { return true; } - private void generateCSV(Map> organizedNonSpatialResults) { + private void generateCSV(Map> organizedNonSpatialResults) { Map csvReports; this.logDocumentMessage += "Generating CSV file... "; logger.info("Generating CSV file... "); @@ -352,7 +352,7 @@ private void generateCSV(Map> organizedNonSpatialResults) throws ExecutionException { + private void generatePlots(Map> organizedNonSpatialResults) throws ExecutionException { logger.info("Generating Plots... "); // We assume if no exception is returned that the plots pass PlottingDataExtractor plotExtractor = new PlottingDataExtractor(this.sedml, this.SEDML_NAME); @@ -369,7 +369,7 @@ private void generatePlots(Map> organizedNonSpatialResults, Map> organizedSpatialResults, SolverHandler solverHandler, HDF5ExecutionResults masterHdf5File) { + private void indexHDF5Data(Map> organizedNonSpatialResults, Map> organizedSpatialResults, SolverHandler solverHandler, HDF5ExecutionResults masterHdf5File) { this.logDocumentMessage += "Indexing HDF5 data... "; logger.info("Indexing HDF5 data... "); diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/SolverHandler.java b/vcell-cli/src/main/java/org/vcell/cli/run/SolverHandler.java index 1828124a7b..f62beaaa81 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/run/SolverHandler.java +++ b/vcell-cli/src/main/java/org/vcell/cli/run/SolverHandler.java @@ -6,6 +6,7 @@ import cbit.vcell.field.FieldDataIdentifierSpec; import cbit.vcell.mapping.MathSymbolMapping; import cbit.vcell.math.FunctionColumnDescription; +import cbit.vcell.math.MathException; import cbit.vcell.messaging.server.SimulationTask; import cbit.vcell.parser.Expression; import cbit.vcell.parser.ExpressionException; @@ -13,45 +14,35 @@ import cbit.vcell.solver.*; import cbit.vcell.solver.Simulation; import cbit.vcell.solver.ode.AbstractJavaSolver; -import cbit.vcell.solver.ode.ODESolver; import cbit.vcell.solver.ode.ODESolverResultSet; +import cbit.vcell.solver.ode.ODESolverResultsSetReturnable; import cbit.vcell.solver.server.Solver; -import cbit.vcell.solver.server.SolverFactory; +import cbit.vcell.solver.server.SolverEvent; import cbit.vcell.solver.server.SolverStatus; -import cbit.vcell.solver.stoch.GibsonSolver; -import cbit.vcell.solver.stoch.HybridSolver; import cbit.vcell.solvers.AbstractCompiledSolver; import cbit.vcell.xml.ExternalDocInfo; -import org.jlibsedml.AbstractTask; -import org.jlibsedml.DataGenerator; -import org.jlibsedml.DataSet; -import org.jlibsedml.Output; -import org.jlibsedml.Range; -import org.jlibsedml.RepeatedTask; -import org.jlibsedml.Report; -import org.jlibsedml.SedML; -import org.jlibsedml.SetValue; -import org.jlibsedml.SubTask; -import org.jlibsedml.Task; -import org.jlibsedml.UniformTimeCourse; -import org.jlibsedml.Variable; -import org.jlibsedml.XPathTarget; -import org.jlibsedml.XMLException; +import org.jlibsedml.*; import org.jlibsedml.modelsupport.SBMLSupport; import org.jmathml.ASTNode; import org.vcell.cli.messaging.CLIRecordable; +import org.vcell.cli.run.results.solver.SolverExecutionRequest; +import org.vcell.cli.run.results.solver.SolverNonSpatialExecutionRequest; +import org.vcell.cli.run.results.solver.SolverSpatialExecutionRequest; import org.vcell.sbml.vcell.*; import org.vcell.sedml.SEDMLImportException; import org.vcell.sedml.log.BiosimulationLog; import org.vcell.trace.Span; import org.vcell.trace.Tracer; import org.vcell.sedml.SEDMLImporter; +import org.vcell.util.DataAccessException; import org.vcell.util.ISize; import org.apache.commons.lang.NotImplementedException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.vcell.util.Pair; +import org.vcell.util.Triplet; import java.beans.PropertyVetoException; import java.io.*; @@ -64,18 +55,17 @@ public class SolverHandler { public int countBioModels = 0; // number of biomodels in this sedml file public int countSuccessfulSimulationRuns = 0; // number of simulations that we ran successfully for this sedml file - private SEDMLImporter sedmlImporter; - Map nonSpatialResults = new LinkedHashMap<>(); + Map nonSpatialResults = new LinkedHashMap<>(); Map spatialResults = new LinkedHashMap<>(); Map tempSimulationToTaskMap = new LinkedHashMap<> (); // key = vcell simulation, value = sedml topmost task (the imported task id) Map taskToTempSimulationMap = new LinkedHashMap<> (); // the opposite Map origSimulationToTempSimulationMap = new LinkedHashMap<> (); // the opposite Map> taskToListOfSubTasksMap = new LinkedHashMap> (); // key = topmost AbstractTask, value = recursive list of subtasks - Map> taskToVariableMap = new LinkedHashMap> (); // key = AbstractTask, value = list of variables calculated by this task - Map> taskToChangeTargetMap = new LinkedHashMap> (); // key = RepeatedTask, value = list of the parameters that are being changed - Map> taskToChildRepeatedTasks = new LinkedHashMap> (); // key = Task, value = list of RepeatedTasks ending with this task - Map topTaskToBaseTask = new LinkedHashMap (); // key = TopmostTaskId, value = Tasks at the bottom of the SubTasks chain OR the topmost task itself if instanceof Task + Map> taskToVariableMap = new LinkedHashMap<> (); // key = AbstractTask, value = list of variables calculated by this task + Map> taskToChangeTargetMap = new LinkedHashMap<> (); // key = RepeatedTask, value = list of the parameters that are being changed + Map> taskToChildRepeatedTasks = new LinkedHashMap<> (); // key = Task, value = list of RepeatedTasks ending with this task + Map topTaskToBaseTask = new LinkedHashMap<> (); // key = TopmostTaskId, value = Tasks at the bottom of the SubTasks chain OR the topmost task itself if instanceof Task private static void sanityCheck(BioModel bioModel) throws SEDMLImportException { if (bioModel == null) throw new SEDMLImportException("Imported BioModel is null."); @@ -107,9 +97,9 @@ public void initialize(List bioModelList, SedML sedml) throws Expressi TempSimulation tempSimulation = new TempSimulation(sim,false); String importedTaskId = tempSimulation.getImportedTaskID(); AbstractTask at = sedml.getTaskWithId(importedTaskId); - tempSimulationToTaskMap.put(tempSimulation, at); - taskToTempSimulationMap.put(at, tempSimulation); - origSimulationToTempSimulationMap.put(sim, tempSimulation); + this.tempSimulationToTaskMap.put(tempSimulation, at); + this.taskToTempSimulationMap.put(at, tempSimulation); + this.origSimulationToTempSimulationMap.put(sim, tempSimulation); topmostTasks.add(at); // all the tasks referred by an importedTaskId are supposed to be topmost } } @@ -157,13 +147,13 @@ public void initialize(List bioModelList, SedML sedml) throws Expressi } else { actualTask = (Task)task; } - taskToListOfSubTasksMap.put(task, subTasksList); // subTasksList may be empty if task instanceof Task - topTaskToBaseTask.put(task.getId(), actualTask); + this.taskToListOfSubTasksMap.put(task, subTasksList); // subTasksList may be empty if task instanceof Task + this.topTaskToBaseTask.put(task.getId(), actualTask); Set childRepeatedTasks = new LinkedHashSet<> (); - taskToChildRepeatedTasks.put(actualTask, childRepeatedTasks); // list of all Tasks, the set is only initialized here + this.taskToChildRepeatedTasks.put(actualTask, childRepeatedTasks); // list of all Tasks, the set is only initialized here } - for(Map.Entry> entry : taskToListOfSubTasksMap.entrySet()) { // populate the taskToChildRepeatedTasks map + for(Map.Entry> entry : this.taskToListOfSubTasksMap.entrySet()) { // populate the taskToChildRepeatedTasks map AbstractTask topmostTask = entry.getKey(); List dependingTasks = entry.getValue(); if(topmostTask instanceof Task) { @@ -182,7 +172,7 @@ public void initialize(List bioModelList, SedML sedml) throws Expressi } } // assert rootTask != null; - Set childRepeatedTasks = taskToChildRepeatedTasks.get(actualTask); + Set childRepeatedTasks = this.taskToChildRepeatedTasks.get(actualTask); // assert childRepeatedTasks.isEmpty() == true; childRepeatedTasks.add((RepeatedTask)topmostTask); for(AbstractTask dependingTask : dependingTasks) { @@ -308,21 +298,19 @@ public TempSimulation getTempSimulation() { } } - public Map simulateAllTasks(ExternalDocInfo externalDocInfo, SedML sedmlRequested, CLIRecordable cliLogger, - File outputDirForSedml, String outDir, String sedmlLocation, - boolean keepTempFiles, boolean exactMatchOnly, boolean bSmallMeshOverride) + public Map> simulateAllTasks(ExternalDocInfo externalDocInfo, SedML sedmlRequested, CLIRecordable cliLogger, + File outputDirForSedml, String outDir, String sedmlLocation, boolean exactMatchOnly, boolean bSmallMeshOverride) throws XMLException, IOException, SEDMLImportException, ExpressionException, PropertyVetoException { // create the VCDocument(s) (bioModel(s) + application(s) + simulation(s)), do sanity checks - Map biosimStatusMap = new LinkedHashMap<>(); + Map> biosimStatusMap = new LinkedHashMap<>(); cbit.util.xml.VCLogger sedmlImportLogger = new LocalLogger(); - String inputFile = externalDocInfo.getFile().getAbsolutePath(); - String bioModelBaseName = org.vcell.util.FileUtils.getBaseName(inputFile); + String bioModelBaseName = org.vcell.util.FileUtils.getBaseName(externalDocInfo.getFile().getAbsolutePath()); //String outDirRoot = outputDirForSedml.toString().substring(0, outputDirForSedml.toString().lastIndexOf(System.getProperty("file.separator"))); - this.sedmlImporter = new SEDMLImporter(sedmlImportLogger, externalDocInfo.getFile(), sedmlRequested, exactMatchOnly); + SEDMLImporter sedmlImporter = new SEDMLImporter(sedmlImportLogger, externalDocInfo.getFile(), sedmlRequested, exactMatchOnly); List bioModelList; try { - bioModelList = this.sedmlImporter.getBioModels(); + bioModelList = sedmlImporter.getBioModels(); } catch (Exception e) { logger.error("Unable to Parse SED-ML into Bio-Model, failed with err: {}", e.getMessage(), e); throw e; @@ -331,248 +319,161 @@ public Map simulateAllTasks(ExternalDocIn this.countBioModels = bioModelList.size(); - this.initialize(bioModelList, sedmlRequested); int simulationJobCount = 0; - int bioModelCount = 0; - boolean hasSomeSpatial = false; - boolean bTimeoutFound = false; for (BioModel bioModel : bioModelList) { - Span biomodel_span = null; - try { - biomodel_span = Tracer.startSpan(Span.ContextType.BioModel, bioModel.getName(), Map.of("bioModelName", bioModel.getName())); - - Map vCellTempSimStatusMap = new LinkedHashMap<>(); - Map simDurationMap_ms = new LinkedHashMap<>(); - - List simJobsList = preProcessTempSimulations(sedmlLocation, bSmallMeshOverride, bioModel, vCellTempSimStatusMap, simDurationMap_ms); - for (TempSimulationJob tempSimulationJob : simJobsList) { - AbstractTask task = tempSimulationToTaskMap.get(tempSimulationJob.getTempSimulation()); - biosimStatusMap.put(task, BiosimulationLog.Status.QUEUED); - String paramScanIndex = task instanceof RepeatedTask ? ":" + tempSimulationJob.getJobIndex() : ""; - String tempSimJobLabel = tempSimulationJob.getSimulationJobID() + tempSimulationJob.getJobIndex(); - String logTaskMessage = String.format("Initializing simulation job %s (%s%s)...", tempSimJobLabel, task.getId(), paramScanIndex); - RunUtils.drawBreakLine("- -", 25); - logger.info(logTaskMessage); - String logTaskError = ""; - long startTimeTask_ms = System.currentTimeMillis(); - - SimulationTask simTask; - String kisao = "null"; - ODESolverResultSet odeSolverResultSet = null; - SolverDescription sd = null; - int solverStatus = SolverStatus.SOLVER_READY; - - Simulation tempSimulationJobSim = tempSimulationJob.getSimulation(); - simTask = new SimulationTask(tempSimulationJob, 0); - Span sim_span = null; - try { - sim_span = Tracer.startSpan(Span.ContextType.SIMULATION_RUN, tempSimulationJobSim.getName(), Map.of("simName", tempSimulationJobSim.getName())); - SimulationOwner so = tempSimulationJobSim.getSimulationOwner(); - tempSimulationJobSim = new TempSimulation(tempSimulationJobSim, false); - tempSimulationJobSim.setSimulationOwner(so); - + try (Span biomodel_span = Tracer.startSpan(Span.ContextType.BioModel, bioModel.getName(), Map.of("bioModelName", bioModel.getName()))) { + Map vCellTempSimStatusMap = new LinkedHashMap<>(); + + List simJobsList = this.preProcessTempSimulations(sedmlLocation, bSmallMeshOverride, bioModel, vCellTempSimStatusMap); + for (TempSimulationJob tempSimulationJob : simJobsList) { + + AbstractTask task = this.tempSimulationToTaskMap.get(tempSimulationJob.getTempSimulation()); + biosimStatusMap.put(task, new Triplet<>(BiosimulationLog.Status.QUEUED, 0.0, null)); + Task baseTask = this.topTaskToBaseTask.get(task.getId()); + if (!(sedmlRequested.getSimulation(baseTask.getSimulationReference()) instanceof UniformTimeCourse utcSim)) throw new IllegalArgumentException("Sim found was not UTC."); + String paramScanIndex = task instanceof RepeatedTask ? ":" + tempSimulationJob.getJobIndex() : ""; + String tempSimJobLabel = tempSimulationJob.getSimulationJobID() + tempSimulationJob.getJobIndex(); + String logTaskMessage = String.format("Initializing SedML task %s%s (%s)...", task.getId(), paramScanIndex, tempSimJobLabel); + RunUtils.drawBreakLine("- -", 25); logger.info(logTaskMessage); + String logTaskError = ""; + long startTimeTask_ms = System.currentTimeMillis(); + + SimulationTask simTask; + String kisao; + + Simulation tempSimulationJobSim = tempSimulationJob.getSimulation(); + simTask = new SimulationTask(tempSimulationJob, 0); + SimulationOwner so = tempSimulationJobSim.getSimulationOwner(); + tempSimulationJobSim = new TempSimulation(tempSimulationJobSim, false); + tempSimulationJobSim.setSimulationOwner(so); + try (Span sim_span = Tracer.startSpan(Span.ContextType.SIMULATION_RUN, tempSimulationJobSim.getName(), Map.of("simName", tempSimulationJobSim.getName()))){ SolverTaskDescription std = tempSimulationJobSim.getSolverTaskDescription(); - sd = std.getSolverDescription(); + SolverDescription sd = std.getSolverDescription(); kisao = sd.getKisao(); if (kisao == null) throw new RuntimeException("KISAO is null."); - - Solver solver = SolverFactory.createSolver(outputDirForSedml, simTask, false); - logTaskMessage += "done. Starting simulation... "; - - if (sd.isSpatial()) hasSomeSpatial = true; - - biosimStatusMap.put(task, BiosimulationLog.Status.RUNNING); - RunUtils.drawBreakLine("- -", 25); - logger.info("Beginning Simulation..."); - if (solver instanceof AbstractCompiledSolver abstractCompiledSolver) { - abstractCompiledSolver.runSolver(); - if (solver instanceof ODESolver odeSolver) { - odeSolverResultSet = odeSolver.getODESolverResultSet(); - } else if (solver instanceof GibsonSolver gibsonSolver) { - odeSolverResultSet = gibsonSolver.getStochSolverResultSet(); - } else if (solver instanceof HybridSolver hybridSolver) { - odeSolverResultSet = hybridSolver.getHybridSolverResultSet(); - } else { - String str = "Solver results will not be compatible with CSV format. "; - logger.warn(str); - // keepTempFiles = true; // temp fix for Jasraj - // throw new RuntimeException(str); - } - } else if (solver instanceof AbstractJavaSolver abstractJavaSolver) { - abstractJavaSolver.runSolver(); - if (SolverStatus.SOLVER_FINISHED == abstractJavaSolver.getSolverStatus().getStatus()){ - odeSolverResultSet = ((ODESolver) solver).getODESolverResultSet(); - // must interpolate data for uniform time course which is not supported natively by the Java solvers - org.jlibsedml.Simulation sedmlSim = sedmlRequested.getSimulation(task.getSimulationReference()); - if (sedmlSim instanceof UniformTimeCourse utcSedmlSim) { - odeSolverResultSet = RunUtils.interpolate(odeSolverResultSet, utcSedmlSim); - logTaskMessage += "done. Interpolating... "; - } - } - } else { - // this should actually never happen... - String str = "Unexpected solver: " + kisao + " " + solver + ". "; - biosimStatusMap.put(task, BiosimulationLog.Status.SKIPPED); - throw new RuntimeException(str); + // Build Solver + SolverExecutionRequest executionRequest; + try { + executionRequest = SolverExecutionRequest.createNewExecutionRequest(tempSimulationJob, sd, simTask, utcSim, outputDirForSedml, sedmlLocation); + } catch (IllegalArgumentException iae) { + biosimStatusMap.put(task, new Triplet<>(BiosimulationLog.Status.SKIPPED, Double.NaN, iae)); + logger.warn("VCell does not support requested simulation:", iae); + continue; + } catch (Exception e) { + biosimStatusMap.put(task, new Triplet<>(BiosimulationLog.Status.FAILED, Double.NaN, e)); + Tracer.failure(e, "\"Unable to build solvers\""); + throw new RuntimeException("Unable to build solvers", e); } + String solverLabel = SolverHandler.getSolverDescriptionLabel(sd, kisao); - if (odeSolverResultSet != null) { - // add output functions, if any, to result set - List funcs = so.getOutputFunctionContext().getOutputFunctionsList(); - if (funcs != null) { - for (AnnotatedFunction function : funcs) { - FunctionColumnDescription fcd = null; - String funcName = function.getName(); - Expression funcExp = function.getExpression(); - fcd = new FunctionColumnDescription(funcExp, funcName, null, function.getDisplayName(), true); - odeSolverResultSet.checkFunctionValidity(fcd); - odeSolverResultSet.addFunctionColumn(fcd); - } - } + // Run Solver + biosimStatusMap.put(task, new Triplet<>(BiosimulationLog.Status.RUNNING, 0.0, null)); + BiosimulationLog.instance().updateTaskStatusYml(sedmlLocation, task.getId(), BiosimulationLog.Status.RUNNING, 0, kisao); + for (AbstractTask subTask : this.taskToListOfSubTasksMap.get(task)) { + BiosimulationLog.instance().updateTaskStatusYml(sedmlLocation, subTask.getId(), BiosimulationLog.Status.RUNNING, 0, kisao); } - - if (solver.getSolverStatus().getStatus() == SolverStatus.SOLVER_FINISHED) { - - logTaskMessage += "done. "; - long endTimeTask_ms = System.currentTimeMillis(); - long elapsedTime_ms = endTimeTask_ms - startTimeTask_ms; - int duration_ms = (int) elapsedTime_ms; - - TempSimulation originalSim = tempSimulationJob.getSimulation(); - int simDuration_ms = simDurationMap_ms.get(originalSim); - simDuration_ms += duration_ms; - simDurationMap_ms.put(originalSim, simDuration_ms); - - logger.info("Successful execution ({}s): Model '{}' Task '{}' ({}).", - ((double)elapsedTime_ms)/1000, bioModel.getName(), tempSimulationJobSim.getDescription(), simTask.getSimulation().getName()); - countSuccessfulSimulationRuns++; // we only count the number of simulations (tasks) that succeeded - if (vCellTempSimStatusMap.get(originalSim) != BiosimulationLog.Status.ABORTED && vCellTempSimStatusMap.get(originalSim) != BiosimulationLog.Status.FAILED) { - vCellTempSimStatusMap.put(originalSim, BiosimulationLog.Status.SUCCEEDED); - } - BiosimulationLog.instance().setOutputMessage(sedmlLocation, tempSimulationJobSim.getImportedTaskID(), "task", logTaskMessage); - biosimStatusMap.put(task, BiosimulationLog.Status.SUCCEEDED); - } else { - String error = solver.getSolverStatus().getSimulationMessage().getDisplayMessage(); - solverStatus = solver.getSolverStatus().getStatus(); - String message = String.format("Solver (%s) status: `%s` (%s) ", solver.getClass().getSimpleName(), solver.getSolverStatus().getStatusAsString(), error); - biosimStatusMap.put(task, BiosimulationLog.Status.FAILED); - throw new RuntimeException(message); - } - } catch (Exception e) { - long endTime_ms = System.currentTimeMillis(); - long elapsedTime_ms = endTime_ms - startTimeTask_ms; - String error = String.format("Failed execution:\n\t> Elapsed Time:\t%dms\n\t> Model:\t\t%s\n\t> Task:\t\t\t%s\n\t> Cause:\t\t%s\n\t> Message:\t\t%s", elapsedTime_ms, bioModel.getName(), tempSimulationJobSim.getDescription(), e.getClass().getSimpleName(), e.getMessage()); - logger.error(error); - Tracer.failure(e, error); - - if (tempSimulationJobSim.getImportedTaskID() == null) { - String str = "'null' imported task id, this should never happen. "; - logger.error(str); - logTaskError += str; - } else { - TempSimulation originalSim = tempSimulationJob.getSimulation(); - if (solverStatus == SolverStatus.SOLVER_ABORTED) { - vCellTempSimStatusMap.put(originalSim, BiosimulationLog.Status.ABORTED); - } else { - vCellTempSimStatusMap.put(originalSim, BiosimulationLog.Status.FAILED); - } + Triplet solverResults = this.performSolverExecution(executionRequest, solverLabel); + + // Parse How Execution Went + Triplet exec_result = this.determineSimulationOutcome(executionRequest, solverResults, startTimeTask_ms); + logger.info("Results of `{}`(Model '{}' Task '{}'): {}({}s): .", tempSimulationJobSim.getDescription(), bioModel.getName(), simTask.getSimulation().getName() , exec_result.one.name(), exec_result.two); + + if (exec_result.one == BiosimulationLog.Status.SUCCEEDED) cliLogger.writeDetailedResultList(bioModelBaseName + ", solver: " + solverLabel); + else cliLogger.writeDetailedErrorList(solverResults.three, bioModelBaseName + ", solver: " + solverLabel + ": " + (solverResults.three == null ? "" : solverResults.three.getClass().getSimpleName()) + ": " + logTaskError); + biosimStatusMap.put(task, exec_result); + BiosimulationLog.instance().updateTaskStatusYml(sedmlLocation, task.getId(), exec_result.one, exec_result.two, kisao); + for (AbstractTask subTask : this.taskToListOfSubTasksMap.get(task)) { + BiosimulationLog.instance().updateTaskStatusYml(sedmlLocation, subTask.getId(), exec_result.one, exec_result.two, kisao); } - // CLIUtils.finalStatusUpdate(CLIUtils.Status.FAILED, outDir); - logTaskError += (e.getMessage() != null ? e.getMessage() : error) + ". "; - String type = e.getClass().getSimpleName(); - BiosimulationLog.instance().setOutputMessage(sedmlLocation, tempSimulationJobSim.getImportedTaskID(), "task", logTaskMessage); - BiosimulationLog.instance().setExceptionMessage(sedmlLocation, tempSimulationJobSim.getImportedTaskID(), "task", type, logTaskError); - String sdl = ""; - if (sd != null && sd.getShortDisplayLabel() != null && !sd.getShortDisplayLabel().isEmpty()) { - sdl = sd.getShortDisplayLabel(); - } else { - sdl = kisao; - } - if (logTaskError.contains("Process timed out")) { - if (!bTimeoutFound) { // don't repeat this for each task - String str = logTaskError.substring(0, logTaskError.indexOf("Process timed out")); - str += "Process timed out"; // truncate the rest of the spam - cliLogger.writeDetailedErrorList(e, bioModelBaseName + ", solver: " + sdl + ": " + type + ": " + str); - bTimeoutFound = true; - } + + if (exec_result.one != BiosimulationLog.Status.SUCCEEDED) continue; + // Get Solver Results + MathSymbolMapping mathMapping = (MathSymbolMapping) simTask.getSimulation().getMathDescription().getSourceSymbolMapping(); + SBMLSymbolMapping sbmlMapping = sedmlImporter.getSBMLSymbolMapping(bioModel); + TaskJob taskJob = new TaskJob(task.getId(), tempSimulationJob.getJobIndex()); + if (executionRequest instanceof SolverSpatialExecutionRequest sser) { + logger.info("Collecting spatial results of execution..."); + SpatialSBMLSimResults spatialResults = sser.getResults(mathMapping, sbmlMapping); + this.spatialResults.put(taskJob, spatialResults); + } else if (executionRequest instanceof SolverNonSpatialExecutionRequest snser) { + logger.info("Collecting non-spatial results of execution..."); + NonSpatialSBMLSimResults nonSpatialSimResults = snser.getResults(mathMapping, sbmlMapping); + this.nonSpatialResults.put(taskJob, nonSpatialSimResults); } else { - cliLogger.writeDetailedErrorList(e,bioModelBaseName + ", solver: " + sdl + ": " + type + ": " + logTaskError); - } - } finally { - if (sim_span != null) { - sim_span.close(); + logger.warn("Unexpectedly reached code-block that shouldn't be possible; results may be missing!"); } + simulationJobCount++; + } catch (MathException | DataAccessException e){ + Tracer.failure(e, "Unable to collect Spatial Data"); } - MathSymbolMapping mathMapping = (MathSymbolMapping) simTask.getSimulation().getMathDescription().getSourceSymbolMapping(); - SBMLSymbolMapping sbmlMapping = this.sedmlImporter.getSBMLSymbolMapping(bioModel); - TaskJob taskJob = new TaskJob(task.getId(), tempSimulationJob.getJobIndex()); - if (sd.isSpatial()) { - logger.info("Processing spatial results of execution..."); - SpatialSBMLSimResults spatialResults = new SpatialSBMLSimResults(tempSimulationJob, outputDirForSedml, sbmlMapping, mathMapping); - this.spatialResults.put(taskJob, spatialResults); - keepTempFiles = true; - } else { - logger.info("Processing non-spatial results of execution..."); - NonSpatialSBMLSimResults nonspatialSimResults = new NonSpatialSBMLSimResults(odeSolverResultSet, sbmlMapping, mathMapping); - this.nonSpatialResults.put(taskJob, nonspatialSimResults); - } - - if (!keepTempFiles) { - RunUtils.removeIntermediarySimFiles(outputDirForSedml); - } - simulationJobCount++; - } - for (Map.Entry entry : vCellTempSimStatusMap.entrySet()) { - - TempSimulation tempSimulation = entry.getKey(); - BiosimulationLog.Status status = entry.getValue(); - if (status == BiosimulationLog.Status.RUNNING) { - continue; // if this happens somehow, we just don't write anything - } - AbstractTask task = tempSimulationToTaskMap.get(tempSimulation); - // assert task != null; - double duration_s = simDurationMap_ms.get(tempSimulation)/1000.0; - SolverTaskDescription std = tempSimulation.getSolverTaskDescription(); - SolverDescription sd = std.getSolverDescription(); - String kisao = sd.getKisao(); - BiosimulationLog.instance().updateTaskStatusYml(sedmlLocation, task.getId(), status, duration_s, kisao); - - List children = taskToListOfSubTasksMap.get(task); - for (AbstractTask rt : children) { - BiosimulationLog.instance().updateTaskStatusYml(sedmlLocation, rt.getId(), status, duration_s, kisao); - } - } - bioModelCount++; - RunUtils.drawBreakLine("- -", 25); - } finally { - if (biomodel_span != null){ - biomodel_span.close(); - } - } - } - logger.info("Ran " + simulationJobCount + " simulation jobs for " + bioModelCount + " biomodels."); - if(hasSomeSpatial) { - cliLogger.writeSpatialList(bioModelBaseName); + } + RunUtils.drawBreakLine("- -", 25); + } } + logger.info("Ran " + simulationJobCount + " simulation jobs for " + bioModelList.size() + " biomodels."); RunUtils.drawBreakLine("-", 100); return biosimStatusMap; } - private List preProcessTempSimulations(String sedmlLocation, boolean bSmallMeshOverride, BioModel bioModel, Map vCellTempSimStatusMap, Map simDurationMap_ms) throws PropertyVetoException { + private Triplet performSolverExecution(SolverExecutionRequest ser, String solverDescriptionLabel) { + try (Span sim_span = Tracer.startSpan(Span.ContextType.SIMULATION_RUN, ser.toDisplayString(), Map.of("solverName", solverDescriptionLabel))){ + Triplet solverResult; + ser.progressGeneralLog.append("Beginning simulation..."); + CLISolverListener solverListener = new CLISolverListener(solverDescriptionLabel); + ser.solver.addSolverListener(solverListener); + new Thread(ser.solver::runSolver).start(); // We want to retain control on this thread. + try { + solverResult = solverListener.solverResult.get(); + } catch (Exception e){ + ser.progressGeneralLog.append("Failed!"); + Tracer.failure(e, "Solver \"" + solverDescriptionLabel + "\" failed!"); + return new Triplet<>(Double.NaN, SolverEvent.SOLVER_ABORTED, e); + } + if (!(ser.solver instanceof ODESolverResultsSetReturnable)) logger.info("Solver results will not be compatible with CSV format."); + ser.progressGeneralLog.append(solverResult.two == SolverEvent.SOLVER_FINISHED ? "Done!" : "Failed!").append("\n"); + return solverResult; + } + } + + // Returns the duration in seconds, and the BioSim-style status of the solver + private Triplet determineSimulationOutcome(SolverExecutionRequest ser, Triplet solverResult, long startTimeTask_ms){ + BiosimulationLog.Status solverStatusType; + + String displayMessage = ser.solver.getSolverStatus().getSimulationMessage().getDisplayMessage(); + String message = String.format("Solver (%s) status: `%s`(%s)", ser.solver.getClass().getSimpleName(), SolverStatus.SOLVER_STATUS[solverResult.two], displayMessage); + + int solverStatus = ser.solver.getSolverStatus().getStatus(); + long elapsedTime_ms = System.currentTimeMillis() - startTimeTask_ms; + if (SolverStatus.SOLVER_FINISHED == solverStatus) { + ser.progressGeneralLog.append("Simulation processed successfully "); + this.countSuccessfulSimulationRuns++; // we only count the number of simulations (tasks) that succeeded + solverStatusType = BiosimulationLog.Status.SUCCEEDED; + } else { + ser.progressGeneralLog.append("Simulation processed unsuccessfully "); + solverStatusType = solverStatus == SolverStatus.SOLVER_ABORTED ? + BiosimulationLog.Status.ABORTED: + BiosimulationLog.Status.FAILED; + } + ser.progressGeneralLog.append("(").append(message).append(")\n"); + ser.bioSimLogSetOutputMessage(ser.progressGeneralLog.toString()); + ser.bioSimLogSetOutputMessage(ser.progressErrLog.toString(), solverResult.three); + return new Triplet<>(solverStatusType, ((double) elapsedTime_ms) / 1000.0, solverResult.three); + } + + private List preProcessTempSimulations(String sedmlLocation, boolean bSmallMeshOverride, BioModel bioModel, Map vCellTempSimStatusMap) throws PropertyVetoException { List simJobsList = new ArrayList<>(); - for (TempSimulation tempSimulation : Arrays.stream(bioModel.getSimulations()).map(s -> origSimulationToTempSimulationMap.get(s)).toList()) { + for (TempSimulation tempSimulation : Arrays.stream(bioModel.getSimulations()).map(s -> this.origSimulationToTempSimulationMap.get(s)).toList()) { if (tempSimulation.getImportedTaskID() == null) { continue; // this is a simulation not matching the imported task, so we skip it } - AbstractTask task = tempSimulationToTaskMap.get(tempSimulation); + AbstractTask task = this.tempSimulationToTaskMap.get(tempSimulation); vCellTempSimStatusMap.put(tempSimulation, BiosimulationLog.Status.RUNNING); BiosimulationLog.instance().updateTaskStatusYml(sedmlLocation, task.getId(), BiosimulationLog.Status.RUNNING, 0.0, tempSimulation.getSolverTaskDescription().getSolverDescription().getKisao()); - simDurationMap_ms.put(tempSimulation, 0); if (bSmallMeshOverride && tempSimulation.getMeshSpecification() != null) { int maxSize = 11; @@ -623,6 +524,14 @@ public boolean hasMessages() { return false; } } + + private static String getSolverDescriptionLabel(SolverDescription solverDescription, String kisao) { + if (solverDescription == null) return kisao; + String sdl = solverDescription.getShortDisplayLabel(); + if (sdl == null) return kisao; + if (sdl.isEmpty()) return kisao; + return sdl; + } // // Running python interactively from java code diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/hdf5/BiosimulationsHdf5Writer.java b/vcell-cli/src/main/java/org/vcell/cli/run/hdf5/BiosimulationsHdf5Writer.java index cad25a5d40..a0e41b3066 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/run/hdf5/BiosimulationsHdf5Writer.java +++ b/vcell-cli/src/main/java/org/vcell/cli/run/hdf5/BiosimulationsHdf5Writer.java @@ -37,7 +37,8 @@ public static void writeHdf5(HDF5ExecutionResults hdf5ExecutionResults, File out boolean didFail = false; // Create and open the Hdf5 file - logger.info("Creating HDF5 file `reports.h5` in {}", outDirForCurrentSedml.getAbsolutePath()); + logger.info("Saving all simulation results to Hdf5 file..."); + if (logger.isDebugEnabled()) logger.info("Creating HDF5 file `reports.h5` in {}", outDirForCurrentSedml.getAbsolutePath()); File tempFile = new File(outDirForCurrentSedml, "reports.h5"); try { try (WritableHdfFile hdf5File = HdfFile.write(tempFile.toPath())){ @@ -107,7 +108,7 @@ public static void writeHdf5(HDF5ExecutionResults hdf5ExecutionResults, File out throw new BiosimulationsHdfWriterException(message, e); } } finally { - if (!didFail) logger.info("HDF5 file successfully written to."); + if (!didFail) logger.info("HDF5 writing completed successfully."); } } diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/hdf5/Hdf5DataExtractor.java b/vcell-cli/src/main/java/org/vcell/cli/run/hdf5/Hdf5DataExtractor.java index 55fee6582f..f43d029b53 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/run/hdf5/Hdf5DataExtractor.java +++ b/vcell-cli/src/main/java/org/vcell/cli/run/hdf5/Hdf5DataExtractor.java @@ -9,6 +9,7 @@ import org.vcell.cli.run.results.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.vcell.sbml.vcell.lazy.LazySBMLDataAccessor; import org.vcell.sbml.vcell.lazy.LazySBMLNonSpatialDataAccessor; import org.vcell.sbml.vcell.lazy.LazySBMLSpatialDataAccessor; @@ -45,7 +46,7 @@ public Hdf5DataExtractor(SedML sedml, Map taskToSi * @see NonSpatialResultsConverter ::convertNonspatialResultsToSedmlFormat * @see SpatialResultsConverter ::collectSpatialDatasets */ - public Hdf5DataContainer extractHdf5RelevantData(Map> organizedNonSpatialResults, Map> organizedSpatialResults, boolean isBioSimMode) { + public Hdf5DataContainer extractHdf5RelevantData(Map> organizedNonSpatialResults, Map> organizedSpatialResults, boolean isBioSimMode) { Map> wrappers = new LinkedHashMap<>(); Hdf5DataContainer hdf5FileWrapper = new Hdf5DataContainer(isBioSimMode); Exception nonSpatialException = null, spatialException = null; diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/plotting/PlottingDataExtractor.java b/vcell-cli/src/main/java/org/vcell/cli/run/plotting/PlottingDataExtractor.java index 7c8c48f518..b9fb069004 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/run/plotting/PlottingDataExtractor.java +++ b/vcell-cli/src/main/java/org/vcell/cli/run/plotting/PlottingDataExtractor.java @@ -37,7 +37,7 @@ public PlottingDataExtractor(SedML sedml, String sedmlName){ * @see NonSpatialResultsConverter ::convertNonspatialResultsToSedmlFormat * @see org.vcell.cli.run.results.SpatialResultsConverter ::collectSpatialDatasets */ - public Map> extractPlotRelevantData(Map> organizedNonSpatialResults) { + public Map> extractPlotRelevantData(Map> organizedNonSpatialResults) { Map> plots = new LinkedHashMap<>(); Set xAxisNames = new LinkedHashSet<>(); if (organizedNonSpatialResults.isEmpty()) return plots; @@ -48,7 +48,7 @@ public Map> extractPlotRelevantData(Map< plot.setTitle(requestedPlot.getName()); for (Curve curve : requestedPlot.getListOfCurves()){ - ValueHolder xResults, yResults; + ValueHolder xResults, yResults; BiosimulationLog.instance().updateCurveStatusYml(this.sedmlName, requestedPlot.getId(), curve.getId(), BiosimulationLog.Status.RUNNING); DataGenerator requestedXGenerator = this.sedml.getDataGeneratorWithId(curve.getXDataReference()); DataGenerator requestedYGenerator = this.sedml.getDataGeneratorWithId(curve.getYDataReference()); @@ -76,8 +76,13 @@ public Map> extractPlotRelevantData(Map< for (int i = 0; i < yResults.listOfResultSets.size(); i++){ SBMLDataRecord xLazyResults, yLazyResults; - LazySBMLDataAccessor xAccessor = xResults.listOfResultSets.get(hasSingleXSeries ? 0 : i); - LazySBMLDataAccessor yAccessor = yResults.listOfResultSets.get(i); + LazySBMLDataAccessor xGenericAccessor = xResults.listOfResultSets.get(hasSingleXSeries ? 0 : i); + LazySBMLDataAccessor yGenericAccessor = yResults.listOfResultSets.get(i); + if ( + !(xGenericAccessor instanceof LazySBMLNonSpatialDataAccessor xAccessor) || + !(yGenericAccessor instanceof LazySBMLNonSpatialDataAccessor yAccessor) + ) throw new IllegalArgumentException("Spatial data can not be handled at this time!"); + try { xLazyResults = xAccessor.getData(); yLazyResults = yAccessor.getData(); @@ -123,14 +128,14 @@ private RuntimeException logBeforeThrowing(RuntimeException e, String plotId, St * Basically, some people make track a species that remains constant over all iterations. We want to reduce that to 1 entry * @return a ValueHolder with the simplified sets */ - private static ValueHolder simplifyRedundantSets(ValueHolder startingValues){ + private static ValueHolder simplifyRedundantSets(ValueHolder startingValues){ if (startingValues == null) return null; - Set setOfData = new LinkedHashSet<>(); // Need to preserve order! - for (LazySBMLNonSpatialDataAccessor dataSet : startingValues.listOfResultSets){ + Set setOfData = new LinkedHashSet<>(); // Need to preserve order! + for (LazySBMLDataAccessor dataSet : startingValues.listOfResultSets){ if (setOfData.contains(dataSet)) continue; setOfData.add(dataSet); } - ValueHolder adjustedSets = startingValues.createEmptySetWithSameVCsim(); + ValueHolder adjustedSets = startingValues.createEmptySetWithSameVCsim(); adjustedSets.listOfResultSets.addAll(setOfData); return adjustedSets; } diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/results/NonSpatialResultsConverter.java b/vcell-cli/src/main/java/org/vcell/cli/run/results/NonSpatialResultsConverter.java index e058ee5626..2ca87bd4f8 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/run/results/NonSpatialResultsConverter.java +++ b/vcell-cli/src/main/java/org/vcell/cli/run/results/NonSpatialResultsConverter.java @@ -1,5 +1,6 @@ package org.vcell.cli.run.results; +import cbit.vcell.math.MathException; import cbit.vcell.parser.Expression; import cbit.vcell.parser.ExpressionException; import cbit.vcell.solver.MathOverrides; @@ -17,15 +18,18 @@ import org.vcell.sbml.vcell.lazy.LazySBMLNonSpatialDataAccessor; import org.vcell.sedml.SEDMLImportException; import org.vcell.sedml.log.BiosimulationLog; +import org.vcell.util.DataAccessException; +import java.io.IOException; import java.nio.file.Paths; import java.util.*; +import java.util.stream.Collectors; public class NonSpatialResultsConverter extends ResultsConverter { private final static Logger logger = LogManager.getLogger(NonSpatialResultsConverter.class); - public static Map> organizeNonSpatialResultsBySedmlDataGenerator(SedML sedml, Map nonSpatialResultsHash, Map taskToSimulationMap) throws ExpressionException, SEDMLImportException { - Map> nonSpatialOrganizedResultsMap = new HashMap<>(); + public static Map> organizeNonSpatialResultsBySedmlDataGenerator(SedML sedml, Map nonSpatialResultsHash, Map taskToSimulationMap) throws ExpressionException, SEDMLImportException, MathException, IOException, DataAccessException { + Map> nonSpatialOrganizedResultsMap = new HashMap<>(); if (nonSpatialResultsHash.isEmpty()) return nonSpatialOrganizedResultsMap; for (Output output : ResultsConverter.getValidOutputs(sedml)){ @@ -63,7 +67,7 @@ else if (output instanceof Plot2D plot2D){ } for (DataGenerator dataGen : dataGeneratorsToProcess) { - ValueHolder valueHolder = NonSpatialResultsConverter.getNonSpatialValueHolderForDataGenerator(sedml, dataGen, nonSpatialResultsHash, taskToSimulationMap, maxTimeLength); + ValueHolder valueHolder = NonSpatialResultsConverter.getNonSpatialValueHolderForDataGenerator(sedml, dataGen, nonSpatialResultsHash, taskToSimulationMap, maxTimeLength); if (valueHolder == null) continue; nonSpatialOrganizedResultsMap.put(dataGen, valueHolder); } @@ -73,7 +77,7 @@ else if (output instanceof Plot2D plot2D){ } - public static Map> prepareNonSpatialDataForHdf5(SedML sedml, Map> nonSpatialResultsMapping, + public static Map> prepareNonSpatialDataForHdf5(SedML sedml, Map> nonSpatialResultsMapping, Set allValidDataGenerators, String sedmlLocation, boolean isBioSimMode) { Map> results = new LinkedHashMap<>(); if (nonSpatialResultsMapping.isEmpty()){ @@ -159,11 +163,11 @@ public static Map> prepareNonSpatialDataForHdf5(S return results; } - private static ValueHolder getNonSpatialValueHolderForDataGenerator(SedML sedml, DataGenerator dataGen, + private static ValueHolder getNonSpatialValueHolderForDataGenerator(SedML sedml, DataGenerator dataGen, Map nonSpatialResultsHash, - Map taskToSimulationMap, int padToLength) throws ExpressionException { + Map taskToSimulationMap, int padToLength) throws ExpressionException, MathException, IOException, DataAccessException { if (dataGen == null) throw new IllegalArgumentException("Provided Data Generator can not be null!"); - Map> resultsByVariable = new HashMap<>(); + Map> resultsByVariable = new HashMap<>(); // get the list of variables associated with the data reference for (Variable var : dataGen.getListOfVariables()) { @@ -204,12 +208,12 @@ private static ValueHolder getNonSpatialValueHol if (taskJobs.isEmpty()) continue; boolean resultsAlreadyExist = topLevelTask instanceof RepeatedTask && resultsByVariable.containsKey(var); - ValueHolder individualVarResultsHolder = resultsAlreadyExist ? resultsByVariable.get(var) : + ValueHolder individualVarResultsHolder = resultsAlreadyExist ? resultsByVariable.get(var) : new ValueHolder<>(taskToSimulationMap.get(topLevelTask)); for (TaskJob taskJob : taskJobs) { // Leaving intermediate variables for debugging access NonSpatialSBMLSimResults nonSpatialResults = nonSpatialResultsHash.get(taskJob); - LazySBMLNonSpatialDataAccessor dataAccessor = nonSpatialResults.getSBMLDataAccessor(vcellVarId, utcSim, padToLength); + LazySBMLDataAccessor dataAccessor = nonSpatialResults.getSBMLDataAccessor(vcellVarId, utcSim); individualVarResultsHolder.listOfResultSets.add(dataAccessor); } resultsByVariable.put(var, individualVarResultsHolder); @@ -220,29 +224,39 @@ private static ValueHolder getNonSpatialValueHol String exampleReference = resultsByVariable.keySet().iterator().next().getReference(); int numJobs = resultsByVariable.values().iterator().next().listOfResultSets.size(); - ValueHolder synthesizedResults = new ValueHolder<>(taskToSimulationMap.get(sedml.getTaskWithId(exampleReference))); - SimpleDataGenCalculator calc = new SimpleDataGenCalculator(dataGen); + ValueHolder synthesizedResults = new ValueHolder<>(taskToSimulationMap.get(sedml.getTaskWithId(exampleReference))); + + // We need to efficiently determine what the desired times should be, and there's a variety of odd cases to consider + // (1) We have results by lists of the same variable over all scans. Let's assume the scans don't change length of time, and get desired times per vars + List> desiredTimesList = resultsByVariable.values().stream().map(ValueHolder::getListOfResultSets).map((x)->x.get(0)).map(LazySBMLDataAccessor::getDesiredTimes).toList(); + // (2) We need to verify that the desired times for all vars is the same + List stringRepresentationOfTimes = desiredTimesList.stream().map((x)->String.join(",", x.stream().map(String::valueOf).toList())).distinct().toList(); + //if (1 != stringRepresentationOfTimes.size()) throw new IllegalArgumentException("Times do not match"); // We can't handle variable requested times right now. + // (3) We just get the first value...aka the "only" value + List desiredTimes = desiredTimesList.get(0); // Perform the math! + SimpleDataGenCalculator calc = new SimpleDataGenCalculator(dataGen); for (int jobNum = 0; jobNum < numJobs; jobNum++){ final int finalJobNum = jobNum; // need to finalize to put in lambda. LazySBMLNonSpatialDataAccessor synthesizedLazyDataset = new LazySBMLNonSpatialDataAccessor( ()-> NonSpatialResultsConverter.getSynthesizedDataSet(calc, resultsByVariable, synthesizedResults.vcSimulation, padToLength, finalJobNum), - padToLength + padToLength, + desiredTimes ); synthesizedResults.listOfResultSets.add(synthesizedLazyDataset); } return synthesizedResults; } - private static SBMLDataRecord getSynthesizedDataSet(SimpleDataGenCalculator calc, Map> resultsByVariable, + private static SBMLDataRecord getSynthesizedDataSet(SimpleDataGenCalculator calc, Map> resultsByVariable, Simulation vcSimulation, int finalMaxLengthOfData, int jobNum) throws Exception { double[] postProcessedData = new double[finalMaxLengthOfData]; for (int datumIndex = 0; datumIndex < finalMaxLengthOfData; datumIndex++){ for (Variable var : resultsByVariable.keySet()){ if (jobNum >= resultsByVariable.get(var).listOfResultSets.size()) continue; - ValueHolder nonSpatialValue = resultsByVariable.get(var); - LazySBMLNonSpatialDataAccessor specificJobDataSet = nonSpatialValue.listOfResultSets.get(jobNum); + ValueHolder nonSpatialValue = resultsByVariable.get(var); + LazySBMLDataAccessor specificJobDataSet = nonSpatialValue.listOfResultSets.get(jobNum); double[] lazyData = specificJobDataSet.getData().data(); double datum = datumIndex >= lazyData.length ? Double.NaN : lazyData[datumIndex]; calc.setArgument(var.getId(), datum); diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/results/SpatialResultsConverter.java b/vcell-cli/src/main/java/org/vcell/cli/run/results/SpatialResultsConverter.java index 44cf771ae2..6d12ce08f9 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/run/results/SpatialResultsConverter.java +++ b/vcell-cli/src/main/java/org/vcell/cli/run/results/SpatialResultsConverter.java @@ -25,8 +25,8 @@ public class SpatialResultsConverter extends ResultsConverter { private final static Logger logger = LogManager.getLogger(SpatialResultsConverter.class); - public static Map> organizeSpatialResultsBySedmlDataGenerator(SedML sedml, Map spatialResultsHash, Map taskToSimulationMap) throws ExpressionException, MathException, IOException, ExecutionException, DataAccessException { - Map> spatialOrganizedResultsMap = new HashMap<>(); + public static Map> organizeSpatialResultsBySedmlDataGenerator(SedML sedml, Map spatialResultsHash, Map taskToSimulationMap) throws ExpressionException, MathException, IOException, ExecutionException, DataAccessException { + Map> spatialOrganizedResultsMap = new HashMap<>(); if (spatialResultsHash.isEmpty()) return spatialOrganizedResultsMap; for (Output output : ResultsConverter.getValidOutputs(sedml)){ @@ -52,7 +52,7 @@ else if (output instanceof Plot2D plot2D){ } for (DataGenerator dataGen : dataGeneratorsToProcess) { - ValueHolder valueHolder = SpatialResultsConverter.getSpatialValueHolderForDataGenerator(sedml, dataGen, spatialResultsHash, taskToSimulationMap); + ValueHolder valueHolder = SpatialResultsConverter.getSpatialValueHolderForDataGenerator(sedml, dataGen, spatialResultsHash, taskToSimulationMap); // if (valueHolder == null) continue; // We don't want this, we want nulls to pass through for later processing. spatialOrganizedResultsMap.put(dataGen, valueHolder); } @@ -61,7 +61,7 @@ else if (output instanceof Plot2D plot2D){ return spatialOrganizedResultsMap; } - public static Map> prepareSpatialDataForHdf5(SedML sedml, Map> spatialResultsMapping, + public static Map> prepareSpatialDataForHdf5(SedML sedml, Map> spatialResultsMapping, Set allValidDataGenerators, String sedmlLocation, boolean isBioSimMode) { Map> results = new LinkedHashMap<>(); if (spatialResultsMapping.isEmpty()){ @@ -99,7 +99,7 @@ public static Map> prepareSpatialDataForHdf5(SedM continue; } - List shapes = new LinkedList<>(); + Hdf5SedmlResultsSpatial dataSourceSpatial = new Hdf5SedmlResultsSpatial(); Hdf5SedmlResults hdf5DatasetWrapper = new Hdf5SedmlResults(); @@ -117,7 +117,7 @@ public static Map> prepareSpatialDataForHdf5(SedM for (DataSet dataSet : refinedDataSets){ ValueHolder dataSetValuesSource = dataSetValues.get(dataSet); - + List shapes = new LinkedList<>(); dataSourceSpatial.dataItems.put(report, dataSet, new LinkedList<>()); dataSourceSpatial.scanBounds = dataSetValuesSource.vcSimulation.getMathOverrides().getScanBounds(); dataSourceSpatial.scanParameterNames = dataSetValuesSource.vcSimulation.getMathOverrides().getScannedConstantNames(); @@ -137,7 +137,11 @@ public static Map> prepareSpatialDataForHdf5(SedM if (!(data instanceof LazySBMLSpatialDataAccessor spatialData)) throw new IllegalArgumentException("Non-spatial data somehow got into spatial data!"); dataSourceSpatial.dataItems.get(report, dataSet).add(spatialData); - shapes.add(Integer.toString(data.getFlatSize())); + List subShapes = new LinkedList<>(); + subShapes.add(Integer.toString(data.getDesiredTimes().size())); + subShapes.addAll(data.getSpatialDimensions().stream().map(String::valueOf).filter((x)->!"1".equals(x)).toList()); + if (1 == subShapes.size()) shapes.add(subShapes.get(0)); + shapes.add(1 == subShapes.size() ? subShapes.get(0) : "[" + String.join(",", subShapes) + "]"); } hdf5DatasetWrapper.dataSource = dataSourceSpatial; // Using upcasting @@ -147,7 +151,8 @@ public static Map> prepareSpatialDataForHdf5(SedM hdf5DatasetWrapper.datasetMetadata.sedmlDataSetLabels.add(dataSet.getLabel()); hdf5DatasetWrapper.datasetMetadata.sedmlDataSetNames.add(dataSet.getName()); - hdf5DatasetWrapper.datasetMetadata.sedmlDataSetShapes.add("(" + String.join(", ", shapes)+ ")"); + String shapesStr = "(" + String.join(",", shapes) + ")"; + hdf5DatasetWrapper.datasetMetadata.sedmlDataSetShapes.add(shapesStr); } if (!results.containsKey(report)) results.put(report, new LinkedList<>()); @@ -156,11 +161,11 @@ public static Map> prepareSpatialDataForHdf5(SedM return results; } - private static ValueHolder getSpatialValueHolderForDataGenerator(SedML sedml, DataGenerator dataGen, + private static ValueHolder getSpatialValueHolderForDataGenerator(SedML sedml, DataGenerator dataGen, Map spatialResultsHash, Map taskToSimulationMap) throws ExpressionException, ExecutionException, MathException, IOException, DataAccessException { if (dataGen == null) throw new IllegalArgumentException("Provided Data Generator can not be null!"); - Map> resultsByVariable = new HashMap<>(); + Map> resultsByVariable = new HashMap<>(); int maxLengthOfData = 0; // get the list of variables associated with the data reference @@ -204,12 +209,12 @@ private static ValueHolder getSpatialValueHolderFor if (taskJobs.isEmpty()) continue; boolean resultsAlreadyExist = topLevelTask instanceof RepeatedTask && resultsByVariable.containsKey(var); - ValueHolder individualVarResultsHolder = resultsAlreadyExist ? resultsByVariable.get(var) : + ValueHolder individualVarResultsHolder = resultsAlreadyExist ? resultsByVariable.get(var) : new ValueHolder<>(taskToSimulationMap.get(topLevelTask)); for (TaskJob taskJob : taskJobs) { // Leaving intermediate variables for debugging access SpatialSBMLSimResults spatialResults = spatialResultsHash.get(taskJob); - LazySBMLSpatialDataAccessor dataAccessor = spatialResults.getSBMLDataAccessor(vcellVarId, utcSim); + LazySBMLDataAccessor dataAccessor = spatialResults.getSBMLDataAccessor(vcellVarId, utcSim); individualVarResultsHolder.listOfResultSets.add(dataAccessor); int localMax; if ((localMax = spatialResults.getMaxDataFlatLength()) > maxLengthOfData) maxLengthOfData = localMax; diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/results/ValueHolder.java b/vcell-cli/src/main/java/org/vcell/cli/run/results/ValueHolder.java index 905e68b131..4f55c79386 100644 --- a/vcell-cli/src/main/java/org/vcell/cli/run/results/ValueHolder.java +++ b/vcell-cli/src/main/java/org/vcell/cli/run/results/ValueHolder.java @@ -7,6 +7,9 @@ import java.util.List; public class ValueHolder { + /** + * The list of results, with each entry being a different run (only 1 for a single task, multiple for repeated tasks) + */ public final List listOfResultSets; final Simulation vcSimulation; @@ -32,6 +35,11 @@ public ValueHolder createEmptySetWithSameVCsim(){ return new ValueHolder<>(this.vcSimulation); } + // even though listOfResultsSets is public, the issue is providing a function reference to "string" + public List getListOfResultSets() { + return this.listOfResultSets; + } + /*public int[] getJobCoordinate(int index){ String[] names = vcSimulation.getMathOverrides().getScannedConstantNames(); java.util.Arrays.sort(names); // must do things in a consistent way diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/results/solver/SolverExecutionRequest.java b/vcell-cli/src/main/java/org/vcell/cli/run/results/solver/SolverExecutionRequest.java new file mode 100644 index 0000000000..b7d562259e --- /dev/null +++ b/vcell-cli/src/main/java/org/vcell/cli/run/results/solver/SolverExecutionRequest.java @@ -0,0 +1,63 @@ +package org.vcell.cli.run.results.solver; + +import cbit.vcell.mapping.MathSymbolMapping; +import cbit.vcell.math.MathException; +import cbit.vcell.messaging.server.SimulationTask; +import cbit.vcell.parser.ExpressionException; +import cbit.vcell.solver.SimulationJob; +import cbit.vcell.solver.SolverDescription; +import cbit.vcell.solver.SolverException; +import cbit.vcell.solver.ode.AbstractJavaSolver; +import cbit.vcell.solver.server.Solver; +import cbit.vcell.solver.server.SolverFactory; +import cbit.vcell.solvers.AbstractCompiledSolver; +import org.jlibsedml.UniformTimeCourse; +import org.vcell.sbml.vcell.SBMLSimResults; +import org.vcell.sbml.vcell.SBMLSymbolMapping; +import org.vcell.sedml.log.BiosimulationLog; +import org.vcell.util.DataAccessException; + +import java.io.File; +import java.io.IOException; + +public abstract class SolverExecutionRequest { + public final StringBuilder progressGeneralLog, progressErrLog; + public final Solver solver; + protected final SolverDescription solverDescription; + protected final org.jlibsedml.UniformTimeCourse utcSim; + private final String sedmlLocation; + private final String taskId; + + public static SolverExecutionRequest createNewExecutionRequest(SimulationJob simulationJob, SolverDescription solverDescription, SimulationTask simTask, UniformTimeCourse utcSimRequested, File outputDirForSedml, String sedmlLocation) throws SolverException { + Solver solver = SolverFactory.createSolver(outputDirForSedml, simTask, false); + return solverDescription.isSpatial() ? + new SolverSpatialExecutionRequest(solver, solverDescription, utcSimRequested, simulationJob, outputDirForSedml, sedmlLocation): + new SolverNonSpatialExecutionRequest(solver, solverDescription, utcSimRequested, simulationJob, sedmlLocation); + } + + protected SolverExecutionRequest(SimulationJob simulationJob, Solver solverRequested, SolverDescription solverDescription, UniformTimeCourse utcSimRequested, String sedmlLocation){ + this.progressGeneralLog = new StringBuilder("Building SolverExecutionRequest..."); + this.progressErrLog = new StringBuilder(); + if (!(solverRequested instanceof AbstractJavaSolver || solverRequested instanceof AbstractCompiledSolver)) throw new IllegalArgumentException("Solver provided not accepted"); + this.solver = solverRequested; + this.solverDescription = solverDescription; + this.utcSim = utcSimRequested; + this.sedmlLocation = sedmlLocation; + this.taskId = simulationJob.getSimulation().getImportedTaskID(); + } + + public abstract SBMLSimResults getResults(MathSymbolMapping mathMapping, SBMLSymbolMapping sbmlMapping) throws ExpressionException, MathException, IOException, DataAccessException; + + public void bioSimLogSetOutputMessage(String message){ + BiosimulationLog.instance().setOutputMessage(this.sedmlLocation, this.taskId, "task", message); + } + + public void bioSimLogSetOutputMessage(String message, Exception e){ + if (e == null) this.bioSimLogSetOutputMessage(message); + else BiosimulationLog.instance().setExceptionMessage(this.sedmlLocation, this.taskId, "task", e.getClass().getSimpleName(), message); + } + + public String toDisplayString(){ + return String.format("%s { %s executing task %s @ %s }", this.getClass().getSimpleName(), this.solver.getClass().getSimpleName(), this.taskId, this.sedmlLocation); + } +} diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/results/solver/SolverNonSpatialExecutionRequest.java b/vcell-cli/src/main/java/org/vcell/cli/run/results/solver/SolverNonSpatialExecutionRequest.java new file mode 100644 index 0000000000..70ef24c5e1 --- /dev/null +++ b/vcell-cli/src/main/java/org/vcell/cli/run/results/solver/SolverNonSpatialExecutionRequest.java @@ -0,0 +1,52 @@ +package org.vcell.cli.run.results.solver; + +import cbit.vcell.mapping.MathSymbolMapping; +import cbit.vcell.math.FunctionColumnDescription; +import cbit.vcell.parser.Expression; +import cbit.vcell.parser.ExpressionException; +import cbit.vcell.solver.AnnotatedFunction; +import cbit.vcell.solver.SimulationJob; +import cbit.vcell.solver.SimulationOwner; +import cbit.vcell.solver.SolverDescription; +import cbit.vcell.solver.ode.AbstractJavaSolver; +import cbit.vcell.solver.ode.ODESolverResultSet; +import cbit.vcell.solver.ode.ODESolverResultsSetReturnable; +import cbit.vcell.solver.server.Solver; +import org.jlibsedml.UniformTimeCourse; +import org.vcell.sbml.vcell.NonSpatialSBMLSimResults; +import org.vcell.sbml.vcell.SBMLSymbolMapping; + +import java.util.List; + +public class SolverNonSpatialExecutionRequest extends SolverExecutionRequest { + private final SimulationOwner simulationOwner; + + protected SolverNonSpatialExecutionRequest(Solver requestedSolver, SolverDescription solverDescription, UniformTimeCourse utcSimRequested, SimulationJob simulationJob, String sedmlLocation) { + super(simulationJob, requestedSolver, solverDescription, utcSimRequested, sedmlLocation); + this.simulationOwner = simulationJob.getSimulation().getSimulationOwner(); + this.progressGeneralLog.append("Done!\n"); + } + + @Override + public NonSpatialSBMLSimResults getResults(MathSymbolMapping mathMapping, SBMLSymbolMapping sbmlMapping) throws ExpressionException { + if (!(this.solver instanceof ODESolverResultsSetReturnable OSRSRSolver)) throw new UnsupportedOperationException("Spatial results stored in non-spatial container"); + ODESolverResultSet resultSet = OSRSRSolver.getODESolverResultSet(); + this.processUserDefinedFunctions(resultSet, this.simulationOwner); + return new NonSpatialSBMLSimResults(resultSet, this.utcSim, sbmlMapping, mathMapping, this.solver instanceof AbstractJavaSolver); + } + + private void processUserDefinedFunctions(ODESolverResultSet odeSolverResultSet, SimulationOwner so) throws ExpressionException { + if (odeSolverResultSet == null) return; + // add output functions, if any, to result set + List funcs = so.getOutputFunctionContext().getOutputFunctionsList(); + if (funcs == null) return; + for (AnnotatedFunction function : funcs) { + FunctionColumnDescription fcd; + String funcName = function.getName(); + Expression funcExp = function.getExpression(); + fcd = new FunctionColumnDescription(funcExp, funcName, null, function.getDisplayName(), true); + odeSolverResultSet.checkFunctionValidity(fcd); + odeSolverResultSet.addFunctionColumn(fcd); + } + } +} diff --git a/vcell-cli/src/main/java/org/vcell/cli/run/results/solver/SolverSpatialExecutionRequest.java b/vcell-cli/src/main/java/org/vcell/cli/run/results/solver/SolverSpatialExecutionRequest.java new file mode 100644 index 0000000000..88c68433a3 --- /dev/null +++ b/vcell-cli/src/main/java/org/vcell/cli/run/results/solver/SolverSpatialExecutionRequest.java @@ -0,0 +1,31 @@ +package org.vcell.cli.run.results.solver; + +import cbit.vcell.mapping.MathSymbolMapping; +import cbit.vcell.math.MathException; +import cbit.vcell.solver.SimulationJob; +import cbit.vcell.solver.SolverDescription; +import cbit.vcell.solver.server.Solver; +import org.jlibsedml.UniformTimeCourse; +import org.vcell.sbml.vcell.SpatialSBMLSimResults; +import org.vcell.sbml.vcell.SBMLSymbolMapping; +import org.vcell.util.DataAccessException; + +import java.io.File; +import java.io.IOException; + +public class SolverSpatialExecutionRequest extends SolverExecutionRequest { + private final SimulationJob simulationJob; + private final File solverResultsDir; + + protected SolverSpatialExecutionRequest(Solver solverRequested, SolverDescription solverDescription, UniformTimeCourse utcSimRequested, SimulationJob simulationJob, File solverResultsDir, String sedmlLocation) { + super(simulationJob, solverRequested, solverDescription, utcSimRequested, sedmlLocation); + this.simulationJob = simulationJob; + this.solverResultsDir = solverResultsDir; + this.progressGeneralLog.append("Done!\n"); + } + + @Override + public SpatialSBMLSimResults getResults(MathSymbolMapping mathMapping, SBMLSymbolMapping sbmlMapping) throws MathException, IOException, DataAccessException { + return new SpatialSBMLSimResults(this.simulationJob, this.solverResultsDir, sbmlMapping, mathMapping); + } +} diff --git a/vcell-cli/src/test/java/org/vcell/cli/run/BSTSBasedOmexExecTest.java b/vcell-cli/src/test/java/org/vcell/cli/run/BSTSBasedOmexExecTest.java index 56a8261b0a..e1c1f61a09 100644 --- a/vcell-cli/src/test/java/org/vcell/cli/run/BSTSBasedOmexExecTest.java +++ b/vcell-cli/src/test/java/org/vcell/cli/run/BSTSBasedOmexExecTest.java @@ -79,7 +79,7 @@ public void writeDetailedResultList(String message) { System.out.println("writeDetailedResultList(): " + message); Tracer.success("writeDetailedResultList(): " + message); } - @Override + public void writeSpatialList(String message) { System.out.println("writeSpatialList(): " + message); Tracer.success("writeSpatialList(): " + message); diff --git a/vcell-cli/src/test/java/org/vcell/cli/run/QuantOmexExecTest.java b/vcell-cli/src/test/java/org/vcell/cli/run/QuantOmexExecTest.java index 73ae90ffdb..e516d51ebc 100644 --- a/vcell-cli/src/test/java/org/vcell/cli/run/QuantOmexExecTest.java +++ b/vcell-cli/src/test/java/org/vcell/cli/run/QuantOmexExecTest.java @@ -117,7 +117,7 @@ public void writeDetailedResultList(String message) { System.out.println("writeDetailedResultList(): " + message); Tracer.log("writeDetailedResultList(): "+message); } - @Override + public void writeSpatialList(String message) { System.out.println("writeSpatialList(): " + message); Tracer.log("writeSpatialList(): "+message); diff --git a/vcell-cli/src/test/java/org/vcell/cli/run/SpatialExecTest.java b/vcell-cli/src/test/java/org/vcell/cli/run/SpatialExecTest.java index 55ca4a5184..2fde871887 100644 --- a/vcell-cli/src/test/java/org/vcell/cli/run/SpatialExecTest.java +++ b/vcell-cli/src/test/java/org/vcell/cli/run/SpatialExecTest.java @@ -123,7 +123,7 @@ public void writeDetailedResultList(String message) { System.out.println("writeDetailedResultList(): " + message); Tracer.log("writeDetailedResultList(): "+message); } - @Override + public void writeSpatialList(String message) { System.out.println("writeSpatialList(): " + message); Tracer.log("writeSpatialList(): "+message); diff --git a/vcell-cli/src/test/java/org/vcell/cli/run/hdf5/BiosimulationsHdf5WriterTest.java b/vcell-cli/src/test/java/org/vcell/cli/run/hdf5/BiosimulationsHdf5WriterTest.java index 70c52fb3d8..2c8c72a638 100644 --- a/vcell-cli/src/test/java/org/vcell/cli/run/hdf5/BiosimulationsHdf5WriterTest.java +++ b/vcell-cli/src/test/java/org/vcell/cli/run/hdf5/BiosimulationsHdf5WriterTest.java @@ -63,11 +63,11 @@ public static HDF5ExecutionResults createExampleData() { Callable row_S0_2_callable = () -> new SBMLDataRecord(row_S0_2, List.of(row_S0_2.length), null); Callable row_S1_callable = () -> new SBMLDataRecord(row_S1, List.of(row_S1.length), null); Callable row_t_callable = () -> new SBMLDataRecord(row_t, List.of(row_t.length), null); - LazySBMLNonSpatialDataAccessor lazy_S0_0 = new LazySBMLNonSpatialDataAccessor(row_S0_0_callable, row_S0_0.length); - LazySBMLNonSpatialDataAccessor lazy_S0_1 = new LazySBMLNonSpatialDataAccessor(row_S0_1_callable, row_S0_1.length); - LazySBMLNonSpatialDataAccessor lazy_S0_2 = new LazySBMLNonSpatialDataAccessor(row_S0_2_callable, row_S0_2.length); - LazySBMLNonSpatialDataAccessor lazy_S1 = new LazySBMLNonSpatialDataAccessor(row_S1_callable, row_S1.length); - LazySBMLNonSpatialDataAccessor lazy_t = new LazySBMLNonSpatialDataAccessor(row_t_callable, row_t.length); + LazySBMLNonSpatialDataAccessor lazy_S0_0 = new LazySBMLNonSpatialDataAccessor(row_S0_0_callable, row_S0_0.length, Arrays.stream(row_t).boxed().toList()); + LazySBMLNonSpatialDataAccessor lazy_S0_1 = new LazySBMLNonSpatialDataAccessor(row_S0_1_callable, row_S0_1.length, Arrays.stream(row_t).boxed().toList()); + LazySBMLNonSpatialDataAccessor lazy_S0_2 = new LazySBMLNonSpatialDataAccessor(row_S0_2_callable, row_S0_2.length, Arrays.stream(row_t).boxed().toList()); + LazySBMLNonSpatialDataAccessor lazy_S1 = new LazySBMLNonSpatialDataAccessor(row_S1_callable, row_S1.length, Arrays.stream(row_t).boxed().toList()); + LazySBMLNonSpatialDataAccessor lazy_t = new LazySBMLNonSpatialDataAccessor(row_t_callable, row_t.length, Arrays.stream(row_t).boxed().toList()); Hdf5SedmlResults plotDatasetWrapper = new Hdf5SedmlResults(); plotDatasetWrapper.datasetMetadata = plotMetadata; diff --git a/vcell-core/src/main/java/cbit/vcell/export/server/JhdfUtils.java b/vcell-core/src/main/java/cbit/vcell/export/server/JhdfUtils.java index 0de0a255dc..ccd1580154 100644 --- a/vcell-core/src/main/java/cbit/vcell/export/server/JhdfUtils.java +++ b/vcell-core/src/main/java/cbit/vcell/export/server/JhdfUtils.java @@ -165,7 +165,8 @@ public static void putAttribute(WritableNode node, String name, List val String[] paddedValues = values.stream().map(s -> s == null ? "" : s).toArray(String[]::new); node.putAttribute(name, paddedValues); } else { - node.putAttribute(name, values.toArray(new String[0])); + String[] paddedValues = values.toArray(String[]::new); + node.putAttribute(name, paddedValues); } } diff --git a/vcell-core/src/main/java/cbit/vcell/math/RowColumnResultSet.java b/vcell-core/src/main/java/cbit/vcell/math/RowColumnResultSet.java index 365014a940..c3af0a240d 100644 --- a/vcell-core/src/main/java/cbit/vcell/math/RowColumnResultSet.java +++ b/vcell-core/src/main/java/cbit/vcell/math/RowColumnResultSet.java @@ -34,128 +34,144 @@ /** * Insert the class' description here. * Creation date: (8/19/2000 8:59:02 PM) + * * @author: John Wagner */ @SuppressWarnings("serial") public class RowColumnResultSet implements java.io.Serializable { - private Vector fieldDataColumnDescriptions = new Vector<>(); - private Vector fieldFunctionColumnDescriptions = new Vector<>(); - private ArrayList fieldValues = new ArrayList<>(); // vector of rows (each row is a double[]) - protected transient java.beans.PropertyChangeSupport propertyChange; - private ColumnDescription[] fieldColumnDescriptions = null; + private static final Logger lg = LogManager.getLogger(RowColumnResultSet.class); + private Vector dataColumnDescriptions; + private Vector functionColumnDescriptions; + private ArrayList values; // vector of rows (each row is a double[]) + private ColumnDescription[] columnDescriptions; + + protected transient java.beans.PropertyChangeSupport propertyChange; private transient VariableSymbolTable resultSetSymbolTableWithFunction = null; private transient VariableSymbolTable resultSetSymbolTableWithoutFunction = null; - private static final Logger lg = LogManager.getLogger(RowColumnResultSet.class); + /** - * construct empty, add columns via {@link #addDataColumn(ColumnDescription)} et. al. after creation + * construct empty, add columns via {@link #addDataColumn(ColumnDescription)} et. al. after creation */ - public RowColumnResultSet(){ + public RowColumnResultSet() { + this(new Vector<>(), new Vector<>(), new ArrayList<>(), null); + } + + public RowColumnResultSet(RowColumnResultSet copyThisRowColumnResultSet) { + this(new Vector<>(copyThisRowColumnResultSet.dataColumnDescriptions), + new Vector<>(copyThisRowColumnResultSet.functionColumnDescriptions), + new ArrayList<>(copyThisRowColumnResultSet.values), + null); } - public RowColumnResultSet(RowColumnResultSet copyThisRowColumnResultSet){ - this.fieldDataColumnDescriptions = new Vector<>(copyThisRowColumnResultSet.fieldDataColumnDescriptions); - this.fieldFunctionColumnDescriptions = new Vector<>(copyThisRowColumnResultSet.fieldFunctionColumnDescriptions); - this.fieldValues = new ArrayList<>(copyThisRowColumnResultSet.fieldValues); + private RowColumnResultSet(Vector dataColumnDescriptions, + Vector functionColumnDescriptions, + ArrayList values, + ColumnDescription[] columnDescriptions) { + this.dataColumnDescriptions = dataColumnDescriptions; + this.functionColumnDescriptions = functionColumnDescriptions; + this.values = values; + this.columnDescriptions = columnDescriptions; } public enum DuplicateMode { CopyValues, ZeroInitialize } + public static RowColumnResultSet deepCopy(RowColumnResultSet original, DuplicateMode mode) { RowColumnResultSet copy = new RowColumnResultSet(); - copy.fieldDataColumnDescriptions = new Vector<>(original.fieldDataColumnDescriptions); - copy.fieldFunctionColumnDescriptions = new Vector<>(original.fieldFunctionColumnDescriptions); - copy.fieldValues = new ArrayList<>(); - for (double[] originalRow : original.fieldValues) { + copy.dataColumnDescriptions = new Vector<>(original.dataColumnDescriptions); + copy.functionColumnDescriptions = new Vector<>(original.functionColumnDescriptions); + copy.values = new ArrayList<>(); + for (double[] originalRow : original.values) { double[] copyRow = new double[originalRow.length]; - if(mode == DuplicateMode.CopyValues) { + if (mode == DuplicateMode.CopyValues) { System.arraycopy(originalRow, 0, copyRow, 0, originalRow.length); } - copy.fieldValues.add(copyRow); + copy.values.add(copyRow); } - return copy; } /** * SimpleODEData constructor comment. - * JMW : THIS NEEDS TO BE FIXED...THIS CONSTRUCTOR SHOULD NOT - * BE DOING ANY COLUMN CONSTRUCTION AT ALL! + * JMW : THIS NEEDS TO BE FIXED...THIS CONSTRUCTOR SHOULD NOT + * BE DOING ANY COLUMN CONSTRUCTION AT ALL! */ - public RowColumnResultSet(String[] dataColumnNames){ - for(int i = 0; i < dataColumnNames.length; i++){ - addDataColumn(new ODESolverResultSetColumnDescription(dataColumnNames[i])); + public RowColumnResultSet(String[] dataColumnNames) { + this(); + for (String dataColumnName : dataColumnNames) { + this.addDataColumn(new ODESolverResultSetColumnDescription(dataColumnName)); } } /** * getVariableNames method comment. - * THIS WILL LATER BE DELETED PROBABLY...THE COLUMNS - * WILL BE SPECIFIED AT CONSTRUCTION TIME! + * THIS WILL LATER BE DELETED PROBABLY...THE COLUMNS + * WILL BE SPECIFIED AT CONSTRUCTION TIME! */ - public final void addDataColumn(ColumnDescription columnDescription){ + public final void addDataColumn(ColumnDescription columnDescription) { // cbit.util.Assertion.assert(getRowCount() == 0); - ColumnDescription[] oldValue = fieldColumnDescriptions; - fieldDataColumnDescriptions.addElement(columnDescription); + ColumnDescription[] oldValue = this.columnDescriptions; + this.dataColumnDescriptions.addElement(columnDescription); - fieldColumnDescriptions = null; - resultSetSymbolTableWithFunction = null; - resultSetSymbolTableWithoutFunction = null; + this.columnDescriptions = null; + this.resultSetSymbolTableWithFunction = null; + this.resultSetSymbolTableWithoutFunction = null; - if(getPropertyChange().getPropertyChangeListeners().length > 0){ - firePropertyChange("columnDescriptions", oldValue, getColumnDescriptions()); + if (this.getPropertyChange().getPropertyChangeListeners().length > 0) { + this.firePropertyChange("columnDescriptions", oldValue, this.getColumnDescriptions()); } } /** * getVariableNames method comment. - * THIS WILL LATER BE DELETED PROBABLY...THE COLUMNS - * WILL BE SPECIFIED AT CONSTRUCTION TIME! - */ - public final void addFunctionColumn(FunctionColumnDescription functionColumnDescription) throws ExpressionException{ - ColumnDescription[] oldValue = fieldColumnDescriptions; - addFunctionColumnInternal(functionColumnDescription); - if(getPropertyChange().getPropertyChangeListeners().length > 0){ - firePropertyChange("columnDescriptions", oldValue, getColumnDescriptions()); + * THIS WILL LATER BE DELETED PROBABLY...THE COLUMNS + * WILL BE SPECIFIED AT CONSTRUCTION TIME! + */ + public final void addFunctionColumn(FunctionColumnDescription functionColumnDescription) throws ExpressionException { + ColumnDescription[] oldValue = this.columnDescriptions; + this.addFunctionColumnInternal(functionColumnDescription); + if (this.getPropertyChange().getPropertyChangeListeners().length > 0) { + this.firePropertyChange("columnDescriptions", oldValue, this.getColumnDescriptions()); } } /** * getVariableNames method comment. - * THIS WILL LATER BE DELETED PROBABLY...THE COLUMNS - * WILL BE SPECIFIED AT CONSTRUCTION TIME! + * THIS WILL LATER BE DELETED PROBABLY...THE COLUMNS + * WILL BE SPECIFIED AT CONSTRUCTION TIME! */ - private final void addFunctionColumnInternal(FunctionColumnDescription functionColumnDescription) throws ExpressionException{ + private void addFunctionColumnInternal(FunctionColumnDescription functionColumnDescription) throws ExpressionException { // // bind and substitute functions (resulting in expressions of only data columns). // - functionColumnDescription.getExpression().bindExpression(getResultSetSymbolTableWithFunction()); - Expression exp1 = MathUtilities.substituteFunctions(functionColumnDescription.getExpression(), getResultSetSymbolTableWithFunction()); + functionColumnDescription.getExpression().bindExpression(this.getResultSetSymbolTableWithFunction()); + Expression exp1 = MathUtilities.substituteFunctions(functionColumnDescription.getExpression(), this.getResultSetSymbolTableWithFunction()); functionColumnDescription.setExpression(exp1.flatten()); // // didn't throw a binding exception, add to result set. // - fieldFunctionColumnDescriptions.addElement(functionColumnDescription); + this.functionColumnDescriptions.addElement(functionColumnDescription); Domain domain = null; //TODO domain Function func = new Function(functionColumnDescription.getName(), new Expression(functionColumnDescription.getExpression()), domain); - getResultSetSymbolTableWithFunction().addVar(func); - func.setIndex(getColumnDescriptionsCount() - 1); + this.getResultSetSymbolTableWithFunction().addVar(func); + func.setIndex(this.getColumnDescriptionsCount() - 1); - fieldColumnDescriptions = null; + this.columnDescriptions = null; } /** * The addPropertyChangeListener method was generated to support the propertyChange field. */ - public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener listener){ - getPropertyChange().addPropertyChangeListener(listener); + public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener listener) { + this.getPropertyChange().addPropertyChangeListener(listener); } /** @@ -163,62 +179,57 @@ public synchronized void addPropertyChangeListener(java.beans.PropertyChangeList */ public synchronized void addRow(double[] values) { // cbit.util.Assertion.assert(values.length == getDataColumnCount()); - int dataColumnCount = getDataColumnCount(); + int dataColumnCount = this.getDataColumnCount(); double[] v = new double[dataColumnCount]; - if(values.length != dataColumnCount) { + if (values.length != dataColumnCount) { throw new RuntimeException("number of values in row is not equal to number of columns"); } System.arraycopy(values, 0, v, 0, dataColumnCount); - fieldValues.add(v); + this.values.add(v); } /** * getVariableNames method comment. */ - private double calculateErrorFactor(int t, double a[], double b[], double c[], double scale[]){ + private double calculateErrorFactor(int t, double[] a, double[] b, double[] c, double[] scale) { double errorFactor = 0.0; // we need to average coming into the point otherwise funny things happen in systems close to equilibrium situations double c_minus_a_t = (c[t] - a[t]) / scale[t]; double b_minus_a_t = (b[t] - a[t]) / scale[t]; double c_minus_a_t_2 = c_minus_a_t * c_minus_a_t; - if(c[t] == b[t]){ - return 1.0; - } - // - for(int i = 0; i < getDataColumnCount(); i++){ - if(i != t){ - double c_minus_a_y = (c[i] - a[i]) / scale[i]; - double F = Math.abs((b_minus_a_t) * (c_minus_a_y) - ((b[i] - a[i]) / scale[i]) * (c_minus_a_t)) / - (c_minus_a_t_2 + c_minus_a_y * c_minus_a_y); - errorFactor = Math.max(errorFactor, F); - } + if (c[t] == b[t]) return 1.0; + + // dataColumnDescriptions + for (int i = 0; i < this.getDataColumnCount(); i++) { + if (i == t) continue; + double c_minus_a_y = (c[i] - a[i]) / scale[i]; + double F = Math.abs((b_minus_a_t) * (c_minus_a_y) - ((b[i] - a[i]) / scale[i]) * (c_minus_a_t)) / + (c_minus_a_t_2 + c_minus_a_y * c_minus_a_y); + errorFactor = Math.max(errorFactor, F); } return errorFactor; } /** - * checkFunctionValidity method - * This method is used to check if a user defined function expression is valid. - * Takes a FunctionColumnDescription as argument. It substitutes the functions and binds the expression of the new function - * to check the functions validity. If it is not valid, it throws an ExpressionException, which is caught and handled by - * by the addFunction method in ODESolverPlotSpecificationPanel. + * checkFunctionValidity method + * This method is used to check if a user defined function expression is valid. + * Takes a FunctionColumnDescription as argument. It substitutes the functions and binds the expression of the new function + * to check the functions validity. If it is not valid, it throws an ExpressionException, which is caught and handled by + * by the addFunction method in ODESolverPlotSpecificationPanel. */ - public void checkFunctionValidity(FunctionColumnDescription fcd) throws ExpressionException{ - double[] values = null; - if(getRowCount() > 0){ - Expression exp = ((FunctionColumnDescription) fcd).getExpression(); - // - // must rebind expression due to transient nature of expression binding (see ASTIdNode.symbolTableEntry) - // - exp.bindExpression(getResultSetSymbolTableWithFunction()); - Expression exp1 = MathUtilities.substituteFunctions(exp, getResultSetSymbolTableWithFunction()); + public void checkFunctionValidity(FunctionColumnDescription fcd) throws ExpressionException { - values = new double[getRowCount()]; - for(int r = 0; r < getRowCount(); r++){ - values[r] = exp1.evaluateVector(getRow(r)); - } + if (this.getRowCount() <= 0) return; + Expression exp = fcd.getExpression(); + // + // must rebind expression due to transient nature of expression binding (see ASTIdNode.symbolTableEntry) + // + exp.bindExpression(this.getResultSetSymbolTableWithFunction()); + Expression exp1 = MathUtilities.substituteFunctions(exp, this.getResultSetSymbolTableWithFunction()); + for (double[] row : this.getRows()) { + exp1.evaluateVector(row); } } @@ -226,27 +237,25 @@ public void checkFunctionValidity(FunctionColumnDescription fcd) throws Expressi /** * Insert the method's description here. * Creation date: (2/19/2003 4:32:00 PM) + * * @return cbit.vcell.parser.SymbolTable */ - private VariableSymbolTable createResultSetSymbolTable(boolean bIncludeFunctions){ + private VariableSymbolTable createResultSetSymbolTable(boolean bIncludeFunctions) { // // create symbol table for binding expression against data columns and functions (of data columns) // VariableSymbolTable resultSetSymbolTable = new VariableSymbolTable(); - for(int i = 0; i < getColumnDescriptionsCount(); i++){ - ColumnDescription colDesc = getColumnDescriptions(i); - if(colDesc instanceof ODESolverResultSetColumnDescription){ - Domain domain = null; - VolVariable vVar = new VolVariable(colDesc.getName(), domain); - vVar.setIndex(i); - resultSetSymbolTable.addVar(vVar); - } else if(bIncludeFunctions && colDesc instanceof FunctionColumnDescription){ - FunctionColumnDescription funcColDesc = (FunctionColumnDescription) colDesc; - Domain domain = null; - Function func = new Function(funcColDesc.getName(), new Expression(funcColDesc.getExpression()), domain); - func.setIndex(i); - resultSetSymbolTable.addVar(func); - } + for (int i = 0; i < this.getColumnDescriptionsCount(); i++) { + ColumnDescription colDesc = this.getColumnDescriptions(i); + boolean isValidColumnDesc = colDesc instanceof ODESolverResultSetColumnDescription + || (colDesc instanceof FunctionColumnDescription && bIncludeFunctions); + if (!isValidColumnDesc) continue; + Domain domain = null; + Variable var = colDesc instanceof FunctionColumnDescription funcColDesc ? + new Function(funcColDesc.getName(), new Expression(funcColDesc.getExpression()), domain): + new VolVariable(colDesc.getName(), domain); + var.setIndex(i); + resultSetSymbolTable.addVar(var); } return resultSetSymbolTable; } @@ -254,53 +263,43 @@ private VariableSymbolTable createResultSetSymbolTable(boolean bIncludeFunctions /** * getVariableNames method comment. - * If column is empty, return null or an empty array? - * For now, null... - */ - public synchronized double[] extractColumn(int c) throws ExpressionException{ - double[] values = null; - if(getRowCount() > 0){ - ColumnDescription colDescription = getColumnDescriptions(c); - if(colDescription instanceof FunctionColumnDescription){ - Expression exp = ((FunctionColumnDescription) colDescription).getExpression(); - // - // must rebind expression due to transient nature of expression binding (see ASTIdNode.symbolTableEntry) - // - exp.bindExpression(getResultSetSymbolTableWithoutFunction()); - - values = new double[getRowCount()]; - for(int r = 0; r < getRowCount(); r++){ - try { - values[r] = exp.evaluateVector(getRow(r)); - } catch(DivideByZeroException e){ - lg.error("divide by zero, setting value to NaN: exp = '" + exp.infix() + "'", e); - values[r] = Double.NaN; - } catch(FunctionDomainException e){ - lg.error("function domain exception, setting value to NaN: exp = '" + exp.infix() + "'", e); - values[r] = Double.NaN; - } - } - } else { - values = new double[getRowCount()]; - for(int r = 0; r < getRowCount(); r++){ - values[r] = getRow(r)[c]; - } + * If column is empty, return null or an empty array? + * For now, null... + */ + public synchronized double[] extractColumn(int c) throws ExpressionException { + if (this.getRowCount() <= 0) return null; + double[] values = new double[this.getRowCount()]; + Expression exp; + ColumnDescription colDesc = this.getColumnDescriptions(c); + if (colDesc instanceof FunctionColumnDescription funcColDesc){ + exp = funcColDesc.getExpression(); + exp.bindExpression(this.getResultSetSymbolTableWithoutFunction()); // must rebind expression due to transient nature of expression binding (see ASTIdNode.symbolTableEntry) + } else exp = null; + for (int r = 0; r < this.getRowCount(); r++) { + try { + values[r] = !(colDesc instanceof FunctionColumnDescription) ? this.getRow(r)[c] : exp.evaluateVector(this.getRow(r)); + } catch (ExpressionException e) { + String errMsg = String.format("%s encountered; setting value to NaN: exp = `%s`", e.getClass().getSimpleName(), exp.infix()); + lg.warn(errMsg, e); + values[r] = Double.NaN; } } - return (values); + return values; } /** - * getVariableNames method comment. + * Attempts to find the index of the column with the provided name + * + * @param columnName the name to look for + * @return the index of the desired column, or -1 if it's not found */ - public int findColumn(String columnName){ - for(int i = 0; i < getColumnDescriptionsCount(); i++){ - if(columnName.equals(getColumnDescriptions(i).getName())) return (i); - } - if(lg.isDebugEnabled()){ - lg.debug("ODEIntegratorResultSet.findColumn() COULD NOT FIND : " + columnName); + public int findColumn(String columnName) { + for (int i = 0; i < this.getColumnDescriptionsCount(); i++) { + if (columnName.equals(this.getColumnDescriptions(i).getName())) return (i); } + lg.debug("Could not find `{}` in results set.", columnName); + return -1; } @@ -308,57 +307,56 @@ public int findColumn(String columnName){ /** * The firePropertyChange method was generated to support the propertyChange field. */ - public void firePropertyChange(java.beans.PropertyChangeEvent evt){ - getPropertyChange().firePropertyChange(evt); + public void firePropertyChange(java.beans.PropertyChangeEvent evt) { + this.getPropertyChange().firePropertyChange(evt); } /** * The firePropertyChange method was generated to support the propertyChange field. */ - public void firePropertyChange(java.lang.String propertyName, int oldValue, int newValue){ - getPropertyChange().firePropertyChange(propertyName, oldValue, newValue); + public void firePropertyChange(java.lang.String propertyName, int oldValue, int newValue) { + this.getPropertyChange().firePropertyChange(propertyName, oldValue, newValue); } /** * The firePropertyChange method was generated to support the propertyChange field. */ - public void firePropertyChange(java.lang.String propertyName, java.lang.Object oldValue, java.lang.Object newValue){ - getPropertyChange().firePropertyChange(propertyName, oldValue, newValue); + public void firePropertyChange(java.lang.String propertyName, java.lang.Object oldValue, java.lang.Object newValue) { + this.getPropertyChange().firePropertyChange(propertyName, oldValue, newValue); } /** * The firePropertyChange method was generated to support the propertyChange field. */ - public void firePropertyChange(java.lang.String propertyName, boolean oldValue, boolean newValue){ - getPropertyChange().firePropertyChange(propertyName, oldValue, newValue); + public void firePropertyChange(java.lang.String propertyName, boolean oldValue, boolean newValue) { + this.getPropertyChange().firePropertyChange(propertyName, oldValue, newValue); } - public final ColumnDescription[] getColumnDescriptions(){ - if(fieldColumnDescriptions == null){ - fieldColumnDescriptions = new ColumnDescription[getDataColumnCount() + getFunctionColumnCount()]; - int index = 0; - for(int i = 0; i < fieldDataColumnDescriptions.size(); i++){ - fieldColumnDescriptions[index++] = fieldDataColumnDescriptions.elementAt(i); - } - for(int i = 0; i < fieldFunctionColumnDescriptions.size(); i++){ - fieldColumnDescriptions[index++] = fieldFunctionColumnDescriptions.elementAt(i); - } + public final ColumnDescription[] getColumnDescriptions() { + if (this.columnDescriptions != null) return this.columnDescriptions; + this.columnDescriptions = new ColumnDescription[this.getDataColumnCount() + this.getFunctionColumnCount()]; + int index = 0; + for (int i = 0; i < this.dataColumnDescriptions.size(); i++) { + this.columnDescriptions[index++] = this.dataColumnDescriptions.elementAt(i); } - return fieldColumnDescriptions; + for (int i = 0; i < this.functionColumnDescriptions.size(); i++) { + this.columnDescriptions[index++] = this.functionColumnDescriptions.elementAt(i); + } + return this.columnDescriptions; } - public ColumnDescription getColumnDescriptions(int index){ - if(index < fieldDataColumnDescriptions.size()){ - return fieldDataColumnDescriptions.get(index); - } else if(index < getColumnDescriptionsCount()){ - return fieldFunctionColumnDescriptions.get(index - fieldDataColumnDescriptions.size()); + public ColumnDescription getColumnDescriptions(int index) { + if (index < this.dataColumnDescriptions.size()) { + return this.dataColumnDescriptions.get(index); + } else if (index < this.getColumnDescriptionsCount()) { + return this.functionColumnDescriptions.get(index - this.dataColumnDescriptions.size()); } else { - throw new ArrayIndexOutOfBoundsException("RowColumnResultSet:getColumnDescriptions(int index), index=" + index + " total count=" + getColumnDescriptionsCount()); + throw new ArrayIndexOutOfBoundsException("RowColumnResultSet:getColumnDescriptions(int index), index=" + index + " total count=" + this.getColumnDescriptionsCount()); } } @@ -366,120 +364,115 @@ public ColumnDescription getColumnDescriptions(int index){ /** * Insert the method's description here. * Creation date: (1/16/2003 11:48:56 AM) + * * @return int */ - public int getColumnDescriptionsCount(){ - return fieldDataColumnDescriptions.size() + fieldFunctionColumnDescriptions.size(); + public int getColumnDescriptionsCount() { + return this.dataColumnDescriptions.size() + this.functionColumnDescriptions.size(); } /** * Insert the method's description here. * Creation date: (1/9/2003 2:06:44 PM) + * * @return int */ - public int getDataColumnCount(){ - return fieldDataColumnDescriptions.size(); + public int getDataColumnCount() { + return this.dataColumnDescriptions.size(); } /** * getVariableNames method comment. */ - public ColumnDescription[] getDataColumnDescriptions(){ - return this.fieldDataColumnDescriptions.toArray(ColumnDescription[]::new); + public ColumnDescription[] getDataColumnDescriptions() { + return this.dataColumnDescriptions.toArray(ColumnDescription[]::new); } /** * Insert the method's description here. * Creation date: (1/9/2003 2:06:44 PM) + * * @return int */ - public int getFunctionColumnCount(){ - return this.fieldFunctionColumnDescriptions.size(); + public int getFunctionColumnCount() { + return this.functionColumnDescriptions.size(); } /** * getVariableNames method comment. */ - public FunctionColumnDescription[] getFunctionColumnDescriptions(){ - return this.fieldFunctionColumnDescriptions.toArray(FunctionColumnDescription[]::new); + public FunctionColumnDescription[] getFunctionColumnDescriptions() { + return this.functionColumnDescriptions.toArray(FunctionColumnDescription[]::new); } /** * Accessor for the propertyChange field. */ - protected java.beans.PropertyChangeSupport getPropertyChange(){ - if(propertyChange == null){ - propertyChange = new java.beans.PropertyChangeSupport(this); - } - ; - return propertyChange; + protected java.beans.PropertyChangeSupport getPropertyChange() { + if (this.propertyChange == null) this.propertyChange = new java.beans.PropertyChangeSupport(this); + return this.propertyChange; } /** * Insert the method's description here. * Creation date: (1/9/2003 3:18:26 PM) - * @return double[] + * * @param row int + * @return double[] */ - public double[] getRow(int row){ - return fieldValues.get(row); + public double[] getRow(int row) { + return this.values.get(row); } - public List getRows(){ - return Collections.unmodifiableList(this.fieldValues); + public List getRows() { + return Collections.unmodifiableList(this.values); } /** * getVariableNames method comment. */ - public int getRowCount(){ - return (fieldValues.size()); + public int getRowCount() { + return (this.values.size()); } /** * The hasListeners method was generated to support the propertyChange field. */ - public synchronized boolean hasListeners(java.lang.String propertyName){ - return getPropertyChange().hasListeners(propertyName); + public synchronized boolean hasListeners(java.lang.String propertyName) { + return this.getPropertyChange().hasListeners(propertyName); } /** * getVariableNames method comment. */ - private boolean isCorner(int t, double a[], double b[], double c[], double scale[], double minSquaredRatio, double maxSquaredRatio){ + private boolean isCorner(int t, double[] a, double[] b, double[] c, double[] scale, double minSquaredRatio, double maxSquaredRatio) { double c_minus_b_t = (c[t] - b[t]) / scale[t]; double b_minus_a_t = (b[t] - a[t]) / scale[t]; double c_minus_b_t_2 = c_minus_b_t * c_minus_b_t; double b_minus_a_t_2 = b_minus_a_t * b_minus_a_t; - if(c[t] == b[t]){ - return false; - } - // - for(int i = 0; i < getDataColumnCount(); i++){ - if(i != t){ - double c_minus_b_y = (c[i] - b[i]) / scale[i]; - double b_minus_a_y = (b[i] - a[i]) / scale[i]; - double ratio = (b_minus_a_y * b_minus_a_y + b_minus_a_t_2) / - (c_minus_b_y * c_minus_b_y + c_minus_b_t_2); - if(ratio < minSquaredRatio || ratio > maxSquaredRatio){ - if(lg.isDebugEnabled()){ - lg.debug("corner with ratio = " + ratio + " at b[t]=" + b[t] + ", a[" + i + "]=" + a[i] + ", b[" + i + "]=" + b[i] + ", c[" + i + "]=" + c[i]); - } - return true; - } - if(lg.isDebugEnabled()){ - lg.debug("NOT A CORNER with ratio = " + ratio + " at b[t]=" + b[t] + ", a[" + i + "]=" + a[i] + ", b[" + i + "]=" + b[i] + ", c[" + i + "]=" + c[i]); - } - } + if (c[t] == b[t]) return false; + + // find corner + for (int i = 0; i < this.getDataColumnCount(); i++) { + if (i == t) continue; + double c_minus_b_y = (c[i] - b[i]) / scale[i]; + double b_minus_a_y = (b[i] - a[i]) / scale[i]; + double ratio = (b_minus_a_y * b_minus_a_y + b_minus_a_t_2) / + (c_minus_b_y * c_minus_b_y + c_minus_b_t_2); + String ratioString = "with ratio = " + ratio + " at b[t]=" + b[t] + ", a[" + i + "]=" + a[i] + ", b[" + i + "]=" + b[i] + ", c[" + i + "]=" + c[i]; + boolean isCornered = ratio < minSquaredRatio || ratio > maxSquaredRatio; + lg.debug("{} {}", isCornered ? "corner" : "NOT A CORNER", ratioString); + if (!isCornered) continue; + return true; } return false; } @@ -487,17 +480,17 @@ private boolean isCorner(int t, double a[], double b[], double c[], double scale /** * getVariableNames method comment. */ - public void removeAllRows(){ - fieldValues.clear(); + public void removeAllRows() { + this.values.clear(); } /** * getVariableNames method comment. - * THIS WILL LATER BE DELETED PROBABLY...THE COLUMNS - * WILL BE SPECIFIED AT CONSTRUCTION TIME! + * THIS WILL LATER BE DELETED PROBABLY...THE COLUMNS + * WILL BE SPECIFIED AT CONSTRUCTION TIME! */ - public final void removeFunctionColumn(FunctionColumnDescription functionColumnDescription) throws ExpressionException{ + public final void removeFunctionColumn(FunctionColumnDescription functionColumnDescription) throws ExpressionException { // // Remove the corresponding FunctionColumnDescription from the fieldFunctionColumnDescriptions vector @@ -505,15 +498,15 @@ public final void removeFunctionColumn(FunctionColumnDescription functionColumnD // column descriptions array. // - ColumnDescription[] oldValue = fieldColumnDescriptions; + ColumnDescription[] oldValue = this.columnDescriptions; - fieldFunctionColumnDescriptions.removeElement(functionColumnDescription); + this.functionColumnDescriptions.removeElement(functionColumnDescription); - fieldColumnDescriptions = null; - resultSetSymbolTableWithFunction = null; + this.columnDescriptions = null; + this.resultSetSymbolTableWithFunction = null; - if(getPropertyChange().getPropertyChangeListeners().length > 0){ - firePropertyChange("columnDescriptions", oldValue, getColumnDescriptions()); + if (this.getPropertyChange().getPropertyChangeListeners().length > 0) { + this.firePropertyChange("columnDescriptions", oldValue, this.getColumnDescriptions()); } } @@ -521,23 +514,23 @@ public final void removeFunctionColumn(FunctionColumnDescription functionColumnD /** * The removePropertyChangeListener method was generated to support the propertyChange field. */ - public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener listener){ - getPropertyChange().removePropertyChangeListener(listener); + public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener listener) { + this.getPropertyChange().removePropertyChangeListener(listener); } /** * The removePropertyChangeListener method was generated to support the propertyChange field. */ - public synchronized void removePropertyChangeListener(java.lang.String propertyName, java.beans.PropertyChangeListener listener){ - getPropertyChange().removePropertyChangeListener(propertyName, listener); + public synchronized void removePropertyChangeListener(java.lang.String propertyName, java.beans.PropertyChangeListener listener) { + this.getPropertyChange().removePropertyChangeListener(propertyName, listener); } /** * getData method comment. */ - public void setValue(int r, int c, double value){ - double[] values = fieldValues.get(r); + public void setValue(int r, int c, double value) { + double[] values = this.values.get(r); values[c] = value; } @@ -545,115 +538,111 @@ public void setValue(int r, int c, double value){ /** * getVariableNames method comment. */ - public synchronized void trimRows(int maxRowCount){ - if(maxRowCount > getRowCount() - 1){ - return; //nothing to do - } - if(lg.isDebugEnabled()){ - lg.info("rowCount=" + getRowCount()); - } - if(maxRowCount <= 0){ - throw new IllegalArgumentException("must keep at least one row"); - } + public synchronized void trimRows(int maxRowCount) { + if (maxRowCount > this.getRowCount() - 1) return; //nothing to do + + if (lg.isDebugEnabled()) lg.info("rowCount={}", this.getRowCount()); + if (maxRowCount <= 0) throw new IllegalArgumentException("must keep at least one row"); + // // identify appropriate scaling for time and each variable // - double min[] = new double[getDataColumnCount()]; - double max[] = new double[getDataColumnCount()]; - double scale[] = new double[getDataColumnCount()]; - for(int i = 0; i < getDataColumnCount(); i++){ + double[] min = new double[this.getDataColumnCount()]; + double[] max = new double[this.getDataColumnCount()]; + double[] scale = new double[this.getDataColumnCount()]; + for (int i = 0; i < this.getDataColumnCount(); i++) { min[i] = Double.MAX_VALUE; max[i] = -Double.MAX_VALUE; } - for(int i = 0; i < getRowCount() - 2; i++){ - double values[] = fieldValues.get(i); - for(int j = 0; j < getDataColumnCount(); j++){ + for (int i = 0; i < this.getRowCount() - 2; i++) { + double[] values = this.values.get(i); + for (int j = 0; j < this.getDataColumnCount(); j++) { min[j] = Math.min(min[j], values[j]); max[j] = Math.max(max[j], values[j]); } } - for(int i = 0; i < getDataColumnCount(); i++){ + for (int i = 0; i < this.getDataColumnCount(); i++) { scale[i] = max[i] - min[i]; - if(scale[i] == 0){ + if (scale[i] == 0) { scale[i] = 1; } - if(lg.isDebugEnabled()){ - lg.info("scale[" + i + "] = " + scale[i]); + if (lg.isDebugEnabled()) { + lg.info("scale[{}] = {}", i, scale[i]); } } double threshold = 0.01; double minSquaredRatio = 0.1 * 0.1; double maxSquaredRatio = 10 * 10; - int t = findColumn("t"); + int t = this.findColumn("t"); final boolean haveT = t >= 0; double TOLERANCE = 0.1; - LinkedList linkedList = new LinkedList(fieldValues); + LinkedList linkedList = new LinkedList<>(this.values); while (maxRowCount < linkedList.size() && threshold < TOLERANCE) { ListIterator iter = linkedList.listIterator(0); - double a[] = iter.next(); - double b[] = iter.next(); - double c[] = null; + double[] a = iter.next(); + double[] b = iter.next(); + double[] c; while (iter.hasNext()) { c = iter.next(); - if(haveT && calculateErrorFactor(t, a, b, c, scale) < threshold && !isCorner(t, a, b, c, scale, minSquaredRatio, maxSquaredRatio)){ + if (haveT && this.calculateErrorFactor(t, a, b, c, scale) < threshold && !this.isCorner(t, a, b, c, scale, minSquaredRatio, maxSquaredRatio)) { iter.previous(); iter.previous(); iter.remove(); - if(iter.hasNext()){ - a = (double[]) iter.next(); + if (iter.hasNext()) { + a = iter.next(); } - if(iter.hasNext()){ - b = (double[]) iter.next(); + if (iter.hasNext()) { + b = iter.next(); } } else { a = b; b = c; } - if(c == linkedList.getLast()){ + if (c == linkedList.getLast()) { break; } } - if(lg.isDebugEnabled()){ - lg.info("TOLERANCE=" + TOLERANCE + ", threshold=" + threshold + ", size=" + linkedList.size()); + if (lg.isDebugEnabled()) { + lg.info("TOLERANCE={}, threshold={}, size={}", TOLERANCE, threshold, linkedList.size()); } threshold += TOLERANCE / 10; - if(threshold >= TOLERANCE){ + if (threshold >= TOLERANCE) { TOLERANCE *= 10; threshold = TOLERANCE / 10; } } - lg.trace("final tolerance=" + TOLERANCE + " final threshold=" + threshold + ", " + linkedList.size() + " remaining (keepAtMost=" + maxRowCount + ")"); - ArrayList values = new ArrayList(); - if(linkedList.size() > maxRowCount){//just sample list evenly in this case - values.add(fieldValues.get(0));//Add first value - if(maxRowCount > 2){//Add values between first and last - for(int i = 1; i < (maxRowCount - 1); i++){ - values.add(fieldValues.get((int) (i * fieldValues.size() / (maxRowCount - 1)))); + lg.trace("final tolerance={} final threshold={}, {} remaining (keepAtMost={})", TOLERANCE, threshold, linkedList.size(), maxRowCount); + ArrayList values = new ArrayList<>(); + if (linkedList.size() > maxRowCount) {//just sample list evenly in this case + values.add(this.values.get(0));//Add first value + if (maxRowCount > 2) {//Add values between first and last + for (int i = 1; i < (maxRowCount - 1); i++) { + values.add(this.values.get(i * this.values.size() / (maxRowCount - 1))); } } - if(maxRowCount > 1){//Add last value - values.add(fieldValues.get(fieldValues.size() - 1)); + if (maxRowCount > 1) {//Add last value + values.add(this.values.get(this.values.size() - 1)); } // throw new RuntimeException("sample tolerance "+TOLERANCE+" exceeded while removing time points, "+linkedList.size()+" remaining (keepAtMost="+maxRowCount+")"); } else { values.addAll(linkedList); } - fieldValues = values; + this.values = values; } - private VariableSymbolTable getResultSetSymbolTableWithFunction(){ - if(resultSetSymbolTableWithFunction == null){ - resultSetSymbolTableWithFunction = createResultSetSymbolTable(true); + private VariableSymbolTable getResultSetSymbolTableWithFunction() { + if (this.resultSetSymbolTableWithFunction == null) { + this.resultSetSymbolTableWithFunction = this.createResultSetSymbolTable(true); } - return resultSetSymbolTableWithFunction; + return this.resultSetSymbolTableWithFunction; } - private VariableSymbolTable getResultSetSymbolTableWithoutFunction(){ - if(resultSetSymbolTableWithoutFunction == null){ - resultSetSymbolTableWithoutFunction = createResultSetSymbolTable(false); + private VariableSymbolTable getResultSetSymbolTableWithoutFunction() { + if (this.resultSetSymbolTableWithoutFunction == null) { + this.resultSetSymbolTableWithoutFunction = this.createResultSetSymbolTable(false); } - return resultSetSymbolTableWithoutFunction; + return this.resultSetSymbolTableWithoutFunction; } } diff --git a/vcell-core/src/main/java/cbit/vcell/math/SourceSymbolMapping.java b/vcell-core/src/main/java/cbit/vcell/math/SourceSymbolMapping.java index c006b40b63..4931ea9fba 100644 --- a/vcell-core/src/main/java/cbit/vcell/math/SourceSymbolMapping.java +++ b/vcell-core/src/main/java/cbit/vcell/math/SourceSymbolMapping.java @@ -5,9 +5,9 @@ import java.io.Serializable; public interface SourceSymbolMapping extends Serializable { - public Variable findVariableByName(String variableName); + Variable findVariableByName(String variableName); - public SymbolTableEntry[] getBiologicalSymbol(Variable var); + SymbolTableEntry[] getBiologicalSymbol(Variable var); - public Variable getVariable(SymbolTableEntry biologicalSymbol); + Variable getVariable(SymbolTableEntry biologicalSymbol); } diff --git a/vcell-core/src/main/java/cbit/vcell/solver/ode/ODESolver.java b/vcell-core/src/main/java/cbit/vcell/solver/ode/ODESolver.java index 0a5a692457..88efd35f9a 100644 --- a/vcell-core/src/main/java/cbit/vcell/solver/ode/ODESolver.java +++ b/vcell-core/src/main/java/cbit/vcell/solver/ode/ODESolver.java @@ -18,6 +18,6 @@ * Creation date: (8/19/2000 8:58:11 PM) * @author: John Wagner */ -public interface ODESolver extends Solver { +public interface ODESolver extends Solver, ODESolverResultsSetReturnable { ODESolverResultSet getODESolverResultSet(); } diff --git a/vcell-core/src/main/java/cbit/vcell/solver/ode/ODESolverResultSet.java b/vcell-core/src/main/java/cbit/vcell/solver/ode/ODESolverResultSet.java index 834f259421..f7bd285939 100644 --- a/vcell-core/src/main/java/cbit/vcell/solver/ode/ODESolverResultSet.java +++ b/vcell-core/src/main/java/cbit/vcell/solver/ode/ODESolverResultSet.java @@ -37,19 +37,14 @@ public ODESolverResultSet() { super(); } public ODESolverResultSet(ODESolverResultSet copyThisODESolverResultSet) { - super((RowColumnResultSet)copyThisODESolverResultSet); + super(copyThisODESolverResultSet); } - public boolean isMultiTrialData() - { - if(getColumnDescriptionsCount() > 0) - { - int totalcol = getColumnDescriptionsCount(); - for(int i=0; i lazyAccessorMapping; + private final double[] originalTimes; + private final Map timeIndexMapping; + + public NonSpatialSBMLSimResults(ODESolverResultSet resultSet, UniformTimeCourse utcSim, SBMLSymbolMapping sbmlMapping, MathSymbolMapping mathMapping, boolean resultsNeedInterpolation) throws ExpressionException { + super(sbmlMapping, mathMapping); + this.resultSet = resultsNeedInterpolation ? NonSpatialSBMLSimResults.interpolate(resultSet, utcSim) : resultSet; + this.originalTimes = this.getRawDataAtAllTimes("t"); + this.timeIndexMapping = new HashMap<>(); + for (int i = 0; i < this.originalTimes.length; i++) { + this.timeIndexMapping.put(this.originalTimes[i], i); + } + } - public NonSpatialSBMLSimResults(ODESolverResultSet resultSet, SBMLSymbolMapping sbmlMapping, MathSymbolMapping mathMapping){ - this.resultSet = resultSet; - this.sbmlMapping = sbmlMapping; - this.mathMapping = mathMapping; - this.lazyAccessorMapping = new HashMap<>(); + @Override + public double[] getOriginalTimes() { + return this.originalTimes; } - public int getMaxDataFlatLength(){ + @Override + public int getMaxDataFlatLength() { return this.resultSet.getRowCount(); } - public LazySBMLNonSpatialDataAccessor getSBMLDataAccessor(String sbmlId, UniformTimeCourse utcSim, int desiredLength){ - String key = NonSpatialSBMLSimResults.createUniqueKey(sbmlId, utcSim); + @Override + public List getSpatialDimensions() { + return List.of(1); + } + + @Override + public LazySBMLDataAccessor getSBMLDataAccessor(String sbmlId, UniformTimeCourse utcSim) { + String key = SpatialSBMLSimResults.createUniqueKey(sbmlId, utcSim); if (this.lazyAccessorMapping.containsKey(key))return this.lazyAccessorMapping.get(key); - LazySBMLNonSpatialDataAccessor newAccessor = new LazySBMLNonSpatialDataAccessor(this.generateCallable(sbmlId, utcSim, desiredLength), desiredLength); + LazySBMLNonSpatialDataAccessor newAccessor = new LazySBMLNonSpatialDataAccessor(this.generateCallable(sbmlId, utcSim), this.getMaxDataFlatLength(), Arrays.stream(this.getDesiredTimes(utcSim)).boxed().toList()); this.lazyAccessorMapping.put(key, newAccessor); return newAccessor; } - private Callable generateCallable(String sbmlId, UniformTimeCourse utcSim, int desiredLength){ + @Override + protected Callable generateCallable(String vcellVarId, UniformTimeCourse utcSim) { return new Callable<>() { /** * Access upon request the data desired from the appropriate @@ -58,148 +69,154 @@ private Callable generateCallable(String sbmlId, UniformTimeCour */ @Override public SBMLDataRecord call() throws Exception { - return NonSpatialSBMLSimResults.this.getSBMLVarData(sbmlId, utcSim, desiredLength); + return NonSpatialSBMLSimResults.this.getDataForSBMLVar(vcellVarId, utcSim); } }; } - // We want to do lazy data fetching - private SBMLDataRecord getSBMLVarData(String sbmlId, UniformTimeCourse utcSim, int desiredLength) - throws ExpressionException { - int column = this.resultSet.findColumn(sbmlId) ; - //double[] data = null; - double[] processedData = new double[desiredLength]; - - if (column < 0) { - SBase sBase = this.sbmlMapping.getMappedSBase(sbmlId); - if (sBase == null) { - throw new RuntimeException("failed to find VCell symbol for sbml id: "+sbmlId); - } - SymbolTableEntry ste = this.sbmlMapping.getSte(sBase, SymbolContext.RUNTIME); - if (ste == null) { - ste = this.sbmlMapping.getSte(this.sbmlMapping.getMappedSBase(sbmlId), SymbolContext.INITIAL); - } + @Override + public SBMLDataRecord getDataForSBMLVar(String sbmlId, UniformTimeCourse utcSim) throws ExpressionException, IOException, DataAccessException { + int column = this.resultSet.findColumn(sbmlId); + return column < 0 ? this.trackDownDataToBuildRecord(sbmlId, utcSim): this.buildDataRecord(column, utcSim); + } - if (ste instanceof Structure.StructureSize) { - StructureMapping.StructureMappingParameter sizeParam = null; - for (SymbolTableEntry bioSte : this.mathMapping.getMappedBiologicalSymbols()) { - if (!(bioSte instanceof StructureMapping.StructureMappingParameter param)) continue; - if (param.getRole() == StructureMapping.ROLE_Size && - param.getStructure().getStructureSize() == ste) { - sizeParam = param; - break; - } - } - if (sizeParam != null){ - ste = sizeParam; - }else { - throw new RuntimeException("failed to find VCell structure size parameter for sbml compartment size: " + sbmlId); + private SBMLDataRecord buildDataRecord(int columnId, UniformTimeCourse utcSim) throws ExpressionException { + double[] times = this.getDesiredTimes(utcSim); + double[] rawData = this.resultSet.extractColumn(columnId); + double[] data = Arrays.stream(times).map((x)->rawData[NonSpatialSBMLSimResults.this.timeIndexMapping.get(x)]).toArray(); + return new SBMLDataRecord(data, List.of(data.length), times); + } + + private SBMLDataRecord trackDownDataToBuildRecord(String sbmlId, UniformTimeCourse utcSim) throws IOException, DataAccessException, ExpressionException { + SBase sBase = this.sbmlMapping.getMappedSBase(sbmlId); + if (sBase == null) throw new RuntimeException("failed to find VCell symbol for sbml id: "+sbmlId); + + SymbolTableEntry ste = this.sbmlMapping.getSte(sBase, SymbolContext.RUNTIME); + if (ste == null) ste = this.sbmlMapping.getSte(this.sbmlMapping.getMappedSBase(sbmlId), SymbolContext.INITIAL); + + if (ste instanceof Structure.StructureSize) { + StructureMapping.StructureMappingParameter sizeParam = null; + for (SymbolTableEntry bioSte : this.mathMapping.getMappedBiologicalSymbols()) { + if (!(bioSte instanceof StructureMapping.StructureMappingParameter param)) continue; + if (param.getRole() == StructureMapping.ROLE_Size && + param.getStructure().getStructureSize() == ste) { + sizeParam = param; + break; } } + if (sizeParam == null) + throw new RuntimeException("failed to find VCell structure size parameter for sbml compartment size: " + sbmlId); + ste = sizeParam; + } - if (ste instanceof Kinetics.KineticsParameter lumpedRate && lumpedRate.getRole() == Kinetics.ROLE_LumpedReactionRate - && this.mathMapping.getVariable(ste) == null - ) { - // if reaction has been transformed to distributed, then retrieve distributed rate and multiply by compartment size. - // find distributed rate by looking in MathSymbolMapping for a variable which is mapped to the same reaction and has ROLE_ReactionRate - Stream kineticsParameterStream = this.mathMapping.getMappedBiologicalSymbols().stream() - .filter(Kinetics.KineticsParameter.class::isInstance).map(Kinetics.KineticsParameter.class::cast); - Kinetics.KineticsParameter distributedRate = kineticsParameterStream - .filter((dRates) -> dRates.getRole()==Kinetics.ROLE_ReactionRate) - .filter((dRates) -> dRates.getKinetics().getReactionStep()==lumpedRate.getKinetics().getReactionStep()) - .findFirst().orElseThrow(() -> new RuntimeException("failed to find VCell distributed reaction rate for sbml reaction: " + sbmlId)); - // find the math variable for the distributed rate - cbit.vcell.math.Variable distributedRateMathVar = this.mathMapping.getVariable(distributedRate); - // find compartment size by looking in MathSymbolMapping for a StructureMappingParameter which is mapped to the same compartment and has ROLE_Size - Stream structureMappingParameterStream = this.mathMapping.getMappedBiologicalSymbols().stream() - .filter(StructureMapping.StructureMappingParameter.class::isInstance).map(StructureMapping.StructureMappingParameter.class::cast); - StructureMapping.StructureMappingParameter sizeParam = structureMappingParameterStream - .filter((smp) -> smp.getRole() == StructureMapping.ROLE_Size) - .filter((smp) -> smp.getStructure() == lumpedRate.getKinetics().getReactionStep().getStructure()) - .findFirst().orElseThrow(() -> new RuntimeException("failed to find VCell compartment size for sbml compartment: " + sbmlId)); - - // find the math variable for the compartment size, and if it is of type Constant, get the value - cbit.vcell.math.Variable sizeMathVar = this.mathMapping.getVariable(sizeParam); - if (!(sizeMathVar instanceof Constant)) { - throw new RuntimeException("expecting compartment size to be a constant"); - } - double compartmentSize = sizeMathVar.getExpression().evaluateConstant(); - // if the distributed rate is in the result set, then multiply distributed rate by compartment size - if (this.resultSet.findColumn(distributedRateMathVar.getName()) >= 0) { - double[] data = this.resultSet.extractColumn(this.resultSet.findColumn(distributedRateMathVar.getName())); - double[] adjustedData = NonSpatialSBMLSimResults.getRequestedDataVector(data.length - processedData.length, processedData.length, data); - System.arraycopy(adjustedData, 0, processedData, 0, adjustedData.length); - for (int i = 0; i < processedData.length; i++) { - processedData[i] *= compartmentSize; - } - } else if (distributedRateMathVar instanceof Constant constDRMV) { - // if the distributed rate is a constant, then multiply constant by compartment size - double[] data = new double[processedData.length]; - Arrays.fill(data, constDRMV.getExpression().evaluateConstant() * compartmentSize); - System.arraycopy(data, 0, processedData, 0, data.length); - } else { - throw new RuntimeException("failed to find VCell reaction rate for sbml reaction: " + sbmlId); - } + Set identifiers = Arrays.stream(this.resultSet.getColumnDescriptions()).map(ColumnDescription::getName).collect(Collectors.toSet()); + return this.getDataForSBMLVar(this.mathMapping.getVariable(ste), ste, identifiers, List.of(1), this.getDesiredTimes(utcSim), utcSim); + } - double[] vector = new double[desiredLength]; - Arrays.fill(vector, Double.NaN); - System.arraycopy(processedData, 0, vector, 0, processedData.length); - return new SBMLDataRecord(vector, List.of(vector.length), null); - } - cbit.vcell.math.Variable mathVar = this.mathMapping.getVariable(ste); - if (mathVar == null) { - throw new RuntimeException("Math mapping couldn't find mathVar with ste: " + ste.getName()); - } - double[] data = null; - int varIndex = this.resultSet.findColumn(mathVar.getName()); - - if (varIndex >= 0){ - data = this.resultSet.extractColumn(varIndex); - } else if (mathVar instanceof Constant){ - double value = mathVar.getExpression().evaluateConstant(); - data = new double[this.resultSet.getRowCount()]; - Arrays.fill(data, value); - } + @Override + protected double[] getRawDataAtAllTimes(String varName) throws ExpressionException { + int columnIndex = this.resultSet.findColumn(varName); + if (columnIndex == -1) throw new RuntimeException("Unable to find `" + varName + "`!"); + return this.resultSet.extractColumn(columnIndex); + } + + private static ODESolverResultSet interpolate(ODESolverResultSet odeSolverResultSet, UniformTimeCourse sedmlSim) throws ExpressionException { + double outputStart = sedmlSim.getOutputStartTime(); + double outputEnd = sedmlSim.getOutputEndTime(); + + int numPoints = sedmlSim.getNumberOfSteps() + 1; + + + ColumnDescription[] columnDescriptions = odeSolverResultSet.getColumnDescriptions(); + + // need to construct a new RowColumnResultSet instance + ODESolverResultSet finalResultSet = new ODESolverResultSet(); - if (data == null) throw new RuntimeException("couldn't find var '" + sbmlId + "' in vcell sim results"); - double[] formattedData = NonSpatialSBMLSimResults.getRequestedDataVector(data.length - processedData.length, processedData.length, data); - System.arraycopy(formattedData, 0, processedData, 0, formattedData.length); - } else { // found column - double[] data = this.resultSet.extractColumn(column); - if ("t".equals(sbmlId)) for (int i = 0; i < data.length; i++) data[i] += utcSim.getInitialTime(); - double[] formattedData = NonSpatialSBMLSimResults.getRequestedDataVector(data.length - processedData.length, processedData.length, data); - System.arraycopy(formattedData, 0, processedData, 0, formattedData.length); + // use same column descriptions + for (ColumnDescription cd : columnDescriptions) { + finalResultSet.addDataColumn(cd); } - //double[] vector = NonSpatialSBMLSimResults.getRequestedDataVector(utcSim.getOutputStartTime() - utcSim.getInitialTime(), utcSim.getNumberOfSteps() + 1, processedData); - double[] vector = new double[desiredLength]; - Arrays.fill(vector, Double.NaN); - System.arraycopy(processedData, 0, vector, 0, processedData.length); + double deltaTime = ((outputEnd - outputStart) / (numPoints - 1)); + double[] timepoints = new double[numPoints]; - return new SBMLDataRecord(vector, List.of(vector.length), null); - } + timepoints[0] = outputStart; + for (int i = 1; i < numPoints; i++) { + timepoints[i] = timepoints[i - 1] + deltaTime; + } + + double[] originalTimepoints = odeSolverResultSet.extractColumn(0); + + + double[][] columnValues = new double[columnDescriptions.length][]; + columnValues[0] = timepoints; + for (int i = 1; i < columnDescriptions.length; i++) { + // each row uses the time index based on the params above and for each column descriptions interpolate the value from the original result set + columnValues[i] = interpLinear(originalTimepoints, odeSolverResultSet.extractColumn(i), timepoints); + } + + + double[][] rowValues = new double[numPoints][columnDescriptions.length]; + + for (int rowCount = 0; rowCount < numPoints; rowCount++) { + for (int colCount = 0; colCount < columnDescriptions.length; colCount++) { + rowValues[rowCount][colCount] = columnValues[colCount][rowCount]; + } + } + + + // add a numPoints number of rows one by one as double[] + for (int rowCount = 0; rowCount < numPoints; rowCount++) { + finalResultSet.addRow(rowValues[rowCount]); + } - /** - * note that because output start time isn't directly used in math calculations, - * we can use the value as a truthy value instead. - * - * @param outputStartTime used to determine if a non-zero start time is used - * @param outputNumberOfPoints size of the output array - * @param data the source data - * @return an adjusted data array - */ - private static double[] getRequestedDataVector(double outputStartTime, int outputNumberOfPoints, double[] data) { - if (outputStartTime == 0) return data; - - double[] adjData = new double[outputNumberOfPoints]; - int statingPosition = Math.max(0, data.length - outputNumberOfPoints - 1); - if (outputNumberOfPoints > data.length) Arrays.fill(adjData, Double.NaN); - System.arraycopy(data, statingPosition, adjData, 0, Math.min(outputNumberOfPoints, data.length)); - return adjData; + return finalResultSet; } - private static String createUniqueKey(String sbmlId, UniformTimeCourse utcSim){ - return String.format("%s@%s", sbmlId, utcSim.toString()); + private static double[] interpLinear(double[] x, double[] y, double[] xi) throws IllegalArgumentException { + + if (x.length != y.length) { + throw new IllegalArgumentException("X and Y must be the same length"); + } + if (x.length == 1) { + throw new IllegalArgumentException("X must contain more than one value"); + } + double[] dx = new double[x.length - 1]; + double[] dy = new double[x.length - 1]; + double[] slope = new double[x.length - 1]; + double[] intercept = new double[x.length - 1]; + + // Calculate the line equation (i.e. slope and intercept) between each point + for (int i = 0; i < x.length - 1; i++) { + dx[i] = x[i + 1] - x[i]; + if (dx[i] == 0) { + throw new IllegalArgumentException("X must be montotonic. A duplicate " + "x-value was found"); + } + if (dx[i] < 0) { + throw new IllegalArgumentException("X must be sorted"); + } + dy[i] = y[i + 1] - y[i]; + slope[i] = dy[i] / dx[i]; + intercept[i] = y[i] - x[i] * slope[i]; + } + + // Perform the interpolation here + double[] yi = new double[xi.length]; + for (int i = 0; i < xi.length; i++) { + if ((xi[i] > x[x.length - 1]) || (xi[i] < x[0])) { + yi[i] = Double.NaN; + } else { + int loc = Arrays.binarySearch(x, xi[i]); + if (loc < -1) { + loc = -loc - 2; + yi[i] = slope[loc] * xi[i] + intercept[loc]; + } else { + yi[i] = y[loc]; + } + } + } + + return yi; } } diff --git a/vcell-core/src/main/java/org/vcell/sbml/vcell/SBMLSimResults.java b/vcell-core/src/main/java/org/vcell/sbml/vcell/SBMLSimResults.java new file mode 100644 index 0000000000..388d36beb7 --- /dev/null +++ b/vcell-core/src/main/java/org/vcell/sbml/vcell/SBMLSimResults.java @@ -0,0 +1,197 @@ +package org.vcell.sbml.vcell; + +import cbit.vcell.mapping.MathSymbolMapping; +import cbit.vcell.mapping.StructureMapping; +import cbit.vcell.math.Constant; +import cbit.vcell.math.MathException; +import cbit.vcell.math.Variable; +import cbit.vcell.model.Kinetics; +import cbit.vcell.parser.ExpressionException; +import cbit.vcell.parser.SymbolTableEntry; +import cbit.vcell.simdata.*; +import cbit.vcell.solver.AnnotatedFunction; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jlibsedml.UniformTimeCourse; +import org.vcell.sbml.vcell.lazy.LazySBMLDataAccessor; +import org.vcell.util.DataAccessException; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.stream.Stream; + +public abstract class SBMLSimResults { + private static final Logger lg = LogManager.getLogger(SBMLSimResults.class); + + protected final OutputContext DEFAULT_OUTPUT_CONTEXT = new OutputContext(new AnnotatedFunction[] {}); + protected final SBMLSymbolMapping sbmlMapping; + protected final MathSymbolMapping mathMapping; + protected final Map lazyAccessorMapping; + + protected SBMLSimResults(SBMLSymbolMapping sbmlMapping, MathSymbolMapping mathMapping){ + this.sbmlMapping = sbmlMapping; + this.mathMapping = mathMapping; + this.lazyAccessorMapping = new HashMap<>(); + } + + public abstract double[] getOriginalTimes(); + + public abstract int getMaxDataFlatLength() throws MathException, IOException, DataAccessException; + + public abstract List getSpatialDimensions(); + + public abstract LazySBMLDataAccessor getSBMLDataAccessor(String sbmlId, UniformTimeCourse utcSim) throws MathException, IOException, DataAccessException; + + protected abstract Callable generateCallable(String vcellVarId, UniformTimeCourse utcSim); + + public abstract SBMLDataRecord getDataForSBMLVar(String sbmlId, UniformTimeCourse utcSim) throws IOException, DataAccessException, ExpressionException; + + protected abstract double[] getRawDataAtAllTimes(String varName) throws ExpressionException, DataAccessException; + + protected SBMLDataRecord getDataForSBMLVar(Variable mathVar, SymbolTableEntry ste, Set varNamesSet, List spatialDimensions, double[] times, UniformTimeCourse utcSim) throws IOException, DataAccessException, ExpressionException { + if (mathVar != null) + return this.processData(mathVar, varNamesSet, spatialDimensions, times, utcSim.getInitialTime()); + if (ste instanceof Kinetics.KineticsParameter lumpedRate + && Kinetics.ROLE_LumpedReactionRate == lumpedRate.getRole()) + return this.processLumpedRate(lumpedRate, varNamesSet, spatialDimensions, times, utcSim.getInitialTime()); + + throw new RuntimeException("Math mapping couldn't find mathVar with ste: " + ste.getName()); + } + + private SBMLDataRecord processData(cbit.vcell.math.Variable mathVar, Set identifierSet, List spatialDimensions, double[] times, double initialTimeAdjustment) throws IOException, DataAccessException, ExpressionException { + double[] data; + List sDims = spatialDimensions == null? new LinkedList<>() : spatialDimensions; + int numOfElementsAtEachTime = sDims.stream().reduce(1, (x, y)-> x * y); + if (numOfElementsAtEachTime < 1){ + lg.warn("Negative or 0 number of elements per step-size detected"); + numOfElementsAtEachTime = 1; + } + /// TODO: Evaluate how to adjust non-spatial time data! + if (identifierSet.contains(mathVar.getName())) { + data = this.loadData(mathVar.getName(), times, numOfElementsAtEachTime, 1); + } else if (mathVar instanceof Constant constMathVar){ + double value = constMathVar.getExpression().evaluateConstant(); + data = new double[numOfElementsAtEachTime * times.length]; + Arrays.fill(data, value); + } else { + throw new RuntimeException("Math mapping couldn't find mathVar with name: " + mathVar.getName()); + } + + List dataDimensions = new ArrayList<>(List.of(times.length)); + for (int dim : sDims) if (dim != 1 || dataDimensions.size() > 1) dataDimensions.add(dim); + return new SBMLDataRecord(data, dataDimensions, initialTimeAdjustment == 0.0 ? times : Arrays.stream(times).map((x)-> x + initialTimeAdjustment).toArray()); + } + + private SBMLDataRecord processLumpedRate(Kinetics.KineticsParameter lumpedRate, Set varNamesSet, List spatialDimensions, double[] times, double initialTimeAdjustment) throws ExpressionException, DataAccessException { + // if reaction has been transformed to distributed, then retrieve distributed rate and multiply by compartment size. + // find distributed rate by looking in MathSymbolMapping for a variable which is mapped to the same reaction and has ROLE_ReactionRate + Stream kineticsParameterStream = this.mathMapping.getMappedBiologicalSymbols().stream() + .filter(Kinetics.KineticsParameter.class::isInstance).map(Kinetics.KineticsParameter.class::cast); + Kinetics.KineticsParameter distributedRate = kineticsParameterStream + .filter((dRates) -> dRates.getRole()==Kinetics.ROLE_ReactionRate) + .filter((dRates) -> dRates.getKinetics().getReactionStep()==lumpedRate.getKinetics().getReactionStep()) + .findFirst().orElseThrow(() -> new RuntimeException("failed to find VCell distributed reaction rate for sbml reaction: ")); + // find the math variable for the distributed rate + cbit.vcell.math.Variable distributedRateMathVar = this.mathMapping.getVariable(distributedRate); + // find compartment size by looking in MathSymbolMapping for a StructureMappingParameter which is mapped to the same compartment and has ROLE_Size + Stream structureMappingParameterStream = this.mathMapping.getMappedBiologicalSymbols().stream() + .filter(StructureMapping.StructureMappingParameter.class::isInstance).map(StructureMapping.StructureMappingParameter.class::cast); + StructureMapping.StructureMappingParameter sizeParam = structureMappingParameterStream + .filter((smp) -> smp.getRole() == StructureMapping.ROLE_Size) + .filter((smp) -> smp.getStructure() == lumpedRate.getKinetics().getReactionStep().getStructure()) + .findFirst().orElseThrow(() -> new RuntimeException("failed to find VCell compartment size for sbml compartment: ")); + + // find the math variable for the compartment size, and if it is of type Constant, get the value + cbit.vcell.math.Variable sizeMathVar = this.mathMapping.getVariable(sizeParam); + if (!(sizeMathVar instanceof Constant)) { + throw new RuntimeException("expecting compartment size to be a constant"); + } + double compartmentSize = sizeMathVar.getExpression().evaluateConstant(); + List sDims = spatialDimensions == null? new LinkedList<>() : spatialDimensions; + int numOfElementsAtEachTime = sDims.stream().reduce(1, (x, y)-> x * y); + if (numOfElementsAtEachTime < 1){ + lg.warn("Negative or 0 number of elements per step-size detected"); + numOfElementsAtEachTime = 1; + } + + // if the distributed rate is in the result set, then multiply distributed rate by compartment size + double[] data; + if (varNamesSet.contains(distributedRateMathVar.getName())) { + data = this.loadData(distributedRateMathVar.getName(), times, numOfElementsAtEachTime, compartmentSize); + + } else if (distributedRateMathVar instanceof Constant constDRMV) { + // if the distributed rate is a constant, then multiply constant by compartment size + data = new double[numOfElementsAtEachTime * times.length]; + Arrays.fill(data, constDRMV.getExpression().evaluateConstant() * compartmentSize); + } else { + throw new RuntimeException("failed to find VCell reaction rate for sbml reaction: "); + } + List dataDimensions = new ArrayList<>(List.of(times.length)); + for (int dim : sDims) if (dim != 1 || dataDimensions.size() > 1) dataDimensions.add(dim); + + return new SBMLDataRecord(data, dataDimensions, initialTimeAdjustment == 1.0 ? times : Arrays.stream(times).map((x)-> x + initialTimeAdjustment).toArray()); + } + + protected SymbolTableEntry getStructureSizeSymbolTableEntry(SymbolTableEntry ste, String sbmlId){ + for (SymbolTableEntry bioSte : this.mathMapping.getMappedBiologicalSymbols()) { + if (!(bioSte instanceof StructureMapping.StructureMappingParameter param)) continue; + if (param.getRole() != StructureMapping.ROLE_Size || param.getStructure().getStructureSize() != ste) continue; + return param; + } + throw new RuntimeException("failed to find VCell structure size parameter for sbml compartment size: " + sbmlId); + } + + protected double[] getDesiredTimes(UniformTimeCourse utcSim) { + double adjustedStartTime = utcSim.getOutputStartTime() - utcSim.getInitialTime(); + double[] finalTimes = new double[utcSim.getNumberOfSteps() + 1]; + double[] preTimes = this.getOriginalTimes(); + int startIndex = -1; + + for (int i = 0; i < preTimes.length; i++) { // Attempt 1 + if (preTimes[i] != adjustedStartTime) continue; + startIndex = i; + break; + } + if (startIndex < 0 && adjustedStartTime != 0.0) for (int i = 0; i < preTimes.length; i++) { // Attempt 2 + double amountOff = Math.abs((preTimes[i] - adjustedStartTime) / adjustedStartTime); + if (amountOff > 1e-9) continue; + startIndex = i; + break; + } + if (startIndex < 0) throw new IllegalArgumentException("Time `" + adjustedStartTime + "` not found"); + double[] subArray = Arrays.copyOfRange(preTimes, startIndex, preTimes.length); + double indexStep = (1.0 * subArray.length - 1) / (utcSim.getNumberOfSteps()); + if (indexStep % 1 != 0) + throw new RuntimeException("Found incompatible sampling with respect to requested output points: `" + subArray.length + "` vs `" + utcSim.getNumberOfSteps() + 1 + "`"); + for (int i = 0, j = 0; i < utcSim.getNumberOfSteps() + 1; i++, j += (int)indexStep) { + //finalTimes[i] = subArray[j] + utcSim.getInitialTime(); + finalTimes[i] = subArray[j]; // We will account for initial time at the end! + } + return finalTimes; + } + + protected static DataSetControllerImpl initializeDataSetController(File userDir) throws FileNotFoundException { + Cachetable cachetable = new Cachetable(2000,1000000L); + return new DataSetControllerImpl(cachetable, userDir.getParentFile(), null); + } + + protected static String createUniqueKey(String sbmlId, UniformTimeCourse utcSim){ + return String.format("%s@%s", sbmlId, utcSim.toString()); + } + + private double[] loadData(String varName, double[] times, int numberOfElementsPerTimestep, double scalingFactor) throws DataAccessException, ExpressionException { +// double[] data = new double[numberOfElementsPerTimestep * times.length]; + double[] preprocessedData = this.getRawDataAtAllTimes(varName); + return scalingFactor == 1.0 ? preprocessedData: Arrays.stream(preprocessedData).map((x)-> x * scalingFactor).toArray(); + +// for (int i = 0; i < times.length; i++) { +// double[] preprocessedData = this.getRawDataAtAllTimes(varName); +// double[] subData = scalingFactor == 1.0 ? preprocessedData: Arrays.stream(preprocessedData).map((x)-> x * scalingFactor).toArray(); +// System.arraycopy(subData, 0, data, i * numberOfElementsPerTimestep, subData.length); +// } +// return data; + } +} diff --git a/vcell-core/src/main/java/org/vcell/sbml/vcell/SpatialSBMLSimResults.java b/vcell-core/src/main/java/org/vcell/sbml/vcell/SpatialSBMLSimResults.java index 50036de17a..3a8813e5b1 100644 --- a/vcell-core/src/main/java/org/vcell/sbml/vcell/SpatialSBMLSimResults.java +++ b/vcell-core/src/main/java/org/vcell/sbml/vcell/SpatialSBMLSimResults.java @@ -1,21 +1,18 @@ package org.vcell.sbml.vcell; import cbit.vcell.mapping.MathSymbolMapping; -import cbit.vcell.mapping.StructureMapping; -import cbit.vcell.math.Constant; import cbit.vcell.math.MathException; -import cbit.vcell.model.Kinetics; import cbit.vcell.model.Structure; import cbit.vcell.parser.ExpressionException; import cbit.vcell.parser.SymbolTableEntry; import cbit.vcell.simdata.*; -import cbit.vcell.solver.AnnotatedFunction; import cbit.vcell.solver.SimulationJob; import cbit.vcell.solver.VCSimulationDataIdentifier; import cbit.vcell.solver.VCSimulationIdentifier; import cbit.vcell.solvers.CartesianMesh; import org.jlibsedml.UniformTimeCourse; import org.sbml.jsbml.SBase; +import org.vcell.sbml.vcell.lazy.LazySBMLDataAccessor; import org.vcell.sbml.vcell.lazy.LazySBMLSpatialDataAccessor; import org.vcell.util.DataAccessException; import org.vcell.util.document.User; @@ -27,47 +24,55 @@ import java.util.*; import java.util.concurrent.Callable; import java.util.stream.Collectors; -import java.util.stream.Stream; -public class SpatialSBMLSimResults { +public class SpatialSBMLSimResults extends SBMLSimResults { + protected final DataSetControllerImpl dataSetController; + protected final VCSimulationIdentifier simId; + protected final VCDataIdentifier vcDId; + protected final double[] originalTimes; + protected final CartesianMesh cartesianMesh; - //private ODESolverResultSet resultSet; - private final OutputContext DEFAULT_OUTPUT_CONTEXT = new OutputContext(new AnnotatedFunction[] {}); - private final DataSetControllerImpl dataSetController; - private final VCSimulationIdentifier simId; - private final int jobIndex; - private final SBMLSymbolMapping sbmlMapping; - private final MathSymbolMapping mathMapping; - private final Map lazyAccessorMapping; - - public SpatialSBMLSimResults(SimulationJob vcellSimJob, File userDir, SBMLSymbolMapping sbmlMapping, MathSymbolMapping mathMapping){ + public SpatialSBMLSimResults(SimulationJob vcellSimJob, File userDir, SBMLSymbolMapping sbmlMapping, MathSymbolMapping mathMapping) throws DataAccessException, MathException, IOException { + super(sbmlMapping, mathMapping); // All spatial solvers don't need interpolation try { - this.dataSetController = SpatialSBMLSimResults.initializeDataSetController(userDir); + this.dataSetController = SBMLSimResults.initializeDataSetController(userDir); } catch (FileNotFoundException e) { throw new IllegalArgumentException("File not found: `" + userDir.getAbsolutePath() + "`", e); } this.simId = SpatialSBMLSimResults.initializeSimulationIdentifier(vcellSimJob, userDir); - this.jobIndex = vcellSimJob.getJobIndex(); - this.sbmlMapping = sbmlMapping; - this.mathMapping = mathMapping; - this.lazyAccessorMapping = new HashMap<>(); + + this.vcDId = new VCSimulationDataIdentifier(this.simId, vcellSimJob.getJobIndex()); + + this.originalTimes = this.dataSetController.getDataSetTimes(this.vcDId); + this.cartesianMesh = this.dataSetController.getMesh(this.vcDId); } - public int getMaxDataFlatLength() throws MathException, IOException, DataAccessException { - var id = new VCSimulationDataIdentifier(this.simId, this.jobIndex); - CartesianMesh mesh = this.dataSetController.getMesh(id); - return mesh.getSizeZ() * mesh.getSizeY() * mesh.getSizeX() * this.dataSetController.getDataSetTimes(id).length; + @Override + public double[] getOriginalTimes() { + return this.originalTimes; } - public LazySBMLSpatialDataAccessor getSBMLDataAccessor(String sbmlId, UniformTimeCourse utcSim) throws MathException, IOException, DataAccessException { + @Override + public int getMaxDataFlatLength() { + return this.cartesianMesh.getSizeZ() * this.cartesianMesh.getSizeY() * this.cartesianMesh.getSizeX() * this.originalTimes.length; + } + + @Override + public List getSpatialDimensions(){ + return List.of(this.cartesianMesh.getSizeZ(), this.cartesianMesh.getSizeY(), this.cartesianMesh.getSizeX()); + } + + @Override + public LazySBMLDataAccessor getSBMLDataAccessor(String sbmlId, UniformTimeCourse utcSim) { String key = SpatialSBMLSimResults.createUniqueKey(sbmlId, utcSim); if (this.lazyAccessorMapping.containsKey(key))return this.lazyAccessorMapping.get(key); - LazySBMLSpatialDataAccessor newAccessor = new LazySBMLSpatialDataAccessor(this.generateCallable(sbmlId, utcSim), this.getMaxDataFlatLength()); + LazySBMLSpatialDataAccessor newAccessor = new LazySBMLSpatialDataAccessor(this.generateCallable(sbmlId, utcSim), this.getMaxDataFlatLength(), this.getSpatialDimensions(), Arrays.stream(this.getDesiredTimes(utcSim)).boxed().toList()); this.lazyAccessorMapping.put(key, newAccessor); return newAccessor; } - private Callable generateCallable(String vcellVarId, UniformTimeCourse utcSim){ + @Override + protected Callable generateCallable(String vcellVarId, UniformTimeCourse utcSim){ return new Callable<>() { /** * Access upon request the data desired from the appropriate @@ -82,146 +87,44 @@ public SBMLDataRecord call() throws Exception { }; } - public SBMLDataRecord getDataForSBMLVar(String sbmlId, UniformTimeCourse utcSim) - throws ExpressionException, DataAccessException, MathException, IOException { - VCDataIdentifier vcDId = new VCSimulationDataIdentifier(this.simId, this.jobIndex); - double[] times = this.getDesiredTimes(vcDId, utcSim); + protected static VCSimulationIdentifier initializeSimulationIdentifier(SimulationJob vcellSimJob, File userDir){ + User user = new User(userDir.getName(), null); + cbit.vcell.solver.Simulation vcellSim = vcellSimJob.getSimulation(); + return new VCSimulationIdentifier(vcellSim.getKey(), user); + } - DataOperation dataOperation = new DataOperation.DataProcessingOutputInfoOP(vcDId,true, this.DEFAULT_OUTPUT_CONTEXT); + public SBMLDataRecord getDataForSBMLVar(String sbmlId, UniformTimeCourse utcSim) throws IOException, DataAccessException, ExpressionException { + DataOperation dataOperation = new DataOperation.DataProcessingOutputInfoOP(this.vcDId,true, this.DEFAULT_OUTPUT_CONTEXT); DataOperationResults.DataProcessingOutputInfo results = (DataOperationResults.DataProcessingOutputInfo) this.dataSetController.doDataOperation(dataOperation); Set varNamesSet = new HashSet<>(Arrays.asList(results.getVariableNames())); + DataIdentifier[] dataIdentifiers = this.dataSetController.getDataIdentifiers(this.DEFAULT_OUTPUT_CONTEXT, this.vcDId); + Set identifierSet = Arrays.stream(dataIdentifiers).map(DataIdentifier::getName).collect(Collectors.toSet()); + identifierSet.addAll(varNamesSet); + + double[] times = this.getDesiredTimes(utcSim); + List spatialDimensions = List.of(this.cartesianMesh.getSizeZ(), this.cartesianMesh.getSizeY(), this.cartesianMesh.getSizeX()); + SBase sBase = this.sbmlMapping.getMappedSBase(sbmlId); if (sBase == null) throw new DataAccessException("Cannot find SBase for " + sbmlId); - CartesianMesh mesh = this.dataSetController.getMesh(vcDId); - SymbolTableEntry runtimeSte = this.sbmlMapping.getSte(sBase, SymbolContext.RUNTIME); SymbolTableEntry ste = (runtimeSte != null) ? runtimeSte : this.sbmlMapping.getSte(this.sbmlMapping.getMappedSBase(sbmlId), SymbolContext.INITIAL); if (ste instanceof Structure.StructureSize) ste = this.getStructureSizeSymbolTableEntry(ste, sbmlId); cbit.vcell.math.Variable mathVar = this.mathMapping.getVariable(ste); - if (mathVar != null) return this.processSpatialData(mathVar, vcDId, mesh, varNamesSet, times); - if (ste instanceof Kinetics.KineticsParameter lumpedRate && Kinetics.ROLE_LumpedReactionRate == lumpedRate.getRole()) - return this.processLumpedRate(sbmlId, vcDId, lumpedRate, times, mesh, varNamesSet); - throw new RuntimeException("Math mapping couldn't find mathVar with ste: " + ste.getName()); - } - - private SBMLDataRecord processSpatialData(cbit.vcell.math.Variable mathVar, VCDataIdentifier vcDId, CartesianMesh mesh, Set varNamesSet, double[] times) throws IOException, DataAccessException, ExpressionException { - DataIdentifier[] dataIdentifiers = this.dataSetController.getDataIdentifiers(this.DEFAULT_OUTPUT_CONTEXT, vcDId); - Set identifierSet = Arrays.stream(dataIdentifiers).map(DataIdentifier::getName).collect(Collectors.toSet()); - identifierSet.addAll(varNamesSet); - int meshStep = mesh.getSizeZ() * mesh.getSizeY() * mesh.getSizeX(); - double[] data; - if (identifierSet.contains(mathVar.getName())) { - data = new double[meshStep * times.length]; - for (int i = 0; i < times.length; i++) { - SimDataBlock sdb = this.dataSetController.getSimDataBlock(this.DEFAULT_OUTPUT_CONTEXT, vcDId, mathVar.getName(), times[i]); - double[] subData = sdb.getData(); - System.arraycopy(subData, 0, data, i * meshStep, subData.length); - } - } else if (mathVar instanceof Constant constMathVar){ - double value = constMathVar.getExpression().evaluateConstant(); - data = new double[meshStep * times.length]; - Arrays.fill(data, value); - } else { - throw new RuntimeException("Math mapping couldn't find mathVar with name: " + mathVar.getName()); - } - - return new SBMLDataRecord(data, List.of(times.length, mesh.getSizeZ(), mesh.getSizeY(), mesh.getSizeX()), times); - } - - private SBMLDataRecord processLumpedRate(String sbmlId, VCDataIdentifier vcDId, Kinetics.KineticsParameter lumpedRate, double[] times, CartesianMesh mesh, Set varNamesSet) throws ExpressionException, DataAccessException { - // if reaction has been transformed to distributed, then retrieve distributed rate and multiply by compartment size. - // find distributed rate by looking in MathSymbolMapping for a variable which is mapped to the same reaction and has ROLE_ReactionRate - Stream kineticsParameterStream = this.mathMapping.getMappedBiologicalSymbols().stream() - .filter(Kinetics.KineticsParameter.class::isInstance).map(Kinetics.KineticsParameter.class::cast); - Kinetics.KineticsParameter distributedRate = kineticsParameterStream - .filter((dRates) -> dRates.getRole()==Kinetics.ROLE_ReactionRate) - .filter((dRates) -> dRates.getKinetics().getReactionStep()==lumpedRate.getKinetics().getReactionStep()) - .findFirst().orElseThrow(() -> new RuntimeException("failed to find VCell distributed reaction rate for sbml reaction: " + sbmlId)); - // find the math variable for the distributed rate - cbit.vcell.math.Variable distributedRateMathVar = this.mathMapping.getVariable(distributedRate); - // find compartment size by looking in MathSymbolMapping for a StructureMappingParameter which is mapped to the same compartment and has ROLE_Size - Stream structureMappingParameterStream = this.mathMapping.getMappedBiologicalSymbols().stream() - .filter(StructureMapping.StructureMappingParameter.class::isInstance).map(StructureMapping.StructureMappingParameter.class::cast); - StructureMapping.StructureMappingParameter sizeParam = structureMappingParameterStream - .filter((smp) -> smp.getRole() == StructureMapping.ROLE_Size) - .filter((smp) -> smp.getStructure() == lumpedRate.getKinetics().getReactionStep().getStructure()) - .findFirst().orElseThrow(() -> new RuntimeException("failed to find VCell compartment size for sbml compartment: " + sbmlId)); - - // find the math variable for the compartment size, and if it is of type Constant, get the value - cbit.vcell.math.Variable sizeMathVar = this.mathMapping.getVariable(sizeParam); - if (!(sizeMathVar instanceof Constant)) { - throw new RuntimeException("expecting compartment size to be a constant"); - } - double compartmentSize = sizeMathVar.getExpression().evaluateConstant(); - - // if the distributed rate is in the result set, then multiply distributed rate by compartment size - double[] data; - int meshStep = mesh.getSizeZ() * mesh.getSizeY() * mesh.getSizeX(); - if (varNamesSet.contains(distributedRateMathVar.getName())) { - data = new double[meshStep * times.length]; - for (int i = 0; i < times.length; i++) { - SimDataBlock sdb = this.dataSetController.getSimDataBlock(this.DEFAULT_OUTPUT_CONTEXT, vcDId, distributedRateMathVar.getName(), times[i]); - double[] subData = sdb.getData(); - for (int j = 0; j < subData.length; j++) { - data[i * meshStep + j] = subData[j] * compartmentSize; - } - } - } else if (distributedRateMathVar instanceof Constant constDRMV) { - // if the distributed rate is a constant, then multiply constant by compartment size - data = new double[meshStep * times.length]; - Arrays.fill(data, constDRMV.getExpression().evaluateConstant() * compartmentSize); - } else { - throw new RuntimeException("failed to find VCell reaction rate for sbml reaction: " + sbmlId); - } - - return new SBMLDataRecord(data, List.of(times.length, mesh.getSizeZ(), mesh.getSizeY(), mesh.getSizeX()), times); - } - - private SymbolTableEntry getStructureSizeSymbolTableEntry(SymbolTableEntry ste, String sbmlId){ - for (SymbolTableEntry bioSte : this.mathMapping.getMappedBiologicalSymbols()) { - if (!(bioSte instanceof StructureMapping.StructureMappingParameter param)) continue; - if (param.getRole() != StructureMapping.ROLE_Size || param.getStructure().getStructureSize() != ste) continue; - return param; - } - throw new RuntimeException("failed to find VCell structure size parameter for sbml compartment size: " + sbmlId); + return this.getDataForSBMLVar(mathVar, ste, identifierSet, spatialDimensions, times, utcSim); } - private double[] getDesiredTimes(VCDataIdentifier vcDId, UniformTimeCourse utcSim) throws DataAccessException { - double adjustedStartTime = utcSim.getOutputStartTime() - utcSim.getInitialTime(); - double[] finalTimes = new double[utcSim.getNumberOfSteps() + 1]; - double[] preTimes = this.dataSetController.getDataSetTimes(vcDId); - int startIndex = -1; - for (int i = 0; i < preTimes.length; i++) { - if (preTimes[i] != adjustedStartTime) continue; - startIndex = i; - break; + protected double[] getRawDataAtAllTimes(String varName) throws DataAccessException { + int meshSize = this.cartesianMesh.getSizeZ() * this.cartesianMesh.getSizeY() * this.cartesianMesh.getSizeX(); + double[] originalTimes = this.getOriginalTimes(); + double[] originalData = new double[meshSize * originalTimes.length]; + for (int i = 0; i < originalTimes.length; i++) { + SimDataBlock sdb = this.dataSetController.getSimDataBlock(this.DEFAULT_OUTPUT_CONTEXT, this.vcDId, varName, originalTimes[i]); + double[] flatDataBlock = sdb.getData(); + System.arraycopy(flatDataBlock, 0, originalData, i * meshSize, flatDataBlock.length); } - if (startIndex < 0) throw new IllegalArgumentException("Time `" + adjustedStartTime + "` not found"); - double[] subArray = Arrays.copyOfRange(preTimes, startIndex, preTimes.length); - double indexStep = (1.0 * subArray.length - 1) / (utcSim.getNumberOfSteps()); - if (indexStep % 1 != 0) - throw new RuntimeException("Found incompatible sampling with respect to requested output points: `" + subArray.length + "` vs `" + utcSim.getNumberOfSteps() + 1 + "`"); - for (int i = 0, j = 0; i < utcSim.getNumberOfSteps() + 1; i++, j += (int)indexStep) { - finalTimes[i] = subArray[j] + utcSim.getInitialTime(); - } - return finalTimes; - } - - private static DataSetControllerImpl initializeDataSetController(File userDir) throws FileNotFoundException { - Cachetable cachetable = new Cachetable(2000,1000000L); - return new DataSetControllerImpl(cachetable, userDir.getParentFile(), null); - } - - private static VCSimulationIdentifier initializeSimulationIdentifier(SimulationJob vcellSimJob, File userDir){ - User user = new User(userDir.getName(), null); - cbit.vcell.solver.Simulation vcellSim = vcellSimJob.getSimulation(); - return new VCSimulationIdentifier(vcellSim.getKey(), user); - } - - private static String createUniqueKey(String sbmlId, UniformTimeCourse utcSim){ - return String.format("%s@%s", sbmlId, utcSim.toString()); + return originalData; } } diff --git a/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLDataAccessor.java b/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLDataAccessor.java index ff69186464..a6f4708b87 100644 --- a/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLDataAccessor.java +++ b/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLDataAccessor.java @@ -3,7 +3,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.vcell.sbml.vcell.SBMLDataRecord; -import org.vcell.util.Pair; import java.util.List; import java.util.concurrent.Callable; @@ -16,7 +15,9 @@ public abstract class LazySBMLDataAccessor { private SBMLDataRecord cachedData; // We want to lazy get the data, to avoid as much in memory usage as possible private final Callable lazyMethod; - private int flatSize; + private final int flatSize; + private final List spatialDimensions; + private final List desiredTimes; /** * Creates a lazy accessor from runs of SBML-originated models @@ -24,8 +25,8 @@ public abstract class LazySBMLDataAccessor { * the data being accessed is not stored in RAM: that would defeat the purpose of lazy access. * @param flatSize the size of the (expected) data. */ - protected LazySBMLDataAccessor(Callable lazyMethod, int flatSize) { - this(lazyMethod, flatSize, true); + protected LazySBMLDataAccessor(Callable lazyMethod, int flatSize, List spatialDimensions, List desiredTimes) { + this(lazyMethod, flatSize, spatialDimensions, desiredTimes, true); } /** @@ -35,12 +36,14 @@ protected LazySBMLDataAccessor(Callable lazyMethod, int flatSize * @param flatSize the size of the (expected) data. * @param shouldUseCaching whether to cache the data collected by the lazyMethod or not. */ - protected LazySBMLDataAccessor(Callable lazyMethod, int flatSize, boolean shouldUseCaching) { + protected LazySBMLDataAccessor(Callable lazyMethod, int flatSize, List spatialDimensions, List desiredTimes, boolean shouldUseCaching) { if (lazyMethod == null) throw new IllegalArgumentException("lazyMethod cannot be null"); this.shouldUseCaching = shouldUseCaching; this.lazyMethod = lazyMethod; this.cachedData = null; this.flatSize = flatSize; + this.spatialDimensions = spatialDimensions; + this.desiredTimes = desiredTimes; } /** @@ -53,7 +56,7 @@ public SBMLDataRecord getData() throws Exception { if (this.cachedData != null) return this.cachedData; // We only set `cachedData` to a not-null value if we `shouldUseCaching` SBMLDataRecord result = this.lazyMethod.call(); int product = 1; for (int i : result.dimensions()) product *= i; - if (this.flatSize != product) lg.warn("Flat size mismatch: final calculated flat-size ({}) != set value ({})", product, this.flatSize); + //if (this.flatSize != product) lg.warn("Flat size mismatch: final calculated flat-size ({}) != set value ({})", product, this.flatSize); if (this.shouldUseCaching) this.cachedData = result; return result; } @@ -64,21 +67,7 @@ public SBMLDataRecord getData() throws Exception { */ public int getFlatSize() { return this.flatSize; } - /** - * Sets the flat size of the data to be lazily accessed. - * @param flatSize the value to set the size to - */ - public void setFlatSize(int flatSize) { this.flatSize = flatSize; } + public List getSpatialDimensions() { return this.spatialDimensions; } - /** - * Determines whether data provided by the method is flat, but has caveats: - * 1) The use of type `Boolean` is intentional; if the data is not cached, we return `null` - * 2) If we have cached the data, we return `true` if the data is considered "flat", otherwise `false` - * @return `true`, `false`, or `null` depending on both the initialization and current state of the object. - */ - public Boolean dataIsFlat(){ - if (this.cachedData == null) return null; - List dimensions = this.cachedData.dimensions(); - return dimensions == null || dimensions.isEmpty() || (dimensions.size() == 2 && dimensions.get(1) == 1); - } + public List getDesiredTimes() { return this.desiredTimes; } } diff --git a/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLNonSpatialDataAccessor.java b/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLNonSpatialDataAccessor.java index aea4d31d2f..f3be96e833 100644 --- a/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLNonSpatialDataAccessor.java +++ b/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLNonSpatialDataAccessor.java @@ -1,15 +1,17 @@ package org.vcell.sbml.vcell.lazy; import org.vcell.sbml.vcell.SBMLDataRecord; + +import java.util.List; import java.util.concurrent.Callable; public class LazySBMLNonSpatialDataAccessor extends LazySBMLDataAccessor { - public LazySBMLNonSpatialDataAccessor(Callable lazyMethod, int flatSize) { - this(lazyMethod, flatSize, true); + public LazySBMLNonSpatialDataAccessor(Callable lazyMethod, int flatSize, List desiredTimes) { + this(lazyMethod, flatSize, desiredTimes, true); } - public LazySBMLNonSpatialDataAccessor(Callable lazyMethod, int flatSize, boolean shouldUseCaching) { - super(lazyMethod, flatSize, shouldUseCaching); + public LazySBMLNonSpatialDataAccessor(Callable lazyMethod, int flatSize, List desiredTimes, boolean shouldUseCaching) { + super(lazyMethod, flatSize, List.of(1), desiredTimes, shouldUseCaching); } } diff --git a/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLSpatialDataAccessor.java b/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLSpatialDataAccessor.java index f8c17063f5..89f1adfc80 100644 --- a/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLSpatialDataAccessor.java +++ b/vcell-core/src/main/java/org/vcell/sbml/vcell/lazy/LazySBMLSpatialDataAccessor.java @@ -1,12 +1,14 @@ package org.vcell.sbml.vcell.lazy; import org.vcell.sbml.vcell.SBMLDataRecord; + +import java.util.List; import java.util.concurrent.Callable; public class LazySBMLSpatialDataAccessor extends LazySBMLDataAccessor { - public LazySBMLSpatialDataAccessor(Callable lazyMethod, int flatSize) { - super(lazyMethod, flatSize, false); // Can't allow caching; data sets can be MASSIVE! + public LazySBMLSpatialDataAccessor(Callable lazyMethod, int flatSize, List spatialDimensions, List desiredTimes) { + super(lazyMethod, flatSize, spatialDimensions, desiredTimes, false); // Can't allow caching; data sets can be MASSIVE! } } diff --git a/vcell-core/src/main/java/org/vcell/sedml/SEDMLImporter.java b/vcell-core/src/main/java/org/vcell/sedml/SEDMLImporter.java index 809987e47c..ef3670fb00 100644 --- a/vcell-core/src/main/java/org/vcell/sedml/SEDMLImporter.java +++ b/vcell-core/src/main/java/org/vcell/sedml/SEDMLImporter.java @@ -1003,15 +1003,16 @@ private void translateTimeBounds(SolverTaskDescription simTaskDesc, org.jlibsedm TimeBounds timeBounds; TimeStep timeStep = new TimeStep(); double outputTimeStep; // = 0.1; - int outputNumberOfPoints; // = 1; + int outputNumberOfSteps; // = 1; // we translate initial time to zero, we provide output for the duration of the simulation // because we can't select just an interval the way the SEDML simulation can double initialTime = ((UniformTimeCourse) sedmlSimulation).getInitialTime(); double outputStartTime = ((UniformTimeCourse) sedmlSimulation).getOutputStartTime(); double outputEndTime = ((UniformTimeCourse) sedmlSimulation).getOutputEndTime(); - outputNumberOfPoints = ((UniformTimeCourse) sedmlSimulation).getNumberOfSteps(); - outputTimeStep = (outputEndTime - outputStartTime) / outputNumberOfPoints; + outputNumberOfSteps = ((UniformTimeCourse) sedmlSimulation).getNumberOfSteps(); + outputTimeStep = (outputEndTime - outputStartTime) / outputNumberOfSteps; timeBounds = new TimeBounds(0, outputEndTime - initialTime); + timeBounds = new TimeBounds(outputStartTime - initialTime, outputEndTime - initialTime); OutputTimeSpec outputTimeSpec = new UniformOutputTimeSpec(outputTimeStep); simTaskDesc.setTimeBounds(timeBounds); @@ -1019,9 +1020,8 @@ private void translateTimeBounds(SolverTaskDescription simTaskDesc, org.jlibsedm if (simTaskDesc.getSolverDescription().supports(outputTimeSpec)) { simTaskDesc.setOutputTimeSpec(outputTimeSpec); } else { - simTaskDesc.setOutputTimeSpec(new DefaultOutputTimeSpec(1,Integer.max(DefaultOutputTimeSpec.DEFAULT_KEEP_AT_MOST, outputNumberOfPoints))); + simTaskDesc.setOutputTimeSpec(new DefaultOutputTimeSpec(1,Integer.max(DefaultOutputTimeSpec.DEFAULT_KEEP_AT_MOST, outputNumberOfSteps))); } - } private void translateAlgorithmParams(SolverTaskDescription simTaskDesc, org.jlibsedml.Simulation sedmlSimulation) throws PropertyVetoException { diff --git a/vcell-core/src/main/java/org/vcell/sedml/testsupport/OmexTestingDatabase.java b/vcell-core/src/main/java/org/vcell/sedml/testsupport/OmexTestingDatabase.java index e9aa881892..cd6eb59fec 100644 --- a/vcell-core/src/main/java/org/vcell/sedml/testsupport/OmexTestingDatabase.java +++ b/vcell-core/src/main/java/org/vcell/sedml/testsupport/OmexTestingDatabase.java @@ -1,6 +1,7 @@ package org.vcell.sedml.testsupport; import cbit.vcell.mapping.MappingException; +import cbit.vcell.solver.SolverException; import com.fasterxml.jackson.databind.MappingIterator; import com.fasterxml.jackson.databind.ObjectMapper; import org.vcell.sbml.vcell.SBMLImportException; @@ -124,6 +125,10 @@ private static FailureType determineFault(TraceEvent traceEvent){ if (traceEvent.message.contains("infinite loop")) return FailureType.SOLVER_FAILURE; } + if (traceEvent.exception instanceof SolverException){ + return FailureType.SOLVER_FAILURE; + } + if (traceEvent.exception != null && traceEvent.message.toLowerCase().contains("non-compatible sedml simulations")) return FailureType.SEDML_NON_UTC_SIMULATION_FOUND; diff --git a/vcell-core/src/main/java/org/vcell/solver/comsol/ComsolSolver.java b/vcell-core/src/main/java/org/vcell/solver/comsol/ComsolSolver.java index 5b201fe45c..22b7a07682 100644 --- a/vcell-core/src/main/java/org/vcell/solver/comsol/ComsolSolver.java +++ b/vcell-core/src/main/java/org/vcell/solver/comsol/ComsolSolver.java @@ -41,38 +41,7 @@ public ComsolSolver(SimulationTask simTask, File directory) throws SolverExcepti @Override public void startSolver() { - try { - setSolverStatus(new SolverStatus(SolverStatus.SOLVER_STARTING, SimulationMessage.MESSAGE_SOLVER_STARTING_INIT)); - initialize(); - setSolverStatus(new SolverStatus(SolverStatus.SOLVER_RUNNING, SimulationMessage.MESSAGE_SOLVER_RUNNING_START)); - fireSolverStarting(SimulationMessage.MESSAGE_SOLVEREVENT_STARTING); - String filePrefix = simTask.getSimulationJob().getSimulationJobID(); - try { - comsolService.connectComsol(); - File reportFile = new File(getSaveDirectory(), filePrefix+".comsoldat"); - File javaFile = new File(getSaveDirectory(), filePrefix+".java"); - File mphFile = new File(getSaveDirectory(), filePrefix+".mph"); - comsolService.solve(this.vccModel, reportFile, javaFile, mphFile); - }finally{ - comsolService.disconnectComsol(); - } - File logFile = new File(getSaveDirectory(),filePrefix+".log"); - FileUtils.writeStringToFile(logFile, createLogFileContents(simTask.getSimulation().getSolverTaskDescription())); - VisMesh visMesh = new VisMesh(); - File reportFile = new File(getSaveDirectory(),filePrefix+".comsoldat"); - ComsolMeshReader.read(visMesh, reportFile); - - time = simTask.getSimulation().getSolverTaskDescription().getTimeBounds().getEndingTime(); - progress = 1.0; - fireSolverPrinted(time); - fireSolverProgress(progress); - setSolverStatus(new SolverStatus(SolverStatus.SOLVER_FINISHED, SimulationMessage.MESSAGE_SOLVER_FINISHED)); - fireSolverFinished(); - }catch (Exception e){ - lg.error(e.getMessage(), e); - setSolverStatus(new SolverStatus (SolverStatus.SOLVER_ABORTED, SimulationMessage.solverAborted(e.getMessage()))); - fireSolverAborted(SimulationMessage.solverAborted(e.getMessage())); - } + this.runSolver(); } public static String createLogFileContents(SolverTaskDescription solverTaskDescription){ @@ -113,7 +82,43 @@ public static double[] getTimesFromLogFile(File logFile) throws IOException{ public void stopSolver() { comsolService.disconnectComsol(); } - + + @Override + public void runSolver() { + try { + setSolverStatus(new SolverStatus(SolverStatus.SOLVER_STARTING, SimulationMessage.MESSAGE_SOLVER_STARTING_INIT)); + initialize(); + setSolverStatus(new SolverStatus(SolverStatus.SOLVER_RUNNING, SimulationMessage.MESSAGE_SOLVER_RUNNING_START)); + fireSolverStarting(SimulationMessage.MESSAGE_SOLVEREVENT_STARTING); + String filePrefix = simTask.getSimulationJob().getSimulationJobID(); + try { + comsolService.connectComsol(); + File reportFile = new File(getSaveDirectory(), filePrefix+".comsoldat"); + File javaFile = new File(getSaveDirectory(), filePrefix+".java"); + File mphFile = new File(getSaveDirectory(), filePrefix+".mph"); + comsolService.solve(this.vccModel, reportFile, javaFile, mphFile); + }finally{ + comsolService.disconnectComsol(); + } + File logFile = new File(getSaveDirectory(),filePrefix+".log"); + FileUtils.writeStringToFile(logFile, createLogFileContents(simTask.getSimulation().getSolverTaskDescription())); + VisMesh visMesh = new VisMesh(); + File reportFile = new File(getSaveDirectory(),filePrefix+".comsoldat"); + ComsolMeshReader.read(visMesh, reportFile); + + time = simTask.getSimulation().getSolverTaskDescription().getTimeBounds().getEndingTime(); + progress = 1.0; + fireSolverPrinted(time); + fireSolverProgress(progress); + setSolverStatus(new SolverStatus(SolverStatus.SOLVER_FINISHED, SimulationMessage.MESSAGE_SOLVER_FINISHED)); + fireSolverFinished(); + }catch (Exception e){ + lg.error(e.getMessage(), e); + setSolverStatus(new SolverStatus (SolverStatus.SOLVER_ABORTED, SimulationMessage.solverAborted(e.getMessage()))); + fireSolverAborted(SimulationMessage.solverAborted(e.getMessage())); + } + } + @Override public double getCurrentTime() { return time; diff --git a/vcell-core/src/main/java/org/vcell/trace/Span.java b/vcell-core/src/main/java/org/vcell/trace/Span.java index f71b43f849..e3b857e314 100644 --- a/vcell-core/src/main/java/org/vcell/trace/Span.java +++ b/vcell-core/src/main/java/org/vcell/trace/Span.java @@ -3,7 +3,7 @@ import java.util.HashMap; import java.util.Map; -public class Span { +public class Span implements AutoCloseable { public enum ContextType { Root, diff --git a/vcell-core/src/main/java/org/vcell/util/exe/Executable.java b/vcell-core/src/main/java/org/vcell/util/exe/Executable.java index e13c02a358..1f0010f969 100644 --- a/vcell-core/src/main/java/org/vcell/util/exe/Executable.java +++ b/vcell-core/src/main/java/org/vcell/util/exe/Executable.java @@ -193,7 +193,7 @@ protected void executeProcess(int[] expectedReturnCodes) throws org.vcell.util.e } } catch (Exception e) { String processName = (command != null && command.length > 0) ? '"' +command[0] + '"' : ""; - logger.error(String.format("Command %s ecountered a problem: ", processName)+": "+e.getMessage(), e); + logger.error(String.format("Command %s encountered a problem: ", processName)+": "+e.getMessage(), e); if (getStatus().isError()) { // process failed and we relay the exception thrown on error status finish above throw new ExecutableException(e.getMessage() + "\n\n(" + getCommand() + ")", e); diff --git a/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/SolverPreprocessor.java b/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/SolverPreprocessor.java index 414d4058c0..7e8b61999c 100644 --- a/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/SolverPreprocessor.java +++ b/vcell-server/src/main/java/cbit/vcell/message/server/batch/sim/SolverPreprocessor.java @@ -125,6 +125,10 @@ public void startSolver() { } public void stopSolver() { } + + @Override + public void runSolver() {} + public double getCurrentTime() { return 0; }