diff --git a/pyproject.toml b/pyproject.toml index b591f37..f8096a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "libvcell" -version = "0.0.6" +version = "0.0.7" description = "This is a python package which wraps a subset of VCell Java code as a native python package." authors = ["Jim Schaff ", "Ezequiel Valencia "] repository = "https://github.com/virtualcell/libvcell" diff --git a/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java b/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java index 0f64d59..35916bf 100644 --- a/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java +++ b/vcell-native/src/main/java/org/vcell/libvcell/Entrypoints.java @@ -52,7 +52,7 @@ public String toJson() { documentation = """ Converts VCML file into Finite Volume Input files. vcml_content: text of VCML XML document - output_dir_path: path to the output directory + output_dir_path: path to the output directory (expected to be subdirectory of the workspace) Returns a JSON string with success status and message""" ) public static CCharPointer entrypoint_vcmlToFiniteVolumeInput( @@ -65,7 +65,9 @@ public static CCharPointer entrypoint_vcmlToFiniteVolumeInput( String vcmlContentStr = CTypeConversion.toJavaString(vcml_content); String simulationName = CTypeConversion.toJavaString(simulation_name); String outputDirPathStr = CTypeConversion.toJavaString(output_dir_path); - vcmlToFiniteVolumeInput(vcmlContentStr, simulationName, new File(outputDirPathStr)); + File outputDir = new File(outputDirPathStr); + File parentDir = outputDir.getParentFile(); + vcmlToFiniteVolumeInput(vcmlContentStr, simulationName, parentDir, outputDir); returnValue = new ReturnValue(true, "Success"); }catch (Throwable t) { logger.error("Error processing spatial model", t); diff --git a/vcell-native/src/main/java/org/vcell/libvcell/MainRecorder.java b/vcell-native/src/main/java/org/vcell/libvcell/MainRecorder.java index a6cf023..f3c7f92 100644 --- a/vcell-native/src/main/java/org/vcell/libvcell/MainRecorder.java +++ b/vcell-native/src/main/java/org/vcell/libvcell/MainRecorder.java @@ -20,6 +20,7 @@ public static void main(String[] args) { File vcml_file = new File(args[1]); String vcml_sim_name = args[2]; File output_dir = new File(args[3]); + File parent_dir = output_dir.getParentFile(); logger.info("Logger logging"); PropertyLoader.setProperty(PropertyLoader.vcellServerIDProperty, "none"); PropertyLoader.setProperty(PropertyLoader.mongodbDatabase, "none"); @@ -35,7 +36,7 @@ public static void main(String[] args) { byte[] data = f_vcml.readAllBytes(); logger.info("Read " + data.length + " bytes from " + vcml_file.getAbsolutePath()); String vcml_str = new String(data); - vcmlToFiniteVolumeInput(vcml_str, vcml_sim_name, output_dir); + vcmlToFiniteVolumeInput(vcml_str, vcml_sim_name, parent_dir, output_dir); } // use reflection to load jsbml classes and call their default constructors diff --git a/vcell-native/src/main/java/org/vcell/libvcell/SolverUtils.java b/vcell-native/src/main/java/org/vcell/libvcell/SolverUtils.java index d80698a..31352c7 100644 --- a/vcell-native/src/main/java/org/vcell/libvcell/SolverUtils.java +++ b/vcell-native/src/main/java/org/vcell/libvcell/SolverUtils.java @@ -2,34 +2,42 @@ import cbit.util.xml.VCLoggerException; import cbit.vcell.biomodel.BioModel; +import cbit.vcell.field.FieldDataIdentifierSpec; +import cbit.vcell.field.FieldFunctionArguments; +import cbit.vcell.field.FieldUtilities; import cbit.vcell.geometry.GeometrySpec; import cbit.vcell.mapping.MappingException; import cbit.vcell.mapping.SimulationContext; +import cbit.vcell.math.MathException; +import cbit.vcell.messaging.server.SimulationTask; import cbit.vcell.mongodb.VCMongoMessage; import cbit.vcell.parser.ExpressionException; -import cbit.vcell.solver.Simulation; -import cbit.vcell.solver.SolverException; -import cbit.vcell.solver.TimeBounds; -import cbit.vcell.solver.UniformOutputTimeSpec; +import cbit.vcell.solver.*; import cbit.vcell.xml.XMLSource; import cbit.vcell.xml.XmlHelper; import cbit.vcell.xml.XmlParseException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.vcell.libvcell.solvers.LocalFVSolverStandalone; import org.vcell.sbml.FiniteVolumeRunUtil; import org.vcell.sbml.vcell.SBMLExporter; import org.vcell.sbml.vcell.SBMLImporter; +import org.vcell.util.document.ExternalDataIdentifier; +import org.vcell.util.document.KeyValue; +import org.vcell.util.document.User; import java.beans.PropertyVetoException; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; public class SolverUtils { private static final Logger logger = LogManager.getLogger(Entrypoints.class); - public static void vcmlToFiniteVolumeInput(String vcml_content, String simulation_name, File outputDir) throws XmlParseException, MappingException, SolverException, ExpressionException { + public static void vcmlToFiniteVolumeInput(String vcml_content, String simulation_name, File parentDir, File outputDir) throws XmlParseException, MappingException, SolverException, ExpressionException, MathException { GeometrySpec.avoidAWTImageCreation = true; VCMongoMessage.enabled = false; if (vcml_content.substring(0, 300).contains(" fdiSpecList = new ArrayList<>(); + for (FieldFunctionArguments fieldFuncArg : fieldFuncArgs) { + if (fieldFuncArg != null) { + String name = fieldFuncArg.getFieldName(); + File fieldDataDir = new File(parentDir, name); + if (!fieldDataDir.exists()) { + throw new IllegalArgumentException("Field data directory does not exist: " + fieldDataDir.getAbsolutePath()); + } + // search fieldDataDir for files with name pattern SimID__* and extract the key + KeyValue key = null; + for (File f: fieldDataDir.listFiles()) { + String[] filename_parts = f.getName().split("_"); + if (filename_parts.length < 3) { + continue; + } + if (filename_parts[0].equals("SimID")) { + key = new KeyValue(filename_parts[1]); + break; + } + } + if (key == null) { + throw new IllegalArgumentException("Field data directory does not contain a file with key: " + name); + } + ExternalDataIdentifier extDataId = new ExternalDataIdentifier(key, User.tempUser, name); + fdiSpecList.add(new FieldDataIdentifierSpec(fieldFuncArg, extDataId)); + } + } + fdiSpecs = fdiSpecList.toArray(new FieldDataIdentifierSpec[fdiSpecList.size()]); + } + return fdiSpecs; } diff --git a/vcell-native/src/main/java/org/vcell/libvcell/solvers/LocalFVSolverStandalone.java b/vcell-native/src/main/java/org/vcell/libvcell/solvers/LocalFVSolverStandalone.java new file mode 100644 index 0000000..fe8426e --- /dev/null +++ b/vcell-native/src/main/java/org/vcell/libvcell/solvers/LocalFVSolverStandalone.java @@ -0,0 +1,257 @@ +package org.vcell.libvcell.solvers; + +import cbit.vcell.field.FieldDataIdentifierSpec; +import cbit.vcell.field.FieldFunctionArguments; +import cbit.vcell.field.FieldUtilities; +import cbit.vcell.math.MathException; +import cbit.vcell.math.Variable; +import cbit.vcell.math.VariableType; +import cbit.vcell.messaging.server.SimulationTask; +import cbit.vcell.parser.Expression; +import cbit.vcell.parser.ExpressionException; +import cbit.vcell.simdata.*; +import cbit.vcell.solver.AnnotatedFunction; +import cbit.vcell.solver.Simulation; +import cbit.vcell.solver.SimulationJob; +import cbit.vcell.solver.SolverException; +import cbit.vcell.solver.server.SimulationMessage; +import cbit.vcell.solver.server.SolverStatus; +import cbit.vcell.solver.test.MathTestingUtilities; +import cbit.vcell.solvers.*; +import org.vcell.util.DataAccessException; +import org.vcell.util.ISize; +import org.vcell.util.document.ExternalDataIdentifier; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.*; + + +public class LocalFVSolverStandalone extends FVSolverStandalone { + final File dataDir; + final File parentDir; + + public LocalFVSolverStandalone(SimulationTask simulationTask, File dataDir) throws SolverException { + super(simulationTask, dataDir, false); + this.dataDir = dataDir; + this.parentDir = dataDir.getParentFile(); + } + + @Override + public void initialize() throws SolverException { + try { + Simulation sim = simTask.getSimulation(); + if(sim.isSerialParameterScan()){ + //write functions file for all the simulations in the scan + if (sim.getJobCount() != sim.getScanCount()){ + throw new SolverException("using scanIndex for JobIndex, assuming no trials"); + } + for(int scan = 0; scan < sim.getScanCount(); scan++){ + SimulationJob simJob = new SimulationJob(sim, scan, simTask.getSimulationJob().getFieldDataIdentifierSpecs()); + // ** Dumping the functions of a simulation into a '.functions' file. + String basename = new File(getSaveDirectory(), simJob.getSimulationJobID()).getPath(); + String functionFileName = basename + FUNCTIONFILE_EXTENSION; + + Vector funcList = simJob.getSimulationSymbolTable().createAnnotatedFunctionsList(simTask.getSimulation().getMathDescription()); + File existingFunctionFile = new File(functionFileName); + if(existingFunctionFile.exists()){ + Vector oldFuncList = FunctionFileGenerator.readFunctionsFile(existingFunctionFile, simTask.getSimulationJobID()); + for(AnnotatedFunction func : oldFuncList){ + if(func.isOldUserDefined()){ + funcList.add(func); + } + } + } + + //Try to save existing user defined functions + FunctionFileGenerator functionFileGenerator = new FunctionFileGenerator(functionFileName, funcList); + try { + functionFileGenerator.generateFunctionFile(); + } catch(Exception e){ + throw new RuntimeException("Error creating .function file for " + functionFileGenerator.getBasefileName() + e.getMessage(), e); + } + } + + } else { + writeFunctionsFile(); + } + + writeVCGAndResampleFieldData(); + + setSolverStatus(new SolverStatus(SolverStatus.SOLVER_RUNNING, SimulationMessage.MESSAGE_SOLVER_RUNNING_INIT)); + fireSolverStarting(SimulationMessage.MESSAGE_SOLVEREVENT_STARTING_INIT); + + setSolverStatus(new SolverStatus(SolverStatus.SOLVER_RUNNING, SimulationMessage.MESSAGE_SOLVER_RUNNING_INPUT_FILE)); + + File fvinputFile = new File(getBaseName() + ".fvinput"); + PrintWriter pw = null; + try { + pw = new PrintWriter(new FileWriter(fvinputFile)); + LocalFiniteVolumeFileWriter writer = new LocalFiniteVolumeFileWriter(pw, simTask, getResampledGeometry(), dataDir); + writer.write(); + } finally { + if(pw != null){ + pw.close(); + } + } + } catch(Exception ex){ + throw new SolverException(ex.getMessage(), ex); + } + } + + @Override + public void writeVCGAndResampleFieldData() throws SolverException { + try { + // write subdomains file + String baseName = new File(getBaseName()).getName(); + SubdomainInfo.write(new File(getSaveDirectory(), baseName + SimDataConstants.SUBDOMAINS_FILE_SUFFIX), simTask.getSimulation().getMathDescription()); + + PrintWriter pw = new PrintWriter(new FileWriter(new File(getSaveDirectory(), baseName + SimDataConstants.VCG_FILE_EXTENSION))); + GeometryFileWriter.write(pw, getResampledGeometry()); + pw.close(); + + FieldDataIdentifierSpec[] argFieldDataIDSpecs = simTask.getSimulationJob().getFieldDataIdentifierSpecs(); + if(argFieldDataIDSpecs != null && argFieldDataIDSpecs.length > 0){ + + FieldFunctionArguments psfFieldFunc = null; + Variable var = simTask.getSimulationJob().getSimulationSymbolTable().getVariable(Simulation.PSF_FUNCTION_NAME); + if(var != null){ + FieldFunctionArguments[] ffas = FieldUtilities.getFieldFunctionArguments(var.getExpression()); + if(ffas == null || ffas.length == 0){ + throw new DataAccessException("Point Spread Function " + Simulation.PSF_FUNCTION_NAME + " can only be a single field function."); + } else { + Expression newexp; + try { + newexp = new Expression(ffas[0].infix()); + if(!var.getExpression().compareEqual(newexp)){ + throw new DataAccessException("Point Spread Function " + Simulation.PSF_FUNCTION_NAME + " can only be a single field function."); + } + psfFieldFunc = ffas[0]; + } catch(ExpressionException e){ + throw new DataAccessException(e.getMessage(), e); + } + } + } + + boolean bResample[] = new boolean[argFieldDataIDSpecs.length]; + Arrays.fill(bResample, true); + for(int i = 0; i < argFieldDataIDSpecs.length; i++){ + argFieldDataIDSpecs[i].getFieldFuncArgs().getTime().bindExpression(simTask.getSimulationJob().getSimulationSymbolTable()); + if(argFieldDataIDSpecs[i].getFieldFuncArgs().equals(psfFieldFunc)){ + bResample[i] = false; + } + } + + int numMembraneElements = getResampledGeometry().getGeometrySurfaceDescription().getSurfaceCollection().getTotalPolygonCount(); + CartesianMesh simpleMesh = CartesianMesh.createSimpleCartesianMesh(getResampledGeometry().getOrigin(), + getResampledGeometry().getExtent(), + simTask.getSimulation().getMeshSpecification().getSamplingSize(), + getResampledGeometry().getGeometrySurfaceDescription().getRegionImage()); + + writeFieldFunctionData(null, argFieldDataIDSpecs, bResample, simpleMesh, numMembraneElements); + } + } catch(Exception e){ + throw new SolverException(e.getMessage()); + } + } + + public void writeFieldFunctionData(OutputContext outputContext, FieldDataIdentifierSpec[] argFieldDataIDSpecs, + boolean[] bResampleFlags, CartesianMesh newMesh, + int simResampleMembraneDataLength) throws DataAccessException, ExpressionException { + + if(argFieldDataIDSpecs == null || argFieldDataIDSpecs.length == 0) return; + + HashMap uniqueFieldDataIDSpecAndFileH = new HashMap<>(); + HashMap bFieldDataResample = new HashMap<>(); + int i=0; + for (FieldDataIdentifierSpec fdiSpec: argFieldDataIDSpecs) { + File ext_dataDir = new File(this.parentDir, fdiSpec.getFieldFuncArgs().getFieldName()); + if (!uniqueFieldDataIDSpecAndFileH.containsKey(fdiSpec)){ + File newResampledFieldDataFile = new File(dataDir, SimulationData.createCanonicalResampleFileName(fdiSpec.getExternalDataIdentifier(), fdiSpec.getFieldFuncArgs())); + uniqueFieldDataIDSpecAndFileH.put(fdiSpec,newResampledFieldDataFile); + bFieldDataResample.put(fdiSpec, bResampleFlags[i]); + } + i++; + } + try { + Set> resampleSet = uniqueFieldDataIDSpecAndFileH.entrySet(); + for (Map.Entry resampleEntry : resampleSet) { + if (resampleEntry.getValue().exists()) { + continue; + } + FieldDataIdentifierSpec fieldDataIdSpec = resampleEntry.getKey(); + FieldFunctionArguments fieldFuncArgs = fieldDataIdSpec.getFieldFuncArgs(); + File dataDir = new File(this.parentDir, fieldFuncArgs.getFieldName()); + boolean bResample = bFieldDataResample.get(fieldDataIdSpec); + CartesianMesh origMesh = getMesh(fieldDataIdSpec.getExternalDataIdentifier()); + VCData vcData = new SimulationData(fieldDataIdSpec.getExternalDataIdentifier(), dataDir, dataDir, null); + SimDataBlock simDataBlock = vcData.getSimDataBlock(outputContext, fieldFuncArgs.getVariableName(), fieldFuncArgs.getTime().evaluateConstant()); + VariableType varType = fieldFuncArgs.getVariableType(); + VariableType dataVarType = simDataBlock.getVariableType(); + if (!varType.equals(VariableType.UNKNOWN) && !varType.equals(dataVarType)) { + throw new IllegalArgumentException("field function variable type (" + varType.getTypeName() + ") doesn't match real variable type (" + dataVarType.getTypeName() + ")"); + } + double[] origData = simDataBlock.getData(); + double[] newData = null; + CartesianMesh resampleMesh = newMesh; + if (!bResample) { + if (resampleMesh.getGeometryDimension() != origMesh.getGeometryDimension()) { + throw new DataAccessException("Field data " + fieldFuncArgs.getFieldName() + " (" + origMesh.getGeometryDimension() + + "D) should have same dimension as simulation mesh (" + resampleMesh.getGeometryDimension() + "D) because it is not resampled to simulation mesh (e.g. Point Spread Function)"); + } + newData = origData; + resampleMesh = origMesh; + } else { + if (CartesianMesh.isSpatialDomainSame(origMesh, resampleMesh)) { + newData = origData; + if (simDataBlock.getVariableType().equals(VariableType.MEMBRANE)) { + if (origData.length != simResampleMembraneDataLength) { + throw new Exception("FieldData variable \"" + fieldFuncArgs.getVariableName() + + "\" (" + simDataBlock.getVariableType().getTypeName() + ") " + + "resampling failed: Membrane Data lengths must be equal" + ); + } + } else if (!simDataBlock.getVariableType().equals(VariableType.VOLUME)) { + throw new Exception("FieldData variable \"" + fieldFuncArgs.getVariableName() + + "\" (" + simDataBlock.getVariableType().getTypeName() + ") " + + "resampling failed: Only Volume and Membrane variable types are supported" + ); + } + } else { + if (!simDataBlock.getVariableType().compareEqual(VariableType.VOLUME)) { + throw new Exception("FieldData variable \"" + fieldFuncArgs.getVariableName() + + "\" (" + simDataBlock.getVariableType().getTypeName() + ") " + + "resampling failed: Only VOLUME FieldData variable type allowed when\n" + + "FieldData spatial domain does not match Simulation spatial domain.\n" + + "Check dimension, xsize, ysize, zsize, origin and extent are equal." + ); + } + if (origMesh.getSizeY() == 1 && origMesh.getSizeZ() == 1) { + newData = MathTestingUtilities.resample1DSpatialSimple(origData, origMesh, resampleMesh); + } else if (origMesh.getSizeZ() == 1) { + newData = MathTestingUtilities.resample2DSpatialSimple(origData, origMesh, resampleMesh); + } else { + newData = MathTestingUtilities.resample3DSpatialSimple(origData, origMesh, resampleMesh); + } + } + } + DataSet.writeNew(resampleEntry.getValue(), + new String[]{fieldFuncArgs.getVariableName()}, + new VariableType[]{simDataBlock.getVariableType()}, + new ISize(resampleMesh.getSizeX(), resampleMesh.getSizeY(), resampleMesh.getSizeZ()), + new double[][]{newData}); + } + } catch (Exception ex) { + throw new DataAccessException(ex.getMessage(), ex); + } + } + public CartesianMesh getMesh(ExternalDataIdentifier extDataID) throws MathException, IOException { + File dataDir = new File(this.parentDir, extDataID.getName()); + File meshFile = new File(dataDir, SimulationData.createCanonicalMeshFileName(extDataID.getKey(),extDataID.getJobIndex(), false)); + File meshMetricsFile = new File(dataDir, SimulationData.createCanonicalMeshMetricsFileName(extDataID.getKey(),extDataID.getJobIndex(), false)); + File subDomainFile = new File(dataDir, SimulationData.createCanonicalSubdomainFileName(extDataID.getKey(),extDataID.getJobIndex(), false)); + return CartesianMesh.readFromFiles(meshFile, meshMetricsFile, subDomainFile); + } +} diff --git a/vcell-native/src/main/java/org/vcell/libvcell/solvers/LocalFiniteVolumeFileWriter.java b/vcell-native/src/main/java/org/vcell/libvcell/solvers/LocalFiniteVolumeFileWriter.java new file mode 100644 index 0000000..aebd22c --- /dev/null +++ b/vcell-native/src/main/java/org/vcell/libvcell/solvers/LocalFiniteVolumeFileWriter.java @@ -0,0 +1,107 @@ +package org.vcell.libvcell.solvers; + +import cbit.vcell.field.FieldDataIdentifierSpec; +import cbit.vcell.field.FieldFunctionArguments; +import cbit.vcell.field.FieldUtilities; +import cbit.vcell.geometry.Geometry; +import cbit.vcell.math.Variable; +import cbit.vcell.math.VariableType; +import cbit.vcell.messaging.server.SimulationTask; +import cbit.vcell.parser.Expression; +import cbit.vcell.parser.ExpressionException; +import cbit.vcell.simdata.SimDataBlock; +import cbit.vcell.simdata.SimulationData; +import cbit.vcell.simdata.VCData; +import cbit.vcell.solver.Simulation; +import cbit.vcell.solvers.FiniteVolumeFileWriter; +import org.vcell.util.DataAccessException; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashSet; + +public class LocalFiniteVolumeFileWriter extends FiniteVolumeFileWriter { + + public LocalFiniteVolumeFileWriter(PrintWriter pw, SimulationTask simTask, Geometry geo, File workingDir) { + super(pw, simTask, geo, workingDir); + } + + @Override + protected void writeFieldData() throws ExpressionException, DataAccessException { + FieldDataIdentifierSpec[] fieldDataIDSpecs = simTask.getSimulationJob().getFieldDataIdentifierSpecs(); + if(fieldDataIDSpecs == null || fieldDataIDSpecs.length == 0){ + return; + } + + printWriter.println("# Field Data"); + printWriter.println("FIELD_DATA_BEGIN"); + printWriter.println("#id, type, new name, name, varname, time, filename"); + + FieldFunctionArguments psfFieldFunc = null; + + Variable var = simTask.getSimulationJob().getSimulationSymbolTable().getVariable(Simulation.PSF_FUNCTION_NAME); + if(var != null){ + FieldFunctionArguments[] ffas = FieldUtilities.getFieldFunctionArguments(var.getExpression()); + if(ffas == null || ffas.length == 0){ + throw new DataAccessException("Point Spread Function " + Simulation.PSF_FUNCTION_NAME + " can only be a single field function."); + } else { + Expression newexp = new Expression(ffas[0].infix()); + if(!var.getExpression().compareEqual(newexp)){ + throw new DataAccessException("Point Spread Function " + Simulation.PSF_FUNCTION_NAME + " can only be a single field function."); + } + psfFieldFunc = ffas[0]; + } + } + + int index = 0; + HashSet uniqueFieldDataIDSpecs = new HashSet<>(); + uniqueFieldDataNSet = new HashSet<>(); + for (FieldDataIdentifierSpec fieldDataIDSpec : fieldDataIDSpecs) { + if (!uniqueFieldDataIDSpecs.contains(fieldDataIDSpec)) { + FieldFunctionArguments ffa = fieldDataIDSpec.getFieldFuncArgs(); + File newResampledFieldDataFile = new File(workingDirectory, + SimulationData.createCanonicalResampleFileName(simTask.getSimulationJob().getVCDataIdentifier(), + fieldDataIDSpec.getFieldFuncArgs()) + ); + uniqueFieldDataIDSpecs.add(fieldDataIDSpec); + VariableType varType = fieldDataIDSpec.getFieldFuncArgs().getVariableType(); + final VariableType dataVarType; + try { + File ext_data_dir = new File(workingDirectory.getParentFile(), ffa.getFieldName()); + VCData vcData = new SimulationData(fieldDataIDSpec.getExternalDataIdentifier(), ext_data_dir, ext_data_dir, null); + SimDataBlock simDataBlock = vcData.getSimDataBlock(null, ffa.getVariableName(), ffa.getTime().evaluateConstant()); + dataVarType = simDataBlock.getVariableType(); + } catch (IOException e) { + throw new DataAccessException("Error reading field data file: " + e.getMessage()); + } + if (varType.equals(VariableType.UNKNOWN)) { + varType = dataVarType; + } else if (!varType.equals(dataVarType)) { + throw new IllegalArgumentException("field function variable type (" + varType.getTypeName() + ") doesn't match real variable type (" + dataVarType.getTypeName() + ")"); + } + if (psfFieldFunc != null && psfFieldFunc.equals(ffa)) { + psfFieldIndex = index; + } + String fieldDataID = "_VCell_FieldData_" + index; + printWriter.println(index + " " + varType.getTypeName() + " " + fieldDataID + " " + ffa.getFieldName() + " " + ffa.getVariableName() + " " + ffa.getTime().flatten().infix() + " " + newResampledFieldDataFile); + uniqueFieldDataNSet.add( + new FieldDataNumerics( + SimulationData.createCanonicalFieldFunctionSyntax( + ffa.getFieldName(), + ffa.getVariableName(), + ffa.getTime().evaluateConstant(), + ffa.getVariableType().getTypeName()), + fieldDataID)); + index++; + } + } + + if(psfFieldIndex >= 0){ + printWriter.println("PSF_FIELD_DATA_INDEX " + psfFieldIndex); + } + printWriter.println("FIELD_DATA_END"); + printWriter.println(); + } + +} diff --git a/vcell-native/src/test/java/org/vcell/libvcell/EntrypointsTest.java b/vcell-native/src/test/java/org/vcell/libvcell/EntrypointsTest.java index dc35fb9..c602e45 100644 --- a/vcell-native/src/test/java/org/vcell/libvcell/EntrypointsTest.java +++ b/vcell-native/src/test/java/org/vcell/libvcell/EntrypointsTest.java @@ -2,9 +2,12 @@ import cbit.util.xml.VCLoggerException; import cbit.vcell.mapping.MappingException; +import cbit.vcell.math.MathException; import cbit.vcell.parser.ExpressionException; import cbit.vcell.solver.SolverException; import cbit.vcell.xml.XmlParseException; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.junit.jupiter.api.Test; import java.beans.PropertyVetoException; @@ -12,10 +15,12 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Objects; +import java.util.UUID; import java.util.stream.Collectors; +import java.util.zip.GZIPInputStream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.vcell.libvcell.SolverUtils.sbmlToFiniteVolumeInput; import static org.vcell.libvcell.SolverUtils.vcmlToFiniteVolumeInput; @@ -24,8 +29,9 @@ public class EntrypointsTest { @Test public void testSbmlToFiniteVolumeInput() throws PropertyVetoException, SolverException, ExpressionException, MappingException, VCLoggerException, IOException { String sbmlContent = getFileContentsAsString("/TinySpatialProject_Application0.xml"); - Path output_dir = Files.createTempDirectory("sbmlToFiniteVolumeInput"); - sbmlToFiniteVolumeInput(sbmlContent, output_dir.toFile()); + File output_dir = Files.createTempDirectory("sbmlToFiniteVolumeInput").toFile(); + sbmlToFiniteVolumeInput(sbmlContent, output_dir); + assertEquals(4, countFiles(output_dir)); } // TODO: better exception handling by JSBML needed @@ -49,20 +55,65 @@ public void testSbmlToFiniteVolumeInput_vcml_instead() throws IOException { } @Test - public void testVcmlToFiniteVolumeInput() throws SolverException, ExpressionException, MappingException, IOException, XmlParseException { + public void testVcmlToFiniteVolumeInput_field_data() throws SolverException, ExpressionException, MappingException, IOException, XmlParseException, MathException, InterruptedException { + String vcmlContent = getFileContentsAsString("/FieldDataDemo.vcml"); + File parent_dir = Files.createTempDirectory("vcmlToFiniteVolumeInput_"+UUID.randomUUID()).toFile(); + File output_dir = new File(parent_dir, "output_dir"); + File ext_data_dir = new File(parent_dir, "test2_lsm_DEMO"); + assertEquals(0, countFiles(ext_data_dir)); + extractTgz(EntrypointsTest.class.getResourceAsStream("/test2_lsm_DEMO.tgz"), parent_dir); + listFilesInDirectory(ext_data_dir); + assertEquals(10, countFiles(ext_data_dir)); + + assertEquals(0, countFiles(output_dir)); + String simulationName = "Simulation0"; + vcmlToFiniteVolumeInput(vcmlContent, simulationName, parent_dir, output_dir); + assertEquals(10, countFiles(ext_data_dir)); + assertEquals(6, countFiles(output_dir)); + } + + @Test + public void testVcmlToFiniteVolumeInput_field_data_not_found() throws SolverException, ExpressionException, MappingException, IOException, XmlParseException, MathException, InterruptedException { + String vcmlContent = getFileContentsAsString("/FieldDataDemo.vcml"); + File parent_dir = Files.createTempDirectory("vcmlToFiniteVolumeInput_"+UUID.randomUUID()).toFile(); + File output_dir = new File(parent_dir, "output_dir"); + File ext_data_dir = new File(parent_dir, "test2_lsm_DEMO"); + File ext_data_dir_MISSPELLED = new File(parent_dir, "test2_lsm_DEMO_MISSPELLED"); + assertEquals(0, countFiles(ext_data_dir)); + assertEquals(0, countFiles(ext_data_dir_MISSPELLED)); + extractTgz(EntrypointsTest.class.getResourceAsStream("/test2_lsm_DEMO.tgz"), parent_dir); + Files.move(ext_data_dir.toPath(), ext_data_dir_MISSPELLED.toPath()).toFile(); + assertEquals(0, countFiles(ext_data_dir)); + listFilesInDirectory(ext_data_dir_MISSPELLED); + assertEquals(10, countFiles(ext_data_dir_MISSPELLED)); + + assertEquals(10, countFiles(ext_data_dir_MISSPELLED)); + assertEquals(0, countFiles(output_dir)); + String simulationName = "Simulation0"; + IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> vcmlToFiniteVolumeInput(vcmlContent, simulationName, parent_dir, output_dir)); + assertTrue(exc.getMessage().contains("Field data directory does not exist") && exc.getMessage().contains(ext_data_dir.getName())); + assertEquals(10, countFiles(ext_data_dir_MISSPELLED)); + assertEquals(0, countFiles(output_dir)); + } + + @Test + public void testVcmlToFiniteVolumeInput() throws SolverException, ExpressionException, MappingException, IOException, XmlParseException, MathException { String vcmlContent = getFileContentsAsString("/TinySpatialProject_Application0.vcml"); - Path output_dir = Files.createTempDirectory("vcmlToFiniteVolumeInput"); + File parent_dir = Files.createTempDirectory("vcmlToFiniteVolumeInput").toFile(); + File output_dir = new File(parent_dir, "output_dir"); String simulationName = "Simulation0"; - vcmlToFiniteVolumeInput(vcmlContent, simulationName, output_dir.toFile()); + vcmlToFiniteVolumeInput(vcmlContent, simulationName, parent_dir, output_dir); + assertEquals(4, countFiles(output_dir)); } @Test public void testVcmlToFiniteVolumeInput_bad_simname() throws IOException { String vcmlContent = getFileContentsAsString("/TinySpatialProject_Application0.vcml"); - Path output_dir = Files.createTempDirectory("vcmlToFiniteVolumeInput"); + File parent_dir = Files.createTempDirectory("vcmlToFiniteVolumeInput").toFile(); + File output_dir = new File(parent_dir, "output_dir"); String simulationName = "wrong_sim_name"; // expect to throw an IllegalArgumentException - IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> vcmlToFiniteVolumeInput(vcmlContent, simulationName, output_dir.toFile())); + IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> vcmlToFiniteVolumeInput(vcmlContent, simulationName, parent_dir, output_dir)); assertEquals("Simulation not found: wrong_sim_name", exc.getMessage()); } @@ -70,10 +121,11 @@ public void testVcmlToFiniteVolumeInput_bad_simname() throws IOException { public void testVcmlToFiniteVolumeInput_not_well_formed() throws IOException { String vcmlContent = getFileContentsAsString("/TinySpatialProject_Application0.vcml") .replaceAll(" vcmlToFiniteVolumeInput(vcmlContent, simulationName, output_dir.toFile())); + RuntimeException exc = assertThrows(RuntimeException.class, () -> vcmlToFiniteVolumeInput(vcmlContent, simulationName, parent_dir, output_dir)); assertEquals( "source document is not well-formed\n" + "Error on line 25: The element type \"SimpleReactionXYZ\" must be terminated by the matching end-tag \"\".", @@ -83,10 +135,11 @@ public void testVcmlToFiniteVolumeInput_not_well_formed() throws IOException { @Test public void testVcmlToFiniteVolumeInput_sbml_instead() throws IOException { String vcmlContent_actually_sbml = getFileContentsAsString("/TinySpatialProject_Application0.xml"); - Path output_dir = Files.createTempDirectory("vcmlToFiniteVolumeInput"); + File parent_dir = Files.createTempDirectory("vcmlToFiniteVolumeInput").toFile(); + File output_dir = new File(parent_dir, "output_dir"); String simulationName = "wrong_sim_name"; // expect to throw an IllegalArgumentException - IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> vcmlToFiniteVolumeInput(vcmlContent_actually_sbml, simulationName, output_dir.toFile())); + IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> vcmlToFiniteVolumeInput(vcmlContent_actually_sbml, simulationName, parent_dir, output_dir)); assertEquals("expecting VCML content, not SBML", exc.getMessage()); } @@ -99,4 +152,66 @@ private static String getFileContentsAsString(String filename) throws IOExceptio .lines().collect(Collectors.joining("\n")); } } + + private static byte[] getFileContentsAsBytes(String filename) throws IOException { + try (InputStream inputStream = EntrypointsTest.class.getResourceAsStream(filename)) { + if (inputStream == null) { + throw new FileNotFoundException("file not found! " + filename); + } + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) != -1) { + byteArrayOutputStream.write(buffer, 0, length); + } + return byteArrayOutputStream.toByteArray(); + } + } + } + + private int countFiles(File dir) { + File[] files = dir.listFiles(); + if (files == null) { + return 0; + } + return Objects.requireNonNull(dir.listFiles()).length; + } + + private void listFilesInDirectory(File dir) { + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + System.out.println(file.getAbsolutePath()); + } + } + } + + public static void extractTgz(InputStream tgzFileStream, File outputDir) throws IOException { + try (GZIPInputStream gis = new GZIPInputStream(tgzFileStream); + TarArchiveInputStream tis = new TarArchiveInputStream(gis)) { + + TarArchiveEntry entry; + while ((entry = tis.getNextTarEntry()) != null) { + File outputFile = new File(outputDir, entry.getName()); + if (entry.isDirectory()) { + if (!outputFile.exists()) { + outputFile.mkdirs(); + } + } else { + File parent = outputFile.getParentFile(); + if (!parent.exists()) { + parent.mkdirs(); + } + try (OutputStream os = Files.newOutputStream(outputFile.toPath())) { + byte[] buffer = new byte[1024]; + int len; + while ((len = tis.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + } + } + } + } + } + } diff --git a/vcell-native/src/test/resources/FieldDataDemo.vcml b/vcell-native/src/test/resources/FieldDataDemo.vcml new file mode 100644 index 0000000..1315673 --- /dev/null +++ b/vcell-native/src/test/resources/FieldDataDemo.vcml @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + NoName + 789CEDDCDB4EC330100450F3FF3F8D901020133B5E7BD67B9B79A4713CA7694392B6698D61188661188661D2E6E34FE423753A29E5A38B74AC562F8DF45651FB8D178365FE5B25ED63619FAC526C14EEA355FC3A8EC17DB68A775131B8006CDB1C763D03ABB47508EDC8BA53DA3B7768DDAAEC5B3BB66E3676CC855B3D6B15B06EB913EB4957C02AF051B2BAFC3734B39ED674A69D5201253D71E756484537DA172BA8A10BEE1B15D6CF81F6DD0AAB875EDF7681AB5CD4EAF6662FC15DB6E2FA596925D4E85CA915D610FCE4096634D9BA77B59B5458C78B1BF7880A6A79490BA002B5382EC6A5A4055F5FD6B442DFBAD07579C52237AE7F6CC37943601B685F15050BD186C10E7655B279C2589FDEBAD2A902615BB76AF95C91B0A79FBF685A558E6A4F660B873DD0AA5A6F63E733EA5295CECD362756B66A9D886AD7DE8A92D5A7560DEB497BE19B98C6C2DF7C97D1B4BAD1EA22FF692D9F884BD436FD3A7A3EEC34A5B077B4D6C89F94C27EA596B6453CAB3B4A29AC22D71AF69C525825ADB56A1062B36275B4D6A85188CD8AD5D05A9326A9642536AB15AEB5E6CC432CB109B4686C2DADB5671A34B696D6DA334D296C2D6D292C0F2CD262B15A6BCC6B4A61915A6BCA7B884D6A2536ABB51416662D85B586AC845862890D654561AD194B29447DC60EFE1CDEDAABC68F24B0CE7E9959C9DA3F1ADEDA5E5A67B5BE2E10DDFAFEB3F144D885CE69AC2BB76E49835DAA9C0DBBB85868EC6AE14CD8D5E56A600F6F86E12282BAE1B192B6D1B1A2B2C1B1C2B2A1B5E2AA81B1F2A671B11B45C36377C644C5EE8C898ADD1A1410BB754394B0D89D109B153BD05AB7524AA94D3BC0E6D4124B6C828CB019B5436B42ECD84A6CEC54B296DA3D553C589C7D952851FED0D25BBB4B39C9B1DDA59CE4D82E95AC0CC3300CC3308C6A3E01FE4F236A + + + + + + NoName + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vcField('test2_lsm_DEMO', 'species0_cyt', 0.5, 'Volume') + 5.0 + + + vcField('test2_lsm_DEMO', 'species0_ec', 0.5, 'Volume') + 5.0 + + + + 96485.3321 + 9.64853321E-5 + 1.0E-9 + 6.02214179E11 + 3.141592653589793 + 8314.46261815 + 300.0 + 1.0 + 1000.0 + 0.001660538783162726 + 5.0 + 5.0 + 1.0 + 0.2 + 0.0 + 1.0 + 1.0 + + + (VolumePerUnitVolume_cyt * vcRegionVolume('cytosol')) + (VolumePerUnitVolume_ec * vcRegionVolume('extracellular')) + (AreaPerUnitArea_pm * vcRegionArea('cytosol_extracellular_membrane')) + vcRegionArea('cytosol_extracellular_membrane') + (vcField('test2_lsm_DEMO', 'Channel0', 2.3108693333333576, 'Volume') / vcField('test2_lsm_DEMO', 'Channel0', 0.0, 'Volume')) + (vcField('test2_lsm_DEMO', 'Channel0', 2.3108693333333576, 'Volume') / vcField('test2_lsm_DEMO', 'Channel0', 0.0, 'Volume')) + vcRegionVolume('cytosol') + vcRegionVolume('extracellular') + + + + + + + + + 0.0 + species0_ec_diffusionRate + species0_ec_init_uM + + + + + + + + + + + 0.0 + species0_cyt_diffusionRate + species0_cyt_init_uM + + + + + + + + + + + 0.0 + 0.0 + + + 0.0 + 0.0 + + + + + + + + + + + + + + + 2 + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vcell-native/src/test/resources/test2_lsm_DEMO.tgz b/vcell-native/src/test/resources/test2_lsm_DEMO.tgz new file mode 100644 index 0000000..f95819b Binary files /dev/null and b/vcell-native/src/test/resources/test2_lsm_DEMO.tgz differ diff --git a/vcell_submodule b/vcell_submodule index e03192e..ac098f0 160000 --- a/vcell_submodule +++ b/vcell_submodule @@ -1 +1 @@ -Subproject commit e03192e882fb958f9136330c24c60ed2ce80d967 +Subproject commit ac098f087c19997fca9074a65e6cbcbefbc15d46