diff options
Diffstat (limited to 'var')
6 files changed, 1176 insertions, 60 deletions
diff --git a/var/spack/repos/builtin/packages/catalyst/package.py b/var/spack/repos/builtin/packages/catalyst/package.py index d9be7b17dd..03f9e4dbcf 100644 --- a/var/spack/repos/builtin/packages/catalyst/package.py +++ b/var/spack/repos/builtin/packages/catalyst/package.py @@ -7,6 +7,7 @@ from spack import * import os import subprocess import llnl.util.tty as tty +from spack.patch import absolute_path_for_package class Catalyst(CMakePackage): @@ -33,17 +34,31 @@ class Catalyst(CMakePackage): variant('python', default=False, description='Enable Python support') variant('essentials', default=False, description='Enable Essentials support') variant('extras', default=False, description='Enable Extras support') - variant('rendering', default=False, description='Enable Vtk Rendering support') + variant('rendering', default=False, description='Enable VTK Rendering support') + variant('osmesa', default=True, description='Use offscreen rendering') - depends_on('git') + depends_on('git', type='build') depends_on('mpi') depends_on('python@2:2.8', when='+python', type=("build", "link", "run")) depends_on('python', when='~python', type=("build")) depends_on('mesa', when='+rendering') - depends_on("libx11", when='+rendering') - depends_on("libxt", when='+rendering') + depends_on('libx11', when='+rendering') + depends_on('libxt', when='+rendering') depends_on('cmake@3.3:', type='build') + @when('@5.5.0:5.5.2') + def patch(self): + """Apply the patch (it should be fixed in Paraview 5.6) + at the package dir to the source code in + root_cmakelists_dir.""" + patch_name = 'vtkm-catalyst-pv551.patch' + pkg_dir = os.path.dirname(absolute_path_for_package(self)) + patch = which("patch", required=True) + with working_dir(self.root_cmakelists_dir): + patch('-s', '-p', '1', '-i', + join_path(pkg_dir, patch_name), + "-d", '.') + def url_for_version(self, version): """Handle ParaView version-based custom URLs.""" if version < Version('5.1.0'): @@ -53,6 +68,30 @@ class Catalyst(CMakePackage): else: return self._urlfmt_xz.format(version.up_to(2), version, '') + @property + def paraview_subdir(self): + """The paraview subdirectory name as paraview-major.minor""" + return 'paraview-{0}'.format(self.spec.version.up_to(2)) + + @property + def editions(self): + """Transcribe spack variants into names of Catalyst Editions""" + selected = ['Base'] # Always required + + if '+python' in self.spec: + selected.append('Enable-Python') + + if '+essentials' in self.spec: + selected.append('Essentials') + + if '+extras' in self.spec: + selected.append('Extras') + + if '+rendering' in self.spec: + selected.append('Rendering-Base') + + return selected + def do_stage(self, mirror_only=False): """Unpacks and expands the fetched tarball. Then, generate the catalyst source files.""" @@ -62,38 +101,15 @@ class Catalyst(CMakePackage): paraview_dir = os.path.join(self.stage.path, 'ParaView-v' + str(self.version)) catalyst_script = os.path.join(paraview_dir, 'Catalyst', 'catalyze.py') + editions_dir = os.path.join(paraview_dir, 'Catalyst', 'Editions') catalyst_source_dir = os.path.abspath(self.root_cmakelists_dir) command = ['python', catalyst_script, - '-r', paraview_dir] + '-r', paraview_dir, + '-o', catalyst_source_dir] - catalyst_edition = os.path.join(paraview_dir, 'Catalyst', - 'Editions', 'Base') - command.append('-i') - command.append(catalyst_edition) - if '+python' in self.spec: - catalyst_edition = os.path.join(paraview_dir, 'Catalyst', - 'Editions', 'Enable-Python') - command.append('-i') - command.append(catalyst_edition) - if '+essentials' in self.spec: - catalyst_edition = os.path.join(paraview_dir, 'Catalyst', - 'Editions', 'Essentials') - command.append('-i') - command.append(catalyst_edition) - if '+extras' in self.spec: - catalyst_edition = os.path.join(paraview_dir, 'Catalyst', - 'Editions', 'Extras') - command.append('-i') - command.append(catalyst_edition) - if '+rendering' in self.spec: - catalyst_edition = os.path.join(paraview_dir, 'Catalyst', - 'Editions', 'Rendering-Base') - command.append('-i') - command.append(catalyst_edition) - - command.append('-o') - command.append(catalyst_source_dir) + for edition in self.editions: + command.extend(['-i', os.path.join(editions_dir, edition)]) if not os.path.isdir(catalyst_source_dir): os.mkdir(catalyst_source_dir) @@ -104,15 +120,29 @@ class Catalyst(CMakePackage): self.stage.path)) def setup_environment(self, spack_env, run_env): + # paraview 5.5 and later + # - cmake under lib/cmake/paraview-5.5 + # - libs under lib + # - python bits under lib/python2.8/site-packages if os.path.isdir(self.prefix.lib64): lib_dir = self.prefix.lib64 else: lib_dir = self.prefix.lib - paraview_version = 'paraview-%s' % self.spec.version.up_to(2) - run_env.prepend_path('LIBRARY_PATH', join_path(lib_dir, - paraview_version)) - run_env.prepend_path('LD_LIBRARY_PATH', join_path(lib_dir, - paraview_version)) + + if self.spec.version <= Version('5.4.1'): + lib_dir = join_path(lib_dir, paraview_subdir) + run_env.set('ParaView_DIR', self.prefix) + run_env.prepend_path('LIBRARY_PATH', lib_dir) + run_env.prepend_path('LD_LIBRARY_PATH', lib_dir) + + if '+python' in self.spec: + python_version = self.spec['python'].version.up_to(2) + run_env.prepend_path('PYTHONPATH', join_path(lib_dir, + 'python{0}'.format(python_version), + 'site-packages')) + + def setup_dependent_environment(self, spack_env, run_env, dependent_spec): + spack_env.set('ParaView_DIR', self.prefix) @property def root_cmakelists_dir(self): @@ -136,8 +166,24 @@ class Catalyst(CMakePackage): def cmake_args(self): """Populate cmake arguments for Catalyst.""" + spec = self.spec + + def variant_bool(feature, on='ON', off='OFF'): + """Ternary for spec variant to ON/OFF string""" + if feature in spec: + return on + return off + + def nvariant_bool(feature): + """Negated ternary for spec variant to OFF/ON string""" + return variant_bool(feature, on='OFF', off='ON') + cmake_args = [ - '-DPARAVIEW_GIT_DESCRIBE=v%s' % str(self.version) + '-DPARAVIEW_GIT_DESCRIBE=v%s' % str(self.version), + '-DVTK_USE_SYSTEM_EXPAT:BOOL=ON', + '-DVTK_USE_X:BOOL=%s' % nvariant_bool('+osmesa'), + '-DVTK_USE_OFFSCREEN:BOOL=%s' % variant_bool('+osmesa'), + '-DVTK_OPENGL_HAS_OSMESA:BOOL=%s' % variant_bool('+osmesa') ] return cmake_args diff --git a/var/spack/repos/builtin/packages/catalyst/vtkm-catalyst-pv551.patch b/var/spack/repos/builtin/packages/catalyst/vtkm-catalyst-pv551.patch new file mode 100644 index 0000000000..fa610722af --- /dev/null +++ b/var/spack/repos/builtin/packages/catalyst/vtkm-catalyst-pv551.patch @@ -0,0 +1,487 @@ +# The catalyst changes (the working directory for output) are slated for +# paraview-5.6. +# They are API-compatible with paraview-5.5 but not ABI compatible. +# +# https://gitlab.kitware.com/paraview/paraview/merge_requests/2433 +# https://gitlab.kitware.com/paraview/paraview/merge_requests/2436 +# +--- Catalyst-v5.5.0/CoProcessing/Catalyst/vtkCPProcessor.cxx.orig 2018-04-06 22:03:33.000000000 +0200 ++++ Catalyst-v5.5.0/CoProcessing/Catalyst/vtkCPProcessor.cxx 2018-05-11 12:02:26.894772713 +0200 +@@ -38,6 +38,7 @@ + #include "vtkStringArray.h" + + #include <list> ++#include <vtksys/SystemTools.hxx> + + struct vtkCPProcessorInternals + { +@@ -47,12 +48,13 @@ + }; + + vtkStandardNewMacro(vtkCPProcessor); +-vtkMultiProcessController* vtkCPProcessor::Controller = NULL; ++vtkMultiProcessController* vtkCPProcessor::Controller = nullptr; + //---------------------------------------------------------------------------- + vtkCPProcessor::vtkCPProcessor() + { + this->Internal = new vtkCPProcessorInternals; +- this->InitializationHelper = NULL; ++ this->InitializationHelper = nullptr; ++ this->WorkingDirectory = nullptr; + } + + //---------------------------------------------------------------------------- +@@ -61,14 +63,15 @@ + if (this->Internal) + { + delete this->Internal; +- this->Internal = NULL; ++ this->Internal = nullptr; + } + + if (this->InitializationHelper) + { + this->InitializationHelper->Delete(); +- this->InitializationHelper = NULL; ++ this->InitializationHelper = nullptr; + } ++ this->SetWorkingDirectory(nullptr); + } + + //---------------------------------------------------------------------------- +@@ -95,7 +98,7 @@ + { + if (which < 0 || which >= this->GetNumberOfPipelines()) + { +- return NULL; ++ return nullptr; + } + int counter = 0; + vtkCPProcessorInternals::PipelineListIterator iter = this->Internal->Pipelines.begin(); +@@ -108,7 +111,7 @@ + counter++; + iter++; + } +- return NULL; ++ return nullptr; + } + + //---------------------------------------------------------------------------- +@@ -130,17 +133,41 @@ + } + + //---------------------------------------------------------------------------- +-int vtkCPProcessor::Initialize() ++int vtkCPProcessor::Initialize(const char* workingDirectory) + { +- if (this->InitializationHelper == NULL) ++ if (this->InitializationHelper == nullptr) + { + this->InitializationHelper = this->NewInitializationHelper(); + } ++ // make sure the directory exists here so that we only do it once ++ if (workingDirectory) ++ { ++ vtkMultiProcessController* controller = vtkMultiProcessController::GetGlobalController(); ++ int success = 1; ++ if (controller == nullptr || controller->GetLocalProcessId() == 0) ++ { ++ success = vtksys::SystemTools::MakeDirectory(workingDirectory) == true ? 1 : 0; ++ if (success == 0) ++ { ++ vtkWarningMacro("Could not make " ++ << workingDirectory << " directory. " ++ << "Results will be generated in current working directory instead."); ++ } ++ } ++ if (controller) ++ { ++ controller->Broadcast(&success, 1, 0); ++ } ++ if (success) ++ { ++ this->SetWorkingDirectory(workingDirectory); ++ } ++ } + return 1; + } + + //---------------------------------------------------------------------------- +-int vtkCPProcessor::Initialize(vtkMPICommunicatorOpaqueComm& comm) ++int vtkCPProcessor::Initialize(vtkMPICommunicatorOpaqueComm& comm, const char* workingDirectory) + { + #ifdef PARAVIEW_USE_MPI + if (vtkCPProcessor::Controller) +@@ -148,7 +175,7 @@ + vtkErrorMacro("Can only initialize with a communicator once per process."); + return 0; + } +- if (this->InitializationHelper == NULL) ++ if (this->InitializationHelper == nullptr) + { + vtkMPICommunicator* communicator = vtkMPICommunicator::New(); + communicator->InitializeExternal(&comm); +@@ -157,12 +184,12 @@ + this->Controller = controller; + this->Controller->SetGlobalController(controller); + communicator->Delete(); +- return this->Initialize(); ++ return this->Initialize(workingDirectory); + } + return 1; + #else + static_cast<void>(&comm); // get rid of variable not used warning +- return this->Initialize(); ++ return this->Initialize(workingDirectory); + #endif + } + +@@ -225,6 +252,13 @@ + input->GetFieldData()->AddArray(catalystChannel); + } + } ++ ++ std::string originalWorkingDirectory; ++ if (this->WorkingDirectory) ++ { ++ originalWorkingDirectory = vtksys::SystemTools::GetCurrentWorkingDirectory(); ++ vtksys::SystemTools::ChangeDirectory(this->WorkingDirectory); ++ } + for (vtkCPProcessorInternals::PipelineListIterator iter = this->Internal->Pipelines.begin(); + iter != this->Internal->Pipelines.end(); iter++) + { +@@ -248,6 +282,10 @@ + } + } + } ++ if (originalWorkingDirectory.empty() == false) ++ { ++ vtksys::SystemTools::ChangeDirectory(originalWorkingDirectory); ++ } + // we want to reset everything here to make sure that new information + // is properly passed in the next time. + dataDescription->ResetAll(); +@@ -259,7 +297,7 @@ + { + if (this->Controller) + { +- this->Controller->SetGlobalController(NULL); ++ this->Controller->SetGlobalController(nullptr); + this->Controller->Finalize(1); + this->Controller->Delete(); + } +--- Catalyst-v5.5.0/CoProcessing/Catalyst/vtkCPProcessor.h.orig 2018-04-06 22:03:33.000000000 +0200 ++++ Catalyst-v5.5.0/CoProcessing/Catalyst/vtkCPProcessor.h 2018-05-11 12:02:26.894772713 +0200 +@@ -76,14 +76,16 @@ + virtual void RemoveAllPipelines(); + + /// Initialize the co-processor. Returns 1 if successful and 0 +- /// otherwise. + /// otherwise. If Catalyst is built with MPI then Initialize() + /// can also be called with a specific MPI communicator if + /// MPI_COMM_WORLD isn't the proper one. Catalyst is initialized +- /// to use MPI_COMM_WORLD by default. +- virtual int Initialize(); ++ /// to use MPI_COMM_WORLD by default. Both methods have an optional ++ /// workingDirectory argument which will set *WorkingDirectory* so ++ /// that files will be put relative to this directory. ++ virtual int Initialize(const char* workingDirectory = nullptr); + #ifndef __WRAP__ +- virtual int Initialize(vtkMPICommunicatorOpaqueComm& comm); ++ virtual int Initialize( ++ vtkMPICommunicatorOpaqueComm& comm, const char* workingDirectory = nullptr); + #endif + + /// The Catalyst input field data string array name. This array will +@@ -111,6 +113,13 @@ + /// implementation an opportunity to clean up, before it is destroyed. + virtual int Finalize(); + ++ /// Get the current working directory for outputting Catalyst files. ++ /// If not set then Catalyst output files will be relative to the ++ /// current working directory. This will not affect where Catalyst ++ /// looks for Python scripts. *WorkingDirectory* gets set through ++ /// the *Initialize()* methods. ++ vtkGetStringMacro(WorkingDirectory); ++ + protected: + vtkCPProcessor(); + virtual ~vtkCPProcessor(); +@@ -118,6 +127,11 @@ + /// Create a new instance of the InitializationHelper. + virtual vtkObject* NewInitializationHelper(); + ++ /// Set the current working directory for outputting Catalyst files. ++ /// This is a protected method since simulation code adaptors should ++ /// set this through the *Initialize()* methods. ++ vtkSetStringMacro(WorkingDirectory); ++ + private: + vtkCPProcessor(const vtkCPProcessor&) = delete; + void operator=(const vtkCPProcessor&) = delete; +@@ -125,6 +139,7 @@ + vtkCPProcessorInternals* Internal; + vtkObject* InitializationHelper; + static vtkMultiProcessController* Controller; ++ char* WorkingDirectory; + }; + + #endif +--- Catalyst-v5.5.0/CoProcessing/Catalyst/vtkCPXMLPWriterPipeline.cxx.orig 2018-04-06 22:03:33.000000000 +0200 ++++ Catalyst-v5.5.0/CoProcessing/Catalyst/vtkCPXMLPWriterPipeline.cxx 2018-05-11 12:02:26.894772713 +0200 +@@ -31,6 +31,7 @@ + #include <vtkSmartPointer.h> + #include <vtkUnstructuredGrid.h> + ++#include <algorithm> + #include <sstream> + #include <string> + +@@ -174,7 +175,7 @@ + + for (unsigned int i = 0; i < dataDescription->GetNumberOfInputDescriptions(); i++) + { +- const char* inputName = dataDescription->GetInputDescriptionName(i); ++ std::string inputName = dataDescription->GetInputDescriptionName(i); + vtkCPInputDataDescription* idd = dataDescription->GetInputDescription(i); + vtkDataObject* grid = idd->GetGrid(); + if (grid == nullptr) +@@ -206,6 +207,8 @@ + vtkSMStringVectorProperty* fileName = + vtkSMStringVectorProperty::SafeDownCast(writer->GetProperty("FileName")); + ++ // If we have a / in the channel name we take it out of the filename we're going to write to ++ inputName.erase(std::remove(inputName.begin(), inputName.end(), '/'), inputName.end()); + std::ostringstream o; + if (this->Path.empty() == false) + { +--- Catalyst-v5.5.0/Wrapping/Python/paraview/coprocessing.py.orig 2018-04-06 22:03:33.000000000 +0200 ++++ Catalyst-v5.5.0/Wrapping/Python/paraview/coprocessing.py 2018-05-11 12:02:27.038772408 +0200 +@@ -11,22 +11,12 @@ + from paraview.vtk.vtkPVVTKExtensionsCore import * + import math + +-# ----------------------------------------------------------------------------- +-def IsInModulo(timestep, frequencyArray): +- """ +- Return True if the given timestep is in one of the provided frequency. +- This can be interpreted as follow:: +- +- isFM = IsInModulo(timestep, [2,3,7]) +- +- is similar to:: ++# If the user created a filename in a location that doesn't exist by default we'll ++# make the directory for them. This can be changed though by setting createDirectoriesIfNeeded ++# to False. ++createDirectoriesIfNeeded = True + +- isFM = (timestep % 2 == 0) or (timestep % 3 == 0) or (timestep % 7 == 0) +- """ +- for frequency in frequencyArray: +- if frequency > 0 and (timestep % frequency == 0): +- return True +- return False ++# ----------------------------------------------------------------------------- + + class CoProcessor(object): + """Base class for co-processing Pipelines. +@@ -68,6 +58,9 @@ + self.__CinemaTracks = {} + self.__InitialFrequencies = {} + self.__PrintEnsightFormatString = False ++ self.__TimeStepToStartOutputAt=0 ++ self.__ForceOutputAtFirstCall=False ++ self.__FirstTimeStepIndex = None + + def SetPrintEnsightFormatString(self, enable): + """If outputting ExodusII files with the purpose of reading them into +@@ -87,6 +80,17 @@ + "Incorrect argument type: %s, must be a dict" % type(frequencies)) + self.__InitialFrequencies = frequencies + ++ def SetInitialOutputOptions(self, timeStepToStartOutputAt, forceOutputAtFirstCall): ++ """Set the frequencies at which the pipeline needs to be updated. ++ Typically, this is called by the subclass once it has determined what ++ timesteps co-processing will be needed to be done. ++ frequencies is a map, with key->string name of for the simulation ++ input, and value is a list of frequencies. ++ """ ++ ++ self.__TimeStepToStartOutputAt=timeStepToStartOutputAt ++ self.__ForceOutputAtFirstCall=forceOutputAtFirstCall ++ + def EnableLiveVisualization(self, enable, frequency = 1): + """Call this method to enable live-visualization. When enabled, + DoLiveVisualization() will communicate with ParaView server if possible +@@ -115,7 +119,7 @@ + # if this is a time step to do live then all of the inputs + # must be made available. note that we want the pipeline built + # before we do the actual first live connection. +- if self.__EnableLiveVisualization and timestep % self.__LiveVisualizationFrequency == 0 \ ++ if self.__EnableLiveVisualization and self.NeedToOutput(timestep, self.__LiveVisualizationFrequency) \ + and self.__LiveVisualizationLink: + if self.__LiveVisualizationLink.Initialize(servermanager.ActiveConnection.Session.GetSessionProxyManager()): + num_inputs = datadescription.GetNumberOfInputDescriptions() +@@ -132,13 +136,13 @@ + # hasn't been set up yet). If we don't have live enabled + # we know that the output frequencies aren't changed and can + # just use the initial frequencies. +- if self.__InitialFrequencies or not self.__EnableLiveVisualization: ++ if self.__ForceOutputAtFirstCall or self.__InitialFrequencies or not self.__EnableLiveVisualization: + num_inputs = datadescription.GetNumberOfInputDescriptions() + for cc in range(num_inputs): + input_name = datadescription.GetInputDescriptionName(cc) + + freqs = self.__InitialFrequencies.get(input_name, []) +- if self.__EnableLiveVisualization or ( self and IsInModulo(timestep, freqs) ): ++ if self.__EnableLiveVisualization or ( self and self.IsInModulo(timestep, freqs) ): + datadescription.GetInputDescription(cc).AllFieldsOn() + datadescription.GetInputDescription(cc).GenerateMeshOn() + else: +@@ -149,15 +153,14 @@ + for writer in self.__WritersList: + frequency = writer.parameters.GetProperty( + "WriteFrequency").GetElement(0) +- if (timestep % frequency) == 0 or \ +- datadescription.GetForceOutput() == True: ++ if self.NeedToOutput(timestep, frequency) or datadescription.GetForceOutput() == True: + writerinputs = cpstate.locate_simulation_inputs(writer) + for writerinput in writerinputs: + datadescription.GetInputDescriptionByName(writerinput).AllFieldsOn() + datadescription.GetInputDescriptionByName(writerinput).GenerateMeshOn() + + for view in self.__ViewsList: +- if (view.cpFrequency and timestep % view.cpFrequency == 0) or \ ++ if (view.cpFrequency and self.NeedToOutput(timestep, view.cpFrequency)) or \ + datadescription.GetForceOutput() == True: + viewinputs = cpstate.locate_simulation_inputs_for_view(view) + for viewinput in viewinputs: +@@ -192,8 +195,7 @@ + for writer in self.__WritersList: + frequency = writer.parameters.GetProperty( + "WriteFrequency").GetElement(0) +- if (timestep % frequency) == 0 or \ +- datadescription.GetForceOutput() == True: ++ if self.NeedToOutput(timestep, frequency) or datadescription.GetForceOutput() == True: + fileName = writer.parameters.GetProperty("FileName").GetElement(0) + paddingamount = writer.parameters.GetProperty("PaddingAmount").GetElement(0) + helperName = writer.GetXMLName() +@@ -203,6 +205,23 @@ + else: + ts = str(timestep).rjust(paddingamount, '0') + writer.FileName = fileName.replace("%t", ts) ++ if '/' in writer.FileName and createDirectoriesIfNeeded: ++ oktowrite = [1.] ++ import vtk ++ comm = vtk.vtkMultiProcessController.GetGlobalController() ++ if comm.GetLocalProcessId() == 0: ++ import os ++ newDir = writer.FileName[0:writer.FileName.rfind('/')] ++ try: ++ os.makedirs(newDir) ++ except OSError: ++ if not os.path.isdir(newDir): ++ print ("ERROR: Cannot make directory for", writer.FileName, ". No data will be written.") ++ oktowrite[0] = 0. ++ comm.Broadcast(oktowrite, 1, 0) ++ if oktowrite[0] == 0: ++ # we can't make the directory so no reason to update the pipeline ++ return + writer.UpdatePipeline(datadescription.GetTime()) + + def WriteImages(self, datadescription, rescale_lookuptable=False, +@@ -240,7 +259,7 @@ + + cinema_dirs = [] + for view in self.__ViewsList: +- if (view.cpFrequency and timestep % view.cpFrequency == 0) or \ ++ if (view.cpFrequency and self.NeedToOutput(timestep, view.cpFrequency)) or \ + datadescription.GetForceOutput() == True: + fname = view.cpFileName + ts = str(timestep).rjust(padding_amount, '0') +@@ -267,6 +286,24 @@ + if dirname: + cinema_dirs.append(dirname) + else: ++ if '/' in fname and createDirectoriesIfNeeded: ++ oktowrite = [1.] ++ import vtk ++ comm = vtk.vtkMultiProcessController.GetGlobalController() ++ if comm.GetLocalProcessId() == 0: ++ import os ++ newDir = fname[0:fname.rfind('/')] ++ try: ++ os.makedirs(newDir) ++ except OSError: ++ if not os.path.isdir(newDir): ++ print ("ERROR: Cannot make directory for", fname, ". No image will be output.") ++ oktowrite[0] = 0. ++ comm.Broadcast(oktowrite, 1, 0) ++ if oktowrite[0] == 0: ++ # we can't make the directory so no reason to update the pipeline ++ return ++ + if image_quality is None and fname.endswith('png'): + # for png quality = 0 means no compression. compression can be a potentially + # very costly serial operation on process 0 +@@ -307,7 +344,7 @@ + + + timeStep = datadescription.GetTimeStep() +- if self.__EnableLiveVisualization and timeStep % self.__LiveVisualizationFrequency == 0: ++ if self.__EnableLiveVisualization and self.NeedToOutput(timeStep, self.__LiveVisualizationFrequency): + if not self.__LiveVisualizationLink.Initialize(servermanager.ActiveConnection.Session.GetSessionProxyManager()): + return + +@@ -412,7 +449,7 @@ + """ + controller = servermanager.ParaViewPipelineController() + # assume that a client only proxy with the same name as a writer +- # is available in "insitu_writer_paramters" ++ # is available in "insitu_writer_parameters" + + # Since coprocessor sometimes pass writer as a custom object and not + # a proxy, we need to handle that. Just creating any arbitrary writer +@@ -666,3 +703,42 @@ + #restore what we showed + pv_introspect.restore_visibility(pxystate) + return os.path.basename(vfname) ++ ++ def IsInModulo(self, timestep, frequencies): ++ """ ++ Return True if the given timestep is in one of the provided frequency. ++ This can be interpreted as follow:: ++ ++ isFM = IsInModulo(timestep-timeStepToStartOutputAt, [2,3,7]) ++ ++ is similar to:: ++ ++ isFM = (timestep-timeStepToStartOutputAt % 2 == 0) or (timestep-timeStepToStartOutputAt % 3 == 0) or (timestep-timeStepToStartOutputAt % 7 == 0) ++ ++ The timeStepToStartOutputAt is the first timestep that will potentially be output. ++ """ ++ if timestep < self.__TimeStepToStartOutputAt and not self.__ForceOutputAtFirstCall: ++ return False ++ for frequency in frequencies: ++ if frequency > 0 and self.NeedToOutput(timestep, frequency): ++ return True ++ ++ return False ++ ++ ++ def NeedToOutput(self, timestep, frequency): ++ """ ++ Return True if we need to output based on the input timestep and frequency. Checks based ++ __FirstTimeStepIndex, __FirstTimeStepIndex, __ForceOutputAtFirstCall and __TimeStepToStartOutputAt ++ member variables. ++ """ ++ if self.__FirstTimeStepIndex == None: ++ self.__FirstTimeStepIndex = timestep ++ ++ if self.__ForceOutputAtFirstCall and self.__FirstTimeStepIndex == timestep: ++ return True ++ ++ if self.__TimeStepToStartOutputAt <= timestep and (timestep-self.__TimeStepToStartOutputAt) % frequency == 0: ++ return True ++ ++ return False diff --git a/var/spack/repos/builtin/packages/of-catalyst/package.py b/var/spack/repos/builtin/packages/of-catalyst/package.py new file mode 100644 index 0000000000..ac1caf654e --- /dev/null +++ b/var/spack/repos/builtin/packages/of-catalyst/package.py @@ -0,0 +1,48 @@ +# Copyright 2013-2018 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +from spack import * + + +class OfCatalyst(CMakePackage): + """Of-catalyst is a library for OpenFOAM that provides a runtime-selectable + function object for embedding ParaView Catalyst in-situ visualization + into arbitrary OpenFOAM simulations. + Supports in-situ conversion of the following types: + 1) finite volume meshes and fields, single or multi-region; + 2) finite area meshes and fields, single region; + 3) lagrangian (clouds), single or multiple clouds. + This offering is part of the community repository supported by OpenCFD Ltd, + producer and distributor of the OpenFOAM software via www.openfoam.com, + and owner of the OPENFOAM trademark. + OpenCFD Ltd has been developing and releasing OpenFOAM since its debut + in 2004. + """ + + # Currently only via git + homepage = "https://develop.openfoam.com/Community/catalyst" + git = "https://develop.openfoam.com/Community/catalyst.git" + + version('develop', branch='develop') + version('1806', tag='v1806') + + variant('full', default=False, description='Build against paraview (full) or catalyst (light)') + + depends_on('openfoam-com@1806', when='@1806', type=('build', 'link', 'run')) + depends_on('openfoam-com@develop', when='@develop', type=('build', 'link', 'run')) + depends_on('catalyst@5.5:', when='~full') + depends_on('paraview@5.5:+osmesa~qt', when='+full') + + root_cmakelists_dir = 'src/catalyst' + + def cmake_args(self): + """Populate cmake arguments for ParaView.""" + cmake_args = [ + '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=%s' % join_path( + self.stage.source_path, + 'spack-build') + ] + + return cmake_args diff --git a/var/spack/repos/builtin/packages/openfoam-com/package.py b/var/spack/repos/builtin/packages/openfoam-com/package.py index 33b9daf405..1cd5722003 100644 --- a/var/spack/repos/builtin/packages/openfoam-com/package.py +++ b/var/spack/repos/builtin/packages/openfoam-com/package.py @@ -405,16 +405,16 @@ class OpenfoamCom(Package): # Unneeded bits # ------------- - 'FOAM_SETTINGS', # Do not use with modules - 'FOAM_INST_DIR', # Old - 'FOAM_(APP|ETC|SRC|SOLVERS|UTILITIES)', + # 'FOAM_SETTINGS', # Do not use with modules + # 'FOAM_INST_DIR', # Old + # 'FOAM_(APP|ETC|SRC|SOLVERS|UTILITIES)', # 'FOAM_TUTORIALS', # can be useful - 'WM_OSTYPE', # Purely optional value + # 'WM_OSTYPE', # Purely optional value # Third-party cruft - only used for orig compilation # ----------------- '[A-Z].*_ARCH_PATH', - '(KAHIP|METIS|SCOTCH)_VERSION', + # '(KAHIP|METIS|SCOTCH)_VERSION', # User-specific # ------------- @@ -426,6 +426,7 @@ class OpenfoamCom(Package): ]) run_env.extend(mods) + spack_env.extend(mods) minimal = False tty.info('OpenFOAM bashrc env: {0}'.format(bashrc)) except Exception: @@ -436,8 +437,11 @@ class OpenfoamCom(Package): tty.info('OpenFOAM minimal env {0}'.format(self.prefix)) run_env.set('FOAM_PROJECT_DIR', self.projectdir) run_env.set('WM_PROJECT_DIR', self.projectdir) + spack_env.set('FOAM_PROJECT_DIR', self.projectdir) + spack_env.set('WM_PROJECT_DIR', self.projectdir) for d in ['wmake', self.archbin]: # bin added automatically run_env.prepend_path('PATH', join_path(self.projectdir, d)) + spack_env.prepend_path('PATH', join_path(self.projectdir, d)) def setup_dependent_environment(self, spack_env, run_env, dependent_spec): """Location of the OpenFOAM project directory. @@ -445,7 +449,7 @@ class OpenfoamCom(Package): variable since it would mask the normal OpenFOAM cleanup of previous versions. """ - spack_env.set('FOAM_PROJECT_DIR', self.projectdir) + self.setup_environment(spack_env, run_env) @property def projectdir(self): diff --git a/var/spack/repos/builtin/packages/paraview/package.py b/var/spack/repos/builtin/packages/paraview/package.py index e1c36a063f..aca00f3cde 100644 --- a/var/spack/repos/builtin/packages/paraview/package.py +++ b/var/spack/repos/builtin/packages/paraview/package.py @@ -59,7 +59,8 @@ class Paraview(CMakePackage): depends_on('libpng') depends_on('libtiff') depends_on('libxml2') - # depends_on('netcdf') + depends_on('netcdf') + depends_on('expat') # depends_on('netcdf-cxx') # depends_on('protobuf') # version mismatches? # depends_on('sqlite') # external version not supported @@ -74,6 +75,9 @@ class Paraview(CMakePackage): # Broken installation (ui_pqExportStateWizard.h) - fixed in 5.2.0 patch('ui_pqExportStateWizard.patch', when='@:5.1.2') + # Broken vtk-m config. Upstream catalyst changes + patch('vtkm-catalyst-pv551.patch', when='@5.5.0:5.5.2') + def url_for_version(self, version): """Handle ParaView version-based custom URLs.""" if version < Version('5.1.0'): @@ -81,34 +85,50 @@ class Paraview(CMakePackage): else: return self._urlfmt.format(version.up_to(2), version, '') + @property + def paraview_subdir(self): + """The paraview subdirectory name as paraview-major.minor""" + return 'paraview-{0}'.format(self.spec.version.up_to(2)) + def setup_dependent_environment(self, spack_env, run_env, dependent_spec): if os.path.isdir(self.prefix.lib64): lib_dir = self.prefix.lib64 else: lib_dir = self.prefix.lib - paraview_version = 'paraview-%s' % self.spec.version.up_to(2) + spack_env.set('ParaView_DIR', self.prefix) spack_env.set('PARAVIEW_VTK_DIR', - join_path(lib_dir, 'cmake', paraview_version)) + join_path(lib_dir, 'cmake', self.paraview_subdir)) def setup_environment(self, spack_env, run_env): + # paraview 5.5 and later + # - cmake under lib/cmake/paraview-5.5 + # - libs under lib + # - python bits under lib/python2.8/site-packages if os.path.isdir(self.prefix.lib64): lib_dir = self.prefix.lib64 else: lib_dir = self.prefix.lib - paraview_version = 'paraview-%s' % self.spec.version.up_to(2) - run_env.prepend_path('LIBRARY_PATH', join_path(lib_dir, - paraview_version)) - run_env.prepend_path('LD_LIBRARY_PATH', join_path(lib_dir, - paraview_version)) + + run_env.set('ParaView_DIR', self.prefix) run_env.set('PARAVIEW_VTK_DIR', - join_path(lib_dir, 'cmake', paraview_version)) + join_path(lib_dir, 'cmake', self.paraview_subdir)) + + if self.spec.version <= Version('5.4.1'): + lib_dir = join_path(lib_dir, self.paraview_subdir) + + run_env.prepend_path('LIBRARY_PATH', lib_dir) + run_env.prepend_path('LD_LIBRARY_PATH', lib_dir) + if '+python' in self.spec: - run_env.prepend_path('PYTHONPATH', join_path(lib_dir, - paraview_version)) - run_env.prepend_path('PYTHONPATH', join_path(lib_dir, - paraview_version, 'site-packages')) - run_env.prepend_path('PYTHONPATH', join_path(lib_dir, - paraview_version, 'site-packages', 'vtk')) + if self.spec.version <= Version('5.4.1'): + pv_pydir = join_path(lib_dir, 'site-packages') + run_env.prepend_path('PYTHONPATH', pv_pydir) + run_env.prepend_path('PYTHONPATH', join_path(pv_pydir, 'vtk')) + else: + python_version = self.spec['python'].version.up_to(2) + run_env.prepend_path('PYTHONPATH', join_path(lib_dir, + 'python{0}'.format(python_version), + 'site-packages')) def cmake_args(self): """Populate cmake arguments for ParaView.""" @@ -139,7 +159,8 @@ class Paraview(CMakePackage): '-DVTK_USE_SYSTEM_HDF5:BOOL=%s' % variant_bool('+hdf5'), '-DVTK_USE_SYSTEM_JPEG:BOOL=ON', '-DVTK_USE_SYSTEM_LIBXML2:BOOL=ON', - '-DVTK_USE_SYSTEM_NETCDF:BOOL=OFF', + '-DVTK_USE_SYSTEM_NETCDF:BOOL=ON', + '-DVTK_USE_SYSTEM_EXPAT:BOOL=ON', '-DVTK_USE_SYSTEM_TIFF:BOOL=ON', '-DVTK_USE_SYSTEM_ZLIB:BOOL=ON', ] diff --git a/var/spack/repos/builtin/packages/paraview/vtkm-catalyst-pv551.patch b/var/spack/repos/builtin/packages/paraview/vtkm-catalyst-pv551.patch new file mode 100644 index 0000000000..8c8883740d --- /dev/null +++ b/var/spack/repos/builtin/packages/paraview/vtkm-catalyst-pv551.patch @@ -0,0 +1,510 @@ +# The VTK-m changes are slated for paraview-5.5.x +# +# The catalyst changes (the working directory for output) are slated for +# paraview-5.6. +# They are API-compatible with paraview-5.5 but not ABI compatible. +# +# https://gitlab.kitware.com/vtk/vtk-m/merge_requests/1166 +# https://gitlab.kitware.com/paraview/paraview/merge_requests/2433 +# https://gitlab.kitware.com/paraview/paraview/merge_requests/2436 +# +--- ParaView-v5.5.0/VTK/ThirdParty/vtkm/vtk-m/CMake/VTKmDetermineVersion.cmake.orig 2018-04-06 22:03:33.000000000 +0200 ++++ ParaView-v5.5.0/VTK/ThirdParty/vtkm/vtk-m/CMake/VTKmDetermineVersion.cmake 2018-04-23 12:00:23.708544206 +0200 +@@ -51,6 +51,8 @@ + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE) ++ else() ++ set(output) + endif() + else() + set(result 0) +@@ -75,7 +77,7 @@ + + # Extracts components from a version string. See determine_version() for usage. + function(extract_version_components version_string var_prefix) +- string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)[-]*(.*)" ++ string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)[-]*(.*)$" + version_matches "${version_string}") + if(CMAKE_MATCH_0) + set(full ${CMAKE_MATCH_0}) +--- ParaView-v5.5.0/CoProcessing/Catalyst/vtkCPProcessor.cxx.orig 2018-04-06 22:03:33.000000000 +0200 ++++ ParaView-v5.5.0/CoProcessing/Catalyst/vtkCPProcessor.cxx 2018-05-11 12:02:26.894772713 +0200 +@@ -38,6 +38,7 @@ + #include "vtkStringArray.h" + + #include <list> ++#include <vtksys/SystemTools.hxx> + + struct vtkCPProcessorInternals + { +@@ -47,12 +48,13 @@ + }; + + vtkStandardNewMacro(vtkCPProcessor); +-vtkMultiProcessController* vtkCPProcessor::Controller = NULL; ++vtkMultiProcessController* vtkCPProcessor::Controller = nullptr; + //---------------------------------------------------------------------------- + vtkCPProcessor::vtkCPProcessor() + { + this->Internal = new vtkCPProcessorInternals; +- this->InitializationHelper = NULL; ++ this->InitializationHelper = nullptr; ++ this->WorkingDirectory = nullptr; + } + + //---------------------------------------------------------------------------- +@@ -61,14 +63,15 @@ + if (this->Internal) + { + delete this->Internal; +- this->Internal = NULL; ++ this->Internal = nullptr; + } + + if (this->InitializationHelper) + { + this->InitializationHelper->Delete(); +- this->InitializationHelper = NULL; ++ this->InitializationHelper = nullptr; + } ++ this->SetWorkingDirectory(nullptr); + } + + //---------------------------------------------------------------------------- +@@ -95,7 +98,7 @@ + { + if (which < 0 || which >= this->GetNumberOfPipelines()) + { +- return NULL; ++ return nullptr; + } + int counter = 0; + vtkCPProcessorInternals::PipelineListIterator iter = this->Internal->Pipelines.begin(); +@@ -108,7 +111,7 @@ + counter++; + iter++; + } +- return NULL; ++ return nullptr; + } + + //---------------------------------------------------------------------------- +@@ -130,17 +133,41 @@ + } + + //---------------------------------------------------------------------------- +-int vtkCPProcessor::Initialize() ++int vtkCPProcessor::Initialize(const char* workingDirectory) + { +- if (this->InitializationHelper == NULL) ++ if (this->InitializationHelper == nullptr) + { + this->InitializationHelper = this->NewInitializationHelper(); + } ++ // make sure the directory exists here so that we only do it once ++ if (workingDirectory) ++ { ++ vtkMultiProcessController* controller = vtkMultiProcessController::GetGlobalController(); ++ int success = 1; ++ if (controller == nullptr || controller->GetLocalProcessId() == 0) ++ { ++ success = vtksys::SystemTools::MakeDirectory(workingDirectory) == true ? 1 : 0; ++ if (success == 0) ++ { ++ vtkWarningMacro("Could not make " ++ << workingDirectory << " directory. " ++ << "Results will be generated in current working directory instead."); ++ } ++ } ++ if (controller) ++ { ++ controller->Broadcast(&success, 1, 0); ++ } ++ if (success) ++ { ++ this->SetWorkingDirectory(workingDirectory); ++ } ++ } + return 1; + } + + //---------------------------------------------------------------------------- +-int vtkCPProcessor::Initialize(vtkMPICommunicatorOpaqueComm& comm) ++int vtkCPProcessor::Initialize(vtkMPICommunicatorOpaqueComm& comm, const char* workingDirectory) + { + #ifdef PARAVIEW_USE_MPI + if (vtkCPProcessor::Controller) +@@ -148,7 +175,7 @@ + vtkErrorMacro("Can only initialize with a communicator once per process."); + return 0; + } +- if (this->InitializationHelper == NULL) ++ if (this->InitializationHelper == nullptr) + { + vtkMPICommunicator* communicator = vtkMPICommunicator::New(); + communicator->InitializeExternal(&comm); +@@ -157,12 +184,12 @@ + this->Controller = controller; + this->Controller->SetGlobalController(controller); + communicator->Delete(); +- return this->Initialize(); ++ return this->Initialize(workingDirectory); + } + return 1; + #else + static_cast<void>(&comm); // get rid of variable not used warning +- return this->Initialize(); ++ return this->Initialize(workingDirectory); + #endif + } + +@@ -225,6 +252,13 @@ + input->GetFieldData()->AddArray(catalystChannel); + } + } ++ ++ std::string originalWorkingDirectory; ++ if (this->WorkingDirectory) ++ { ++ originalWorkingDirectory = vtksys::SystemTools::GetCurrentWorkingDirectory(); ++ vtksys::SystemTools::ChangeDirectory(this->WorkingDirectory); ++ } + for (vtkCPProcessorInternals::PipelineListIterator iter = this->Internal->Pipelines.begin(); + iter != this->Internal->Pipelines.end(); iter++) + { +@@ -248,6 +282,10 @@ + } + } + } ++ if (originalWorkingDirectory.empty() == false) ++ { ++ vtksys::SystemTools::ChangeDirectory(originalWorkingDirectory); ++ } + // we want to reset everything here to make sure that new information + // is properly passed in the next time. + dataDescription->ResetAll(); +@@ -259,7 +297,7 @@ + { + if (this->Controller) + { +- this->Controller->SetGlobalController(NULL); ++ this->Controller->SetGlobalController(nullptr); + this->Controller->Finalize(1); + this->Controller->Delete(); + } +--- ParaView-v5.5.0/CoProcessing/Catalyst/vtkCPProcessor.h.orig 2018-04-06 22:03:33.000000000 +0200 ++++ ParaView-v5.5.0/CoProcessing/Catalyst/vtkCPProcessor.h 2018-05-11 12:02:26.894772713 +0200 +@@ -76,14 +76,16 @@ + virtual void RemoveAllPipelines(); + + /// Initialize the co-processor. Returns 1 if successful and 0 +- /// otherwise. + /// otherwise. If Catalyst is built with MPI then Initialize() + /// can also be called with a specific MPI communicator if + /// MPI_COMM_WORLD isn't the proper one. Catalyst is initialized +- /// to use MPI_COMM_WORLD by default. +- virtual int Initialize(); ++ /// to use MPI_COMM_WORLD by default. Both methods have an optional ++ /// workingDirectory argument which will set *WorkingDirectory* so ++ /// that files will be put relative to this directory. ++ virtual int Initialize(const char* workingDirectory = nullptr); + #ifndef __WRAP__ +- virtual int Initialize(vtkMPICommunicatorOpaqueComm& comm); ++ virtual int Initialize( ++ vtkMPICommunicatorOpaqueComm& comm, const char* workingDirectory = nullptr); + #endif + + /// The Catalyst input field data string array name. This array will +@@ -111,6 +113,13 @@ + /// implementation an opportunity to clean up, before it is destroyed. + virtual int Finalize(); + ++ /// Get the current working directory for outputting Catalyst files. ++ /// If not set then Catalyst output files will be relative to the ++ /// current working directory. This will not affect where Catalyst ++ /// looks for Python scripts. *WorkingDirectory* gets set through ++ /// the *Initialize()* methods. ++ vtkGetStringMacro(WorkingDirectory); ++ + protected: + vtkCPProcessor(); + virtual ~vtkCPProcessor(); +@@ -118,6 +127,11 @@ + /// Create a new instance of the InitializationHelper. + virtual vtkObject* NewInitializationHelper(); + ++ /// Set the current working directory for outputting Catalyst files. ++ /// This is a protected method since simulation code adaptors should ++ /// set this through the *Initialize()* methods. ++ vtkSetStringMacro(WorkingDirectory); ++ + private: + vtkCPProcessor(const vtkCPProcessor&) = delete; + void operator=(const vtkCPProcessor&) = delete; +@@ -125,6 +139,7 @@ + vtkCPProcessorInternals* Internal; + vtkObject* InitializationHelper; + static vtkMultiProcessController* Controller; ++ char* WorkingDirectory; + }; + + #endif +--- ParaView-v5.5.0/CoProcessing/Catalyst/vtkCPXMLPWriterPipeline.cxx.orig 2018-04-06 22:03:33.000000000 +0200 ++++ ParaView-v5.5.0/CoProcessing/Catalyst/vtkCPXMLPWriterPipeline.cxx 2018-05-11 12:02:26.894772713 +0200 +@@ -31,6 +31,7 @@ + #include <vtkSmartPointer.h> + #include <vtkUnstructuredGrid.h> + ++#include <algorithm> + #include <sstream> + #include <string> + +@@ -174,7 +175,7 @@ + + for (unsigned int i = 0; i < dataDescription->GetNumberOfInputDescriptions(); i++) + { +- const char* inputName = dataDescription->GetInputDescriptionName(i); ++ std::string inputName = dataDescription->GetInputDescriptionName(i); + vtkCPInputDataDescription* idd = dataDescription->GetInputDescription(i); + vtkDataObject* grid = idd->GetGrid(); + if (grid == nullptr) +@@ -206,6 +207,8 @@ + vtkSMStringVectorProperty* fileName = + vtkSMStringVectorProperty::SafeDownCast(writer->GetProperty("FileName")); + ++ // If we have a / in the channel name we take it out of the filename we're going to write to ++ inputName.erase(std::remove(inputName.begin(), inputName.end(), '/'), inputName.end()); + std::ostringstream o; + if (this->Path.empty() == false) + { +--- ParaView-v5.5.0/Wrapping/Python/paraview/coprocessing.py.orig 2018-04-06 22:03:33.000000000 +0200 ++++ ParaView-v5.5.0/Wrapping/Python/paraview/coprocessing.py 2018-05-11 12:02:27.038772408 +0200 +@@ -11,22 +11,12 @@ + from paraview.vtk.vtkPVVTKExtensionsCore import * + import math + +-# ----------------------------------------------------------------------------- +-def IsInModulo(timestep, frequencyArray): +- """ +- Return True if the given timestep is in one of the provided frequency. +- This can be interpreted as follow:: +- +- isFM = IsInModulo(timestep, [2,3,7]) +- +- is similar to:: ++# If the user created a filename in a location that doesn't exist by default we'll ++# make the directory for them. This can be changed though by setting createDirectoriesIfNeeded ++# to False. ++createDirectoriesIfNeeded = True + +- isFM = (timestep % 2 == 0) or (timestep % 3 == 0) or (timestep % 7 == 0) +- """ +- for frequency in frequencyArray: +- if frequency > 0 and (timestep % frequency == 0): +- return True +- return False ++# ----------------------------------------------------------------------------- + + class CoProcessor(object): + """Base class for co-processing Pipelines. +@@ -68,6 +58,9 @@ + self.__CinemaTracks = {} + self.__InitialFrequencies = {} + self.__PrintEnsightFormatString = False ++ self.__TimeStepToStartOutputAt=0 ++ self.__ForceOutputAtFirstCall=False ++ self.__FirstTimeStepIndex = None + + def SetPrintEnsightFormatString(self, enable): + """If outputting ExodusII files with the purpose of reading them into +@@ -87,6 +80,17 @@ + "Incorrect argument type: %s, must be a dict" % type(frequencies)) + self.__InitialFrequencies = frequencies + ++ def SetInitialOutputOptions(self, timeStepToStartOutputAt, forceOutputAtFirstCall): ++ """Set the frequencies at which the pipeline needs to be updated. ++ Typically, this is called by the subclass once it has determined what ++ timesteps co-processing will be needed to be done. ++ frequencies is a map, with key->string name of for the simulation ++ input, and value is a list of frequencies. ++ """ ++ ++ self.__TimeStepToStartOutputAt=timeStepToStartOutputAt ++ self.__ForceOutputAtFirstCall=forceOutputAtFirstCall ++ + def EnableLiveVisualization(self, enable, frequency = 1): + """Call this method to enable live-visualization. When enabled, + DoLiveVisualization() will communicate with ParaView server if possible +@@ -115,7 +119,7 @@ + # if this is a time step to do live then all of the inputs + # must be made available. note that we want the pipeline built + # before we do the actual first live connection. +- if self.__EnableLiveVisualization and timestep % self.__LiveVisualizationFrequency == 0 \ ++ if self.__EnableLiveVisualization and self.NeedToOutput(timestep, self.__LiveVisualizationFrequency) \ + and self.__LiveVisualizationLink: + if self.__LiveVisualizationLink.Initialize(servermanager.ActiveConnection.Session.GetSessionProxyManager()): + num_inputs = datadescription.GetNumberOfInputDescriptions() +@@ -132,13 +136,13 @@ + # hasn't been set up yet). If we don't have live enabled + # we know that the output frequencies aren't changed and can + # just use the initial frequencies. +- if self.__InitialFrequencies or not self.__EnableLiveVisualization: ++ if self.__ForceOutputAtFirstCall or self.__InitialFrequencies or not self.__EnableLiveVisualization: + num_inputs = datadescription.GetNumberOfInputDescriptions() + for cc in range(num_inputs): + input_name = datadescription.GetInputDescriptionName(cc) + + freqs = self.__InitialFrequencies.get(input_name, []) +- if self.__EnableLiveVisualization or ( self and IsInModulo(timestep, freqs) ): ++ if self.__EnableLiveVisualization or ( self and self.IsInModulo(timestep, freqs) ): + datadescription.GetInputDescription(cc).AllFieldsOn() + datadescription.GetInputDescription(cc).GenerateMeshOn() + else: +@@ -149,15 +153,14 @@ + for writer in self.__WritersList: + frequency = writer.parameters.GetProperty( + "WriteFrequency").GetElement(0) +- if (timestep % frequency) == 0 or \ +- datadescription.GetForceOutput() == True: ++ if self.NeedToOutput(timestep, frequency) or datadescription.GetForceOutput() == True: + writerinputs = cpstate.locate_simulation_inputs(writer) + for writerinput in writerinputs: + datadescription.GetInputDescriptionByName(writerinput).AllFieldsOn() + datadescription.GetInputDescriptionByName(writerinput).GenerateMeshOn() + + for view in self.__ViewsList: +- if (view.cpFrequency and timestep % view.cpFrequency == 0) or \ ++ if (view.cpFrequency and self.NeedToOutput(timestep, view.cpFrequency)) or \ + datadescription.GetForceOutput() == True: + viewinputs = cpstate.locate_simulation_inputs_for_view(view) + for viewinput in viewinputs: +@@ -192,8 +195,7 @@ + for writer in self.__WritersList: + frequency = writer.parameters.GetProperty( + "WriteFrequency").GetElement(0) +- if (timestep % frequency) == 0 or \ +- datadescription.GetForceOutput() == True: ++ if self.NeedToOutput(timestep, frequency) or datadescription.GetForceOutput() == True: + fileName = writer.parameters.GetProperty("FileName").GetElement(0) + paddingamount = writer.parameters.GetProperty("PaddingAmount").GetElement(0) + helperName = writer.GetXMLName() +@@ -203,6 +205,23 @@ + else: + ts = str(timestep).rjust(paddingamount, '0') + writer.FileName = fileName.replace("%t", ts) ++ if '/' in writer.FileName and createDirectoriesIfNeeded: ++ oktowrite = [1.] ++ import vtk ++ comm = vtk.vtkMultiProcessController.GetGlobalController() ++ if comm.GetLocalProcessId() == 0: ++ import os ++ newDir = writer.FileName[0:writer.FileName.rfind('/')] ++ try: ++ os.makedirs(newDir) ++ except OSError: ++ if not os.path.isdir(newDir): ++ print ("ERROR: Cannot make directory for", writer.FileName, ". No data will be written.") ++ oktowrite[0] = 0. ++ comm.Broadcast(oktowrite, 1, 0) ++ if oktowrite[0] == 0: ++ # we can't make the directory so no reason to update the pipeline ++ return + writer.UpdatePipeline(datadescription.GetTime()) + + def WriteImages(self, datadescription, rescale_lookuptable=False, +@@ -240,7 +259,7 @@ + + cinema_dirs = [] + for view in self.__ViewsList: +- if (view.cpFrequency and timestep % view.cpFrequency == 0) or \ ++ if (view.cpFrequency and self.NeedToOutput(timestep, view.cpFrequency)) or \ + datadescription.GetForceOutput() == True: + fname = view.cpFileName + ts = str(timestep).rjust(padding_amount, '0') +@@ -267,6 +286,24 @@ + if dirname: + cinema_dirs.append(dirname) + else: ++ if '/' in fname and createDirectoriesIfNeeded: ++ oktowrite = [1.] ++ import vtk ++ comm = vtk.vtkMultiProcessController.GetGlobalController() ++ if comm.GetLocalProcessId() == 0: ++ import os ++ newDir = fname[0:fname.rfind('/')] ++ try: ++ os.makedirs(newDir) ++ except OSError: ++ if not os.path.isdir(newDir): ++ print ("ERROR: Cannot make directory for", fname, ". No image will be output.") ++ oktowrite[0] = 0. ++ comm.Broadcast(oktowrite, 1, 0) ++ if oktowrite[0] == 0: ++ # we can't make the directory so no reason to update the pipeline ++ return ++ + if image_quality is None and fname.endswith('png'): + # for png quality = 0 means no compression. compression can be a potentially + # very costly serial operation on process 0 +@@ -307,7 +344,7 @@ + + + timeStep = datadescription.GetTimeStep() +- if self.__EnableLiveVisualization and timeStep % self.__LiveVisualizationFrequency == 0: ++ if self.__EnableLiveVisualization and self.NeedToOutput(timeStep, self.__LiveVisualizationFrequency): + if not self.__LiveVisualizationLink.Initialize(servermanager.ActiveConnection.Session.GetSessionProxyManager()): + return + +@@ -412,7 +449,7 @@ + """ + controller = servermanager.ParaViewPipelineController() + # assume that a client only proxy with the same name as a writer +- # is available in "insitu_writer_paramters" ++ # is available in "insitu_writer_parameters" + + # Since coprocessor sometimes pass writer as a custom object and not + # a proxy, we need to handle that. Just creating any arbitrary writer +@@ -666,3 +703,42 @@ + #restore what we showed + pv_introspect.restore_visibility(pxystate) + return os.path.basename(vfname) ++ ++ def IsInModulo(self, timestep, frequencies): ++ """ ++ Return True if the given timestep is in one of the provided frequency. ++ This can be interpreted as follow:: ++ ++ isFM = IsInModulo(timestep-timeStepToStartOutputAt, [2,3,7]) ++ ++ is similar to:: ++ ++ isFM = (timestep-timeStepToStartOutputAt % 2 == 0) or (timestep-timeStepToStartOutputAt % 3 == 0) or (timestep-timeStepToStartOutputAt % 7 == 0) ++ ++ The timeStepToStartOutputAt is the first timestep that will potentially be output. ++ """ ++ if timestep < self.__TimeStepToStartOutputAt and not self.__ForceOutputAtFirstCall: ++ return False ++ for frequency in frequencies: ++ if frequency > 0 and self.NeedToOutput(timestep, frequency): ++ return True ++ ++ return False ++ ++ ++ def NeedToOutput(self, timestep, frequency): ++ """ ++ Return True if we need to output based on the input timestep and frequency. Checks based ++ __FirstTimeStepIndex, __FirstTimeStepIndex, __ForceOutputAtFirstCall and __TimeStepToStartOutputAt ++ member variables. ++ """ ++ if self.__FirstTimeStepIndex == None: ++ self.__FirstTimeStepIndex = timestep ++ ++ if self.__ForceOutputAtFirstCall and self.__FirstTimeStepIndex == timestep: ++ return True ++ ++ if self.__TimeStepToStartOutputAt <= timestep and (timestep-self.__TimeStepToStartOutputAt) % frequency == 0: ++ return True ++ ++ return False |