506 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			506 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from dolfin import *
 | |
| import dolfin
 | |
| import argparse
 | |
| from common import inout
 | |
| import numpy as np
 | |
| import sys
 | |
| # import ruamel.yaml as yaml
 | |
| from pathlib import Path
 | |
| import csv
 | |
| import shutil
 | |
| 
 | |
| # parameters['allow_extrapolation'] = True
 | |
| # careful with this option... can give nonsensical results outside
 | |
| 
 | |
| 
 | |
| if '2017' in dolfin.__version__:
 | |
|     class MPI(MPI):
 | |
|         comm_world = MPI.comm_world
 | |
| 
 | |
| 
 | |
| def solution_function_space(options):
 | |
|     ''' Create function space to read in solution checkpoints, as specified in
 | |
|     options.
 | |
| 
 | |
|     Args:
 | |
|         options (dict):     Options dictionary (from input file)
 | |
| 
 | |
|     Returns:
 | |
|         FunctionSpace:      Solution checkpoint function space
 | |
|     '''
 | |
|     mesh, _, _ = inout.read_mesh(options['mesh'])
 | |
| 
 | |
|     if 'fluid' in options:
 | |
|         if options['fem']['velocity_space'].lower() == 'p2':
 | |
|             Evel = VectorElement('P', mesh.ufl_cell(), 2)
 | |
| 
 | |
|         elif options['fem']['velocity_space'].lower() == 'p1':
 | |
|             Evel = VectorElement('P', mesh.ufl_cell(), 1)
 | |
| 
 | |
|         else:
 | |
|             raise Exception('Velocity space {} not yet implemented!'.format(
 | |
|                 options['fem']['velocity_space'].lower()))
 | |
| 
 | |
|     elif 'material' in options:
 | |
|         if options['incompressibility']['mixed_formulation']:
 | |
|             raise Exception('Only compressible hyperelasticity data is '
 | |
|                             'supported here, for simplicity')
 | |
| 
 | |
|         deg = options['solver']['fe_degree']
 | |
|         Evel = VectorElement('P', mesh.ufl_cell(), deg)
 | |
| 
 | |
|     V = FunctionSpace(mesh, Evel)
 | |
| 
 | |
|     return V
 | |
| 
 | |
| 
 | |
| def measurement_function_spaces(options):
 | |
|     ''' Create function space for measurements, as specified in
 | |
|     options.
 | |
| 
 | |
|     Args:
 | |
|         options (dict):     Options dictionary (from input file)
 | |
| 
 | |
|     Returns:
 | |
|         list((Vector)FunctionSpace):  list of function spaces on measurement
 | |
|             mesh, scalar if measurements are scalar, else VectorFunctionSpace
 | |
|         list(VectorFunctionSpace):  if scalar measurements, a list of
 | |
|             corresponding VectorFunctionSpace for interpolation, else empty
 | |
|             list
 | |
|     '''
 | |
| 
 | |
|     measurement_lst = options['estimation']['measurements']
 | |
|     if not isinstance(measurement_lst, list):
 | |
|         measurement_lst = [measurement_lst]
 | |
| 
 | |
|     meshes = [meas['mesh'] for meas in measurement_lst]
 | |
| 
 | |
|     if 'fe_degree' in options['estimation']['measurements'][0]:
 | |
|         degree = options['estimation']['measurements'][0]['fe_degree']
 | |
|     else:
 | |
|         degree = 1
 | |
| 
 | |
|     for measurement in measurement_lst[1:]:
 | |
|         if not degree == measurement['fe_degree']:
 | |
|             raise Exception('fe_degree must the the same for all '
 | |
|                             'measurements!')
 | |
| 
 | |
|     if degree in  (1, 2):
 | |
|         element_family = 'P'
 | |
|     elif degree == 0:
 | |
|         element_family = 'DG'
 | |
|     else:
 | |
|         raise Exception('Unsupported measurement FE degree: {}'
 | |
|                         .format(degree))
 | |
| 
 | |
|     scalar = [False]*len(measurement_lst)
 | |
|     for i, measurement in enumerate(measurement_lst):
 | |
|         if 'velocity_direction' in measurement:
 | |
|             direction = measurement['velocity_direction']
 | |
|             if direction and None not in direction and sum(direction) > 0:
 | |
|                 scalar[i] = True
 | |
| 
 | |
|     V = []
 | |
|     V_aux = []
 | |
|     for scal, file in zip(scalar, meshes):
 | |
|         mesh, _, _ = inout.read_mesh(file)
 | |
|         if scal:
 | |
|             V.append(FunctionSpace(mesh, element_family, degree))
 | |
|             # 1. interpolate velocity vector onto measurement grid (V_aux)
 | |
|             # 2. perform component projection in the measurement space
 | |
|             # ---> need to store both scalar and vector spaces
 | |
|             V_aux.append(VectorFunctionSpace(mesh, element_family, degree))
 | |
|         else:
 | |
|             V.append(VectorFunctionSpace(mesh, element_family, degree))
 | |
| 
 | |
|     return V, V_aux
 | |
| 
 | |
| 
 | |
| def find_checkpoints(options):
 | |
|     ''' Find all checkpoints of a simulation from configuration in options
 | |
|     dictionary.
 | |
| 
 | |
|     Args:
 | |
|         options (dict):     Options dictionary (from YAML input file)
 | |
| 
 | |
|     Returns:
 | |
|         list:     List of found checkpoint indices
 | |
|         list:     List of found checkpoint times
 | |
|         list:     List of found checkpoint files
 | |
|     '''
 | |
|     if MPI.rank(MPI.comm_world) > 0:
 | |
|         return
 | |
| 
 | |
|     chkpt_root = options['io']['write_path'] + '/checkpoint/{i}/u.h5'
 | |
| 
 | |
|     indices = options['estimation']['measurements'][0]['indices']
 | |
|     if not indices:
 | |
|         path_all = list(Path().glob(chkpt_root.format(i='*')))
 | |
|         indices = sorted(int(str(s).split('/')[-2]) for s in path_all)
 | |
| 
 | |
|     # dt_meas = options['timemarching']['checkpoint_dt']
 | |
|     # times = np.concatenate(([options['timemarching']['dt']],
 | |
|     #                         dt_meas*np.array(indices[1:])))
 | |
|     dt = options['timemarching']['dt']
 | |
|     times = dt*np.array(indices)
 | |
| 
 | |
|     print('indices: \n')
 | |
|     print('\t', indices)
 | |
|     print('times: \n')
 | |
|     print('\t', times)
 | |
| 
 | |
|     files = [chkpt_root.format(i=i) for i in indices]
 | |
| 
 | |
|     # check if all files are found
 | |
|     for f in files:
 | |
|         if not Path(f).is_file():
 | |
|             raise FileNotFoundError(f)
 | |
| 
 | |
|     return indices, times, files
 | |
| 
 | |
| 
 | |
| def generate(options, seed_lst, print_norms=False):
 | |
|     ''' Generate measurements.
 | |
| 
 | |
|     Args:
 | |
|         options (dict):     Options dictionary (from input file)
 | |
|         seed_lst (list):    list of random seeds
 | |
|         print_norms (bool):   switch for printing norms of measurements
 | |
|     '''
 | |
| 
 | |
|     V = solution_function_space(options)
 | |
|     ndim = V.mesh().topology().dim()
 | |
|     u = Function(V)
 | |
| 
 | |
|     V_meas_lst, V_aux_lst = measurement_function_spaces(options)
 | |
|     u_meas_lst = [Function(V_meas, name='measurement') for V_meas in
 | |
|                   V_meas_lst]
 | |
|     u_meas_cpy = [Function(V_meas, name='measurement') for V_meas in
 | |
|                   V_meas_lst]
 | |
|     LI = LagrangeInterpolator
 | |
|     if V_aux_lst:
 | |
|         u_aux_lst = [Function(V_aux) for V_aux in V_aux_lst]
 | |
|         comp_assigner = [FunctionAssigner([V]*ndim, V_aux) for V, V_aux in
 | |
|                          zip(V_meas_lst, V_aux_lst)]
 | |
| 
 | |
|     measurement_lst = options['estimation']['measurements']
 | |
|     noise_sd = [meas['noise_stddev'] for meas in measurement_lst]
 | |
| 
 | |
|     # if 'project' in options['estimation']['measurements']:
 | |
|     #     project_switch = bool(options['estimation']['measurements']
 | |
|     #                           ['project'])
 | |
|     # else:
 | |
|     #     project_switch = False
 | |
|     project_switch = False
 | |
| 
 | |
|     indices, times, files = find_checkpoints(options)
 | |
| 
 | |
|     outfile_root_lst = [meas['file_root'] for meas in measurement_lst]
 | |
|     xdmf_paths = [meas['xdmf_file'] for meas in measurement_lst]
 | |
| 
 | |
|     # don't automatically append seed{s} do path anymore!
 | |
|     # suppose seed{s} is included
 | |
|     # if seed_lst[0] > 0:
 | |
|     #     for i, (out, xdmf) in enumerate(zip(outfile_root_lst, xdmf_paths)):
 | |
|     #         # if isinstance(out, str) and 'seed{s}' not in out:
 | |
|     #         #     out_ = out.split('/')
 | |
|     #         #     outfile_root_lst[i] = '/'.join(out_[:-1] + ['seed{s}',
 | |
|     #         #                                                 out_[-1]])
 | |
|     #         if isinstance(xdmf, str) and 'seed{s}' not in xdmf:
 | |
|     #             xdmf_ = xdmf.split('/')
 | |
|     #             xdmf_paths[i] = '/'.join(xdmf_[:-1] + ['seed{s}', xdmf_[-1]])
 | |
| 
 | |
|     # check if a list of seeds is given that {s} is included in path
 | |
|     if len(seed_lst) > 1:
 | |
|         for out in outfile_root_lst:
 | |
|             assert '{s}' in out, ('For a list of seeds, the string \'{s}\' '
 | |
|                                   'must be included in the file_root and will '
 | |
|                                   'be replaced by the seed number')
 | |
| 
 | |
|     if any(xdmf_paths):
 | |
|         xdmf_lst = []
 | |
|         for pth in xdmf_paths:
 | |
|             if not isinstance(pth, str):
 | |
|                 raise TypeError('xdmf_file setting must be None or a string '
 | |
|                                 'indicating the target XDMF file. Got type {}'.
 | |
|                                 format(type(pth)))
 | |
| 
 | |
|             seed_dict = {}
 | |
|             for seed in seed_lst:
 | |
|                 file = XDMFFile(pth.format(s=seed))
 | |
|                 file.parameters['rewrite_function_mesh'] = False
 | |
|                 seed_dict[seed] = file
 | |
|             xdmf_lst.append(seed_dict)
 | |
|     else:
 | |
|         xdmf_lst = [None]*len(u_meas_lst)
 | |
| 
 | |
|     for count, (index, time, infile) in enumerate(zip(indices, times, files)):
 | |
|         if MPI.rank(MPI.comm_world) == 0:
 | |
|             print('Processing {} at t = {}'.format(infile, time))
 | |
| 
 | |
|         t_ = inout.read_HDF5_data(u.function_space().mesh().mpi_comm(), infile,
 | |
|                                   u, '/u')
 | |
| 
 | |
|         assert np.allclose(time, t_), ('Timestamps do not match! {} vs {} '
 | |
|                                        '(HDF5 file)'.format(time, t_))
 | |
| 
 | |
|         # interpolate u to measurement meshes
 | |
|         for k, (u_meas, outfile_root, xdmf, sd) in enumerate(zip(
 | |
|                 u_meas_lst, outfile_root_lst, xdmf_lst, noise_sd)):
 | |
| 
 | |
|             if project_switch:
 | |
|                 u_meas.assign(project(u, u_meas.function_space()))
 | |
| 
 | |
|             else:
 | |
|                 if V_aux_lst:
 | |
|                     if MPI.rank(MPI.comm_world) == 0:
 | |
|                         print('- Scalar velocity component')
 | |
|                     direction = (options['estimation']['measurements'][k]
 | |
|                                  ['velocity_direction'])
 | |
|                     if direction.count(0) == 2 and direction.count(1) == 1:
 | |
|                         LI.interpolate(u_meas, u.sub(direction.index(1)))
 | |
| 
 | |
|                     else:
 | |
|                         assert u_meas.value_shape() == []
 | |
|                         # normalize projection direction
 | |
|                         direction = np.array(direction, dtype=np.float64)
 | |
|                         direction /= np.sqrt(np.dot(direction, direction))
 | |
|                         if MPI.rank(MPI.comm_world) == 0:
 | |
|                             print('-   direction: {}'.format(direction))
 | |
| 
 | |
|                         LagrangeInterpolator.interpolate(u_aux_lst[k], u)
 | |
| 
 | |
|                         # This is faster than simply Xobs_aux.split(True) !
 | |
|                         u_i = [u_meas] + [u_meas.copy(True) for j in
 | |
|                                           range(ndim - 1)]
 | |
| 
 | |
|                         comp_assigner[k].assign(u_i, u_aux_lst[k])
 | |
|                         u_meas.vector()[:] *= direction[0]
 | |
|                         for ui, d in zip(u_i[1:], direction[1:]):
 | |
|                             if d:
 | |
|                                 u_meas.vector().axpy(d, ui.vector())
 | |
| 
 | |
|                 else:
 | |
|                     if MPI.rank(MPI.comm_world) == 0:
 | |
|                         print('- Full velocity vector')
 | |
|                     LI.interpolate(u_meas, u)
 | |
|             if sd:
 | |
|                 u_meas_cpy[k].assign(u_meas)
 | |
| 
 | |
|             # add noise
 | |
|             for seed in seed_lst:
 | |
|                 if sd:
 | |
|                     if seed > 0:
 | |
|                         np.random.seed(seed + count)
 | |
| 
 | |
|                         if MPI.rank(MPI.comm_world) == 0:
 | |
|                             print('- Add noise with stddev = {}, seed = {}'
 | |
|                                   .format(sd, seed + count))
 | |
| 
 | |
|                     noise = np.random.normal(0., sd,
 | |
|                                              u_meas.vector().local_size())
 | |
|                     u_meas.assign(u_meas_cpy[k])
 | |
|                     u_meas.vector()[:] += noise
 | |
| 
 | |
|                 if MPI.rank(MPI.comm_world) == 0:
 | |
|                     if print_norms:
 | |
|                         print('Writing file at t = {}\t |u_m| = {}'.format(
 | |
|                             time, norm(u_meas)))
 | |
|                     else:
 | |
|                         print('Writing file at t = {}\t'.format(time))
 | |
| 
 | |
|                 outfile = outfile_root.format(i=index, s=seed)
 | |
|                 inout.write_HDF5_data(
 | |
|                     u_meas.function_space().mesh().mpi_comm(), outfile, u_meas,
 | |
|                     '/u', time)
 | |
| 
 | |
|                 if xdmf:
 | |
|                     xdmf[seed].write(u_meas, time)
 | |
| 
 | |
|     # THIS IS OBSOLETE NOW
 | |
|     # write indices and timesteps
 | |
|     # write_timestamps(options, indices, times, seed_lst)
 | |
| 
 | |
| 
 | |
| def write_timestamps(options, indices, times, seed_lst):
 | |
|     ''' Write time stamps of measurements to csv file.
 | |
| 
 | |
|     Args:
 | |
|         options (dict):   Options dictionary (from YAML input file)
 | |
|         indices (list):   List of checkpoint indices
 | |
|         times (list):     List of checpoint times
 | |
|         seed_lst (list):  List of random seeds
 | |
|     '''
 | |
|     if MPI.rank(MPI.comm_world) > 0:
 | |
|         return
 | |
| 
 | |
|     warning('timestamps.csv is OBSOLETE!')
 | |
| 
 | |
|     file_root_lst = [meas['file_root'] for meas in options['estimation']
 | |
|                      ['measurements']]
 | |
| 
 | |
|     # if seed_lst[0]:
 | |
|     #     for i, out in enumerate(file_root_lst):
 | |
|     #         if isinstance(out, str) and 'seed{s}' not in out:
 | |
|     #             out_ = out.split('/')
 | |
|     #             file_root_lst[i] = '/'.join(out_[:-1]
 | |
|     #                                         + ['seed{s}', out_[-1]])
 | |
| 
 | |
|     for file_root in file_root_lst:
 | |
|         for seed in seed_lst:
 | |
|             path = Path(file_root.format(s=seed, i=-1)).parent
 | |
|             if seed > 0 and 'seed{s}'.format(s=seed) not in str(path):
 | |
|                 path = path.joinpath('seed{s}'.format(s=seed))
 | |
|             path = path.joinpath('timestamps.csv')
 | |
|             print('Writing timestamps to file: {}'.format(path))
 | |
|             with path.open('w') as file:
 | |
|                 writer = csv.writer(file, delimiter=' ')
 | |
|                 for i, t in zip(indices, times):
 | |
|                     writer.writerow((i, t))
 | |
| 
 | |
| 
 | |
| def copy_inputfile(options, inputfile, seed_lst):
 | |
|     ''' Copy input file of reference solution to measurements directory.
 | |
| 
 | |
|     Args:
 | |
|         options (dict):   Options dictionary (from YAML input file)
 | |
|         inputfile (str):  Path of input file to be copied
 | |
|         seed_lst (list):  List of random seeds
 | |
|     '''
 | |
|     if MPI.rank(MPI.comm_world) > 0:
 | |
|         return
 | |
| 
 | |
|     file_root_lst = [meas['file_root'] for meas in options['estimation']
 | |
|                      ['measurements']]
 | |
|     for file_root in file_root_lst:
 | |
|         for seed in seed_lst:
 | |
|             path = Path(file_root.format(s=seed, i=-1)).parent
 | |
|             # if seed > 0 and 'seed{s}'.format(s=seed) not in str(path):
 | |
|             #     path = path.joinpath('seed{s}'.format(s=seed))
 | |
|             path = path.joinpath('input.yaml')
 | |
|             # path.parent.mkdir(parents=True, exist_ok=True)
 | |
|             shutil.copy2(str(inputfile), str(path))
 | |
|             print('Copied input file to {}'.format(path))
 | |
| 
 | |
| 
 | |
| def dump_example_options(path):
 | |
|     ''' Dump example options to given path and exit.
 | |
| 
 | |
|     Args:
 | |
|         path (str):     Path to input file
 | |
|     '''
 | |
|     example = '''\
 | |
| # example fractional step inputfile for gen_measurements_from_checkpoints.py
 | |
| mesh: './meshes/mesh.h5'
 | |
| 
 | |
| io:
 | |
|     # Path containing checkpoints in a checkpoints/ folder
 | |
|     write_path: './results/test/'
 | |
| 
 | |
| timemarching:
 | |
|     # final time of simulation to be processed
 | |
|     T: 0.4
 | |
|     # simulation time step size (not dt_checkpoint)
 | |
|     dt: 0.001
 | |
| 
 | |
| fem:
 | |
|     # Velocity and presse (for monolithic) function spaces of checkpoints
 | |
|     velocity_space: 'p1'
 | |
|     pressure_space: 'p1'
 | |
| 
 | |
| estimation:
 | |
|     measurements:
 | |
|         # List of measurements
 | |
|         -
 | |
|             # Mesh of 1. measurement set
 | |
|             mesh: './measurements/mesh_meas.h5'
 | |
| 
 | |
|             # degree of finite element function space
 | |
|             # (1 for linear, 0 for discontinuous piece-wise constant)
 | |
|             fe_degree: 0
 | |
| 
 | |
|             # velocity measurement files to be written by this tool
 | |
|             # {i} will be replaced by the corresponding index of the checkpoint
 | |
|             # if given, {s} may be replaced by the seed number, for instance
 | |
|             #   path/seed{s}/u{i}.h5
 | |
|             file_root: './measurements/u{i}.h5'
 | |
| 
 | |
|             # XDMF file where measurements are optionally stored for
 | |
|             # visualization (one file for all time steps)
 | |
|             xdmf_file: './measurements/meas.xdmf'
 | |
| 
 | |
|             # Select a velocity component:
 | |
|             #  * None (~) means "use complete vector"
 | |
|             #  * else, use a scalar velocity by projecting onto the given
 | |
|             #    direction vector, [x, y, (z)]
 | |
|             velocity_direction: ~
 | |
| 
 | |
|             # (absolute) standard deviation of Gaussian noise
 | |
|             noise_stddev: 10.
 | |
| 
 | |
|             # indices of checkpoints to be processed:
 | |
|             #  * 0 == all
 | |
|             #  * a list of integes
 | |
|             indices: 0
 | |
| 
 | |
|         -
 | |
|             # second measurement ...
 | |
|         -
 | |
|             # further measurements ...
 | |
| '''
 | |
| 
 | |
|     with open(path, 'w') as f:
 | |
|         f.write(example)
 | |
|     print('Example options:\n\n')
 | |
|     print(example)
 | |
|     print('\ndumped example options to file: {}'.format(path))
 | |
|     sys.exit(0)
 | |
| 
 | |
| 
 | |
| def get_parser():
 | |
|     parser = argparse.ArgumentParser(description='''\
 | |
| Generate measurements from HDF5 checkpoints, generated by the Fractional-Step
 | |
| or the monolithic Navier-Stokes solvers.
 | |
| 
 | |
| Reads options from the given input file.
 | |
| 
 | |
| See the example options for further explanations::
 | |
| (`gen_measurements_from_checkpoints.py -d example.yaml`).
 | |
| ''', formatter_class=argparse.RawDescriptionHelpFormatter)
 | |
|     parser.add_argument('inputfile', type=str, help='path to yaml input file')
 | |
|     # parser.add_argument('-s', '--seed', type=int, default=-1,
 | |
|     #                     help='seed for random generator')
 | |
|     parser.add_argument('-s', '--seed', nargs='+', help='seed or list of seeds'
 | |
|                         ' for noise random number generator. '
 | |
|                         'Will take the given seed for the first time step '
 | |
|                         'and increment by +1 for all subsequent time steps, '
 | |
|                         'so that noise is different for all times '
 | |
|                         'but reproducible', default=[-1], type=int)
 | |
|     parser.add_argument('-n', '--print_norms', action='store_true',
 | |
|                         help='print norms')
 | |
|     parser.add_argument('-d', '--dump', action='store_true',
 | |
|                         help='dump minimal example parameters to inputfile')
 | |
| 
 | |
|     return parser
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     args = get_parser().parse_args()
 | |
| 
 | |
|     if args.dump:
 | |
|         dump_example_options(args.inputfile)
 | |
| 
 | |
|     seed = args.seed
 | |
| 
 | |
|     if seed[0] <= 0:
 | |
|         assert len(seed) == 1, 'if multiple seeds are given, all should be > 0'
 | |
| 
 | |
|     try:
 | |
|         options = inout.read_parameters(args.inputfile)
 | |
|     except IOError:
 | |
|         raise IOError('File could not be read: {}'.format(args.inputfile))
 | |
| 
 | |
|     generate(options, seed, args.print_norms)
 | |
| 
 | |
|     copy_inputfile(options, args.inputfile, seed)
 |