def _moveFiles(self): # check for orificed flow bounds files. These will often be named based on the # case that this one is dependent upon, but not always. For example, testSassys # is dependent on the safety case but requires physics bounds files. now copy # the files over copyFilesFrom = [ filePath for possiblePat in self.cs["copyFilesFrom"] for filePath in glob.glob(possiblePat) ] copyFilesTo = self.cs["copyFilesTo"] if len(copyFilesTo) in (len(copyFilesFrom), 0, 1): # if any files to copy, then use the first as the default, i.e. len() == 1, # otherwise assume '.' default = copyFilesTo[0] if any(copyFilesTo) else "." for filename, dest in itertools.zip_longest(copyFilesFrom, copyFilesTo, fillvalue=default): pathTools.copyOrWarn("copyFilesFrom", filename, dest) else: runLog.error( "cs['copyFilesTo'] must either be length 1, 0, or have the same number of entries as " "cs['copyFilesFrom']. Actual values:\n" " copyFilesTo : {}\n" " copyFilesFrom : {}".format(copyFilesTo, copyFilesFrom)) raise InputError("Failed to process copyFilesTo/copyFilesFrom")
def copyInterfaceInputs(cs, destination: str, sourceDir: Optional[str] = None): """ Copy sets of files that are considered "input" from each active interface. This enables developers to add new inputs in a plugin-dependent/ modular way. In parameter sweeps, these often have a sourceDir associated with them that is different from the cs.inputDirectory. """ activeInterfaces = interfaces.getActiveInterfaceInfo(cs) sourceDir = sourceDir or cs.inputDirectory for klass, kwargs in activeInterfaces: if not kwargs.get("enabled", True): # Don't consider disabled interfaces continue interfaceFileNames = klass.specifyInputs(cs) for label, relativeGlobs in interfaceFileNames.items(): for relativeGlob in relativeGlobs: for sourceFullPath in glob.glob( os.path.join(sourceDir, relativeGlob)): if not sourceFullPath: continue _sourceDir, sourceName = os.path.split(sourceFullPath) pathTools.copyOrWarn(label, sourceFullPath, os.path.join(destination, sourceName))
def copyInterfaceInputs(cs, destination): """ Copy sets of files that are considered "input" from each active interface. This enables developers to add new inputs in a plugin-dependent/ modular way. """ activeInterfaces = interfaces.getActiveInterfaceInfo(cs) for klass, kwargs in activeInterfaces: if not kwargs.get("enabled", True): # Don't consider disabled interfaces continue interfaceFileNames = klass.specifyInputs(cs) for label, fileNames in interfaceFileNames.items(): for f in fileNames: pathTools.copyOrWarn( label, pathTools.armiAbsPath(cs.inputDirectory, f), destination)
def retrieveOutputFiles(self, runPath): """ Copy interesting output files from local disks to shared network disk. Run this if you want copies of the local output files. This copies output from the runPath to the current working directory, which is generally the shared network drive. See Also -------- specifyOutputFilesToRetrieve : says which ones to copy back. armi.utils.directoryChangers.DirectoryChanger.retrieveFiles : should be used instead of this. Notes ----- This could be done in a separate thread to let processing continue while I/O spins. """ for sourceName, destinationName in self._outputFilesNamesToRetrieve: workingFileName = os.path.join(runPath, sourceName) pathTools.copyOrWarn("output file", workingFileName, destinationName)
def copyInterfaceInputs(cs, destination: str, sourceDir: Optional[str] = None) -> Dict[str, str]: """ Copy sets of files that are considered "input" from each active interface. This enables developers to add new inputs in a plugin-dependent/ modular way. In parameter sweeps, these often have a sourceDir associated with them that is different from the cs.inputDirectory. Parameters ---------- cs : CaseSettings The source case settings to find input files destination: str The target directory to copy input files to sourceDir: str, optional The directory from which to copy files. Defaults to cs.inputDirectory Notes ----- This may seem a bit overly complex, but a lot of the behavior is important. Relative paths are copied into the target directory, which in some cases requires updating the setting that pointed to the file in the first place. This is necessary to avoid case dependencies in relavive locations above the input directory, which can lead to issues when cloneing case suites. In the future this could be simplified by adding a concept for a suite root directory, below which it is safe to copy files without needing to update settings that point with a relative path to files that are below it. """ activeInterfaces = interfaces.getActiveInterfaceInfo(cs) sourceDir = sourceDir or cs.inputDirectory sourceDirPath = pathlib.Path(sourceDir) destPath = pathlib.Path(destination) newSettings = {} assert destPath.is_dir() for klass, _ in activeInterfaces: interfaceFileNames = klass.specifyInputs(cs) # returned files can be absolute paths, relative paths, or even glob patterns. # Since we don't have an explicit way to signal about these, we sort of have to # guess. In future, it might be nice to have interfaces specify which # explicitly. for key, files in interfaceFileNames.items(): if not isinstance(key, settings.Setting): try: key = cs.getSetting(key) except NonexistentSetting(key): raise ValueError( "{} is not a valid setting. Ensure the relevant specifyInputs method uses a correct setting name." .format(key)) label = key.name for f in files: path = pathlib.Path(f) if path.is_absolute() and path.exists() and path.is_file(): # looks like an extant, absolute path; no need to do anything pass else: # An OSError can occur if a wildcard is included in the file name so # this is wrapped in a try/except to circumvent instances where an # interface requests to copy multiple files based on some prefix/suffix. try: if not (path.exists() and path.is_file()): runLog.extra( f"Input file `{f}` not found. Checking for file at path `{sourceDirPath}`" ) except OSError: pass # relative path/glob. Should be safe to just use glob resolution. # Note that `glob.glob` is being used here rather than `pathlib.glob` because # `pathlib.glob` for Python 3.7.2 does not handle case sensitivity for file and # path names. This is required for copying and using Python scripts (e.g., fuel management, # control logic, etc.). srcFiles = [ pathlib.Path(os.path.join(sourceDirPath, g)) for g in glob.glob(os.path.join(sourceDirPath, f)) ] for sourceFullPath in srcFiles: if not sourceFullPath: continue sourceName = os.path.basename(sourceFullPath.name) destFilePath = os.path.abspath(destPath / sourceName) pathTools.copyOrWarn(label, sourceFullPath, destFilePath) if len(srcFiles) == 0: runLog.warning( f"No input files for `{label}` could be resolved " f"with the following file path: `{f}`.") elif len(srcFiles) > 1: runLog.warning( f"Input files for `{label}` resolved to more " f"than one file; cannot update settings safely. " f"Discovered input files: {srcFiles}") elif len(srcFiles) == 1: newSettings[label] = str(destFilePath) return newSettings
def clone(self, additionalFiles=None, title=None, modifiedSettings=None): """ Clone existing ARMI inputs to current directory with optional settings modifications. Since each case depends on multiple inputs, this is a safer way to move cases around without having to wonder if you copied all the files appropriately. Parameters ---------- additionalFiles : list (optional) additional file paths to copy to cloned case title : str (optional) title of new case modifiedSettings : dict (optional) settings to set/modify before creating the cloned case Raises ------ RuntimeError If the source and destination are the same """ cloneCS = self.cs.duplicate() if modifiedSettings is not None: cloneCS = cloneCS.modified(newSettings=modifiedSettings) clone = Case(cloneCS) clone.cs.path = pathTools.armiAbsPath(title or self.title) + ".yaml" if pathTools.armiAbsPath(clone.cs.path) == pathTools.armiAbsPath( self.cs.path): raise RuntimeError( "The source file and destination file are the same: {}\n" "Cannot use armi-clone to modify armi settings file.".format( pathTools.armiAbsPath(clone.cs.path))) newSettings = copyInterfaceInputs(self.cs, clone.cs.inputDirectory) newCs = clone.cs.modified(newSettings=newSettings) clone.cs = newCs runLog.important("writing settings file {}".format(clone.cs.path)) clone.cs.writeToYamlFile(clone.cs.path) runLog.important("finished writing {}".format(clone.cs)) fromPath = lambda fname: pathTools.armiAbsPath(self.cs.inputDirectory, fname) for inputFileSetting in ["loadingFile", "geomFile"]: fileName = self.cs[inputFileSetting] if fileName: pathTools.copyOrWarn( inputFileSetting, fromPath(fileName), os.path.join(clone.cs.inputDirectory, fileName), ) else: runLog.warning( "skipping {}, there is no file specified".format( inputFileSetting)) with open(self.cs["loadingFile"], "r") as f: # The root for handling YAML includes is relative to the YAML file, not the # settings file root = (pathlib.Path(self.cs.inputDirectory) / pathlib.Path(self.cs["loadingFile"]).parent) cloneRoot = (pathlib.Path(clone.cs.inputDirectory) / pathlib.Path(clone.cs["loadingFile"]).parent) for includePath, mark in textProcessors.findYamlInclusions( f, root=root): if not includePath.is_absolute(): includeSrc = root / includePath includeDest = cloneRoot / includePath else: # don't bother copying absolute files continue if not includeSrc.exists(): raise OSError( "The input file file `{}` referenced at {} does not exist." .format(includeSrc, mark)) pathTools.copyOrWarn( "auxiliary input file `{}` referenced at {}".format( includeSrc, mark), includeSrc, includeDest, ) for fileName in additionalFiles or []: pathTools.copyOrWarn("additional file", fromPath(fileName), clone.cs.inputDirectory) return clone
def snapshotRequest(self, cycle, node): """ Process a snapshot request at this time. This copies various physics input and output files to a special folder that follow-on analysis be executed upon later. Notes ----- This was originally used to produce MC2/DIF3D inputs for external parties (who didn't have ARMI) to review. Since then, the concept of snapshots has evolved with respect to the :py:class:`~armi.operators.snapshots.OperatorSnapshots`. """ runLog.info("Producing snapshot for cycle {0} node {1}".format( cycle, node)) self.r.core.zones.summary() newFolder = "snapShot{0}_{1}".format(cycle, node) if os.path.exists(newFolder): runLog.important( "Deleting existing snapshot data in {0}".format(newFolder)) utils.cleanPath(newFolder) # careful with cleanPath! # give it a minute. time.sleep(1) if os.path.exists(newFolder): runLog.warning( "Deleting existing snapshot data in {0} failed".format( newFolder)) else: os.mkdir(newFolder) inf = "{0}{1:03d}{2:03d}.inp".format(self.cs.caseTitle, cycle, node) writer = self.getInterface("dif3d") if not writer: writer = self.getInterface("rebus") if not writer: runLog.warning( "There are no interface attached that can write a snapshot input" ) else: writer.writeInput(os.path.join(newFolder, inf)) # copy the cross section inputs for fileName in os.listdir("."): if "mcc" in fileName and re.search(r"[A-Z]AF?\d?.inp", fileName): base, ext = os.path.splitext(fileName) # add the cycle and timenode to the XS input file names so that a rx-coeff case that runs # in here won't overwrite them. shutil.copy( fileName, os.path.join( newFolder, "{0}_{1:03d}_{2:d}{3}".format(base, cycle, node, ext)), ) isoFName = "ISOTXS-c{0}".format(cycle) pathTools.copyOrWarn("ISOTXS for snapshot", isoFName, pathTools.armiAbsPath(newFolder, "ISOTXS")) pathTools.copyOrWarn( "DIF3D output for snapshot", self.cs.caseTitle + "{0:03d}.out".format(cycle), newFolder, ) pathTools.copyOrWarn("Shuffle logic for snapshot", self.cs["shuffleLogic"], newFolder) pathTools.copyOrWarn("Geometry file for snapshot", self.cs["geomFile"], newFolder) pathTools.copyOrWarn("Loading definition for snapshot", self.cs["loadingFile"], newFolder) pathTools.copyOrWarn( "Flow history for snapshot", self.cs.caseTitle + ".flow_history.txt", newFolder, ) pathTools.copyOrWarn( "Pressure history for snapshot", self.cs.caseTitle + ".pressure_history.txt", newFolder, )
def clone(self, additionalFiles=None, title=None, modifiedSettings=None): """ Clone existing ARMI inputs to current directory with optional settings modifications. Since each case depends on multiple inputs, this is a safer way to move cases around without having to wonder if you copied all the files appropriately. Parameters ---------- additionalFiles : list (optional) additional file paths to copy to cloned case title : str (optional) title of new case modifiedSettings : dict (optional) settings to set/modify before creating the cloned case Raises ------ RuntimeError If the source and destination are the same """ cloneCS = self.cs.duplicate() if modifiedSettings is not None: cloneCS.update(modifiedSettings) clone = Case(cloneCS) clone.cs.path = pathTools.armiAbsPath(title or self.title) + ".yaml" if pathTools.armiAbsPath(clone.cs.path) == pathTools.armiAbsPath( self.cs.path): raise RuntimeError( "The source file and destination file are the same: {}\n" "Cannot use armi-clone to modify armi settings file.".format( pathTools.armiAbsPath(clone.cs.path))) runLog.important("writing settings file {}".format(clone.cs.path)) clone.cs.writeToYamlFile(clone.cs.path) runLog.important("finished writing {}".format(clone.cs)) fromPath = lambda fname: pathTools.armiAbsPath(self.cs.inputDirectory, fname) for inputFileSetting in [ "loadingFile", "geomFile", "shuffleLogic", "explicitRepeatShuffles", ]: fileName = self.cs[inputFileSetting] if fileName: pathTools.copyOrWarn( inputFileSetting, fromPath(fileName), os.path.join(clone.cs.inputDirectory, fileName), ) else: runLog.warning( "skipping {}, there is no file specified".format( inputFileSetting)) copyInterfaceInputs(self.cs, clone.cs.inputDirectory) for grid in self.bp.gridDesigns or []: if grid.latticeFile: pathTools.copyOrWarn( "system lattice for {}".format(grid.name), fromPath(grid.latticeFile), clone.cs.inputDirectory, ) for fileName in additionalFiles or []: pathTools.copyOrWarn("additional file", fromPath(fileName), clone.cs.inputDirectory) return clone
def copyInterfaceInputs(cs, destination: str, sourceDir: Optional[str] = None) -> Dict[str, str]: """ Copy sets of files that are considered "input" from each active interface. This enables developers to add new inputs in a plugin-dependent/ modular way. In parameter sweeps, these often have a sourceDir associated with them that is different from the cs.inputDirectory. Parameters ---------- cs : CaseSettings The source case settings to find input files destination: str The target directory to copy input files to sourceDir: str, optional The directory from which to copy files. Defaults to cs.inputDirectory Notes ----- This may seem a bit overly complex, but a lot of the behavior is important. Relative paths are copied into the target directory, which in some cases requires updating the setting that pointed to the file in the first place. This is necessary to avoid case dependencies in relavive locations above the input directory, which can lead to issues when cloneing case suites. In the future this could be simplified by adding a concept for a suite root directory, below which it is safe to copy files without needing to update settings that point with a relative path to files that are below it. """ activeInterfaces = interfaces.getActiveInterfaceInfo(cs) sourceDir = sourceDir or cs.inputDirectory sourceDirPath = pathlib.Path(sourceDir) destPath = pathlib.Path(destination) newSettings = {} assert destPath.is_dir() for klass, kwargs in activeInterfaces: interfaceFileNames = klass.specifyInputs(cs) # returned files can be absolute paths, relative paths, or even glob patterns. # Since we don't have an explicit way to signal about these, we sort of have to # guess. In future, it might be nice to have interfaces specify which # explicitly. for key, files in interfaceFileNames.items(): if isinstance(key, settings.Setting): label = key.name else: label = key for f in files: path = pathlib.Path(f) if path.is_absolute() and path.exists() and path.is_file(): # looks like an extant, absolute path; no need to do anything pass else: # relative path/glob. Should be safe to just use glob resolution srcFiles = list(sourceDirPath.glob(f)) for sourceFullPath in srcFiles: if not sourceFullPath: continue _sourceDir, sourceName = os.path.split(sourceFullPath) sourceName = sourceFullPath.name destFile = (destPath / sourceName).relative_to(destPath) pathTools.copyOrWarn(label, sourceFullPath, destPath / sourceName) if len(srcFiles) > 1: runLog.warning( f"Input files for `{label}` resolved to more " "than one file; cannot update settings safely.") elif isinstance(key, settings.Setting): newSettings[key.name] = str(destFile) return newSettings