diff --git a/opencmp/config_functions/expanded_config_parser.py b/opencmp/config_functions/expanded_config_parser.py index ce01a8f..1e9ddb3 100644 --- a/opencmp/config_functions/expanded_config_parser.py +++ b/opencmp/config_functions/expanded_config_parser.py @@ -63,6 +63,7 @@ 'restart_from': ''}, 'VISUALIZATION': {'save_to_file': False, 'save_type': '.sol', + 'save_vtu_each_timestep': False, 'save_frequency': ['1', 'numit'], 'subdivision': -1, 'split_components': False}, diff --git a/opencmp/helpers/saving.py b/opencmp/helpers/saving.py index 9103aa3..3507417 100644 --- a/opencmp/helpers/saving.py +++ b/opencmp/helpers/saving.py @@ -15,8 +15,9 @@ # . # ######################################################################################################################## +import re from typing import Union -from ngsolve import GridFunction, CoefficientFunction +from ngsolve import GridFunction, CoefficientFunction, VTKOutput from ..models import Model from pathlib import Path @@ -52,6 +53,15 @@ def __init__(self, model: Model, quiet: bool = False) -> None: self.base_filename_sol = self.save_dir_sol + model.name() + '_' self.base_filename_phi_sol = self.save_dir_phi_sol + 'phi' + '_' self.base_subdivision = model.config.get_item(['VISUALIZATION', 'subdivision'], int, quiet) + self.save_vtu_each_timestep = ( + base_type == '.vtu' + and model.config.get_item( + ['VISUALIZATION', 'save_vtu_each_timestep'], bool, quiet) + ) + self.model_name = model.name() + self.mesh = model.mesh + self.save_names = model.save_names + self.pvd_entries = {False: {}, True: {}} # Create the save dir if it doesn't exist Path(self.save_dir).mkdir(parents=True, exist_ok=True) @@ -91,3 +101,43 @@ def save(self, gfu: Union[GridFunction, CoefficientFunction], timestep: float, D # Save to file gfu.Save(filename) + + if self.save_vtu_each_timestep: + self._save_vtu(gfu, timestep, DIM) + + def _save_vtu(self, gfu: Union[GridFunction, CoefficientFunction], timestep: float, DIM: bool) -> None: + """Write the just-saved checkpoint to VTU and refresh its PVD collection.""" + if DIM: + vtu_dir = self.save_dir_phi_vtu + basename = 'phi_' + str(timestep) + names = ['phi'] + pvd_filename = self.save_dir_phi + 'phi_transient.pvd' + relative_filename = self.model_name + '_vtu/' + basename + '.vtu' + else: + vtu_dir = self.save_dir_vtu + basename = self.model_name + '_' + str(timestep) + names = self.save_names + pvd_filename = self.save_dir + self.model_name + '_transient.pvd' + relative_filename = self.model_name + '_vtu/' + basename + '.vtu' + + # On the first write of a run, keep any entries already in the .pvd so a + # resumed run doesn't truncate the collection to just the new timesteps. + if not self.pvd_entries[DIM] and Path(pvd_filename).is_file(): + self.pvd_entries[DIM] = {float(t): f for t, f in + re.findall(r'timestep="([^"]+)".*?file="([^"]+)"', + Path(pvd_filename).read_text())} + + coefs = list(gfu.components) if isinstance(gfu, GridFunction) and len(gfu.components) > 0 else [gfu] + VTKOutput(ma=self.mesh, coefs=coefs, names=names, + filename=vtu_dir + basename, subdivision=self.base_subdivision).Do() + + self.pvd_entries[DIM][float(timestep)] = relative_filename + with open(pvd_filename, 'w') as pvd: + pvd.write('\n' + '\n' + '\n') + for time, path in sorted(self.pvd_entries[DIM].items()): + pvd.write('\n' + % (time, path)) + pvd.write('\n') diff --git a/opencmp/post_processing/__init__.py b/opencmp/post_processing/__init__.py index d1d83e7..ee8b6c7 100644 --- a/opencmp/post_processing/__init__.py +++ b/opencmp/post_processing/__init__.py @@ -44,8 +44,10 @@ def run_post_processing(config_parser: ConfigParser, solver: Solver, sol: GridFu save_output = config_parser.get_item(['VISUALIZATION', 'save_to_file'], bool, quiet=True) save_type = config_parser.get_item(['VISUALIZATION', 'save_type'], str, quiet=True) + save_vtu_each_timestep = config_parser.get_item( + ['VISUALIZATION', 'save_vtu_each_timestep'], bool, quiet=True) # Run the post-processor to convert the .sol to .vtu - if save_output and save_type == '.vtu': + if save_output and save_type == '.vtu' and not save_vtu_each_timestep: print('Converting saved output to VTU.') sol_to_vtu(config_parser, solver) @@ -55,4 +57,3 @@ def run_post_processing(config_parser: ConfigParser, solver: Solver, sol: GridFu sol_to_components(config_parser, config_parser.get_item(['OTHER', 'run_dir'], str) + '/output/', solver.model) -