def df_wrap(val, description, degree, sim): """ Wrap numbers as dolfin.Constant and strings as dolfin.Expression C++ code. Lists must be ndim long and contain either numbers or strings """ if isinstance(val, (int, float)): # A real number return dolfin.Constant(val) elif isinstance(val, str): # A C++ code string return OcellarisCppExpression(sim, val, description, degree) elif isinstance(val, (list, tuple)): D = sim.ndim L = len(val) if L != D: raise OcellarisError( 'Invalid length of list', 'BC list in "%r" must be length %d, is %d.' % (description, D, L), ) if all(isinstance(v, str) for v in val): # A list of C++ code strings return OcellarisCppExpression(sim, val, description, degree) else: # A mix of constants and (possibly) C++ strings val = [ df_wrap(v, description + ' item %d' % i, degree, sim) for i, v in enumerate(val) ] return dolfin.as_vector(val)
def get_variable(self, name): if not name == self.var_name: raise OcellarisError( 'FreeSurfaceZone field does not define %r' % name, 'This FreeSurfaceZone field defines %r' % self.var_name, ) return self.function
def update(self, timestep_number, t, dt): """ Update the mesh position according to the calculated fluid velocities """ sim = self.simulation hfunc = sim.data['height_function'] xpos = sim.data['height_function_x'] Nx = xpos.size old_h = hfunc.vector().get_local() with dolfin.Timer('Ocellaris update height and colour'): # Get updated mesh velocity in each xpos all_vel = numpy.zeros_like(xpos) vel = numpy.zeros(1, float) pos = numpy.zeros(2, float) uvert = sim.data['u1'] for i, x in enumerate(xpos): pos[:] = (x, old_h[i]) try: uvert.eval(vel, pos) except Exception: raise OcellarisError( 'Error in u1 eval in height function', 'Position %r outside the domain?' % (pos, ), ) all_vel[i] = vel[0] # Get height function max and min hmin = self.hmin.run(xpos=xpos) hmax = self.hmax.run(xpos=xpos) assert hmin.shape == xpos.shape assert hmax.shape == xpos.shape # Update the height function dt = sim.input.get_value('time/dt', required_type='float') old_h[:Nx] += dt * all_vel old_h[:Nx] = numpy.clip(old_h[:Nx], hmin, hmax) hfunc.vector().set_local(old_h) hfunc.vector().apply('insert') self._update_colour() # Report on the new height function self.simulation.reporting.report_timestep_value( 'min(h)', old_h[:Nx].min()) self.simulation.reporting.report_timestep_value( 'max(h)', old_h[:Nx].max()) self.simulation.hooks.run_custom_hook('MultiPhaseModelUpdated')
def __init__(self, simulation, var_name, inp_dict, subdomains, subdomain_id): """ Free slip boundary condition """ if var_name[-1] in '0123456789': raise OcellarisError( 'Error in FreeSlip boundary conditions', 'You must give the name of a vector field, like "u", ' 'not a component. You gave %r' % var_name, ) # Store the boundary condition for use in the solver bc = FreeSlipBC(simulation, subdomain_id) bcs = simulation.data['slip_bcs'] bcs.setdefault(var_name, []).append(bc) simulation.log.info(' FreeSlipBC for %s' % var_name)
def __init__(self, simulation, var_name, inp_dict, subdomains, subdomain_id): """ Open outlet boundary condition """ self.simulation = simulation hydrostatic = inp_dict.get_value('hydrostatic', False, 'bool') if not var_name == 'u': raise OcellarisError( 'Error in boundary condition', 'OpenOutletBoundary should be applied to ' '"u" only, p should be left out', ) # Store the boundary condition for use in the solver bc = OcellarisOutletBC(self.simulation, subdomain_id) bc.hydrostatic = hydrostatic self.simulation.data['outlet_bcs'].append(bc) self.simulation.log.info(' OpenOutletBoundary for %s' % var_name)
def get_facet_dof_indices(V): """ Get local indices for the facet dofs for each facet in the cell """ ndim = V.mesh().topology().dim() degree = V.ufl_element().degree() ndim_deg = (ndim, degree) if ndim_deg == (2, 1): # Linear triangle facet_dof_indices = numpy.zeros((3, 2), dtype=int) facet_dof_indices[0, :] = (1, 2) facet_dof_indices[1, :] = (0, 2) facet_dof_indices[2, :] = (0, 1) elif ndim_deg == (2, 2): # Quadratic triangle facet_dof_indices = numpy.zeros((3, 3), dtype=int) facet_dof_indices[0, :] = (1, 2, 3) facet_dof_indices[1, :] = (0, 2, 4) facet_dof_indices[2, :] = (0, 1, 5) elif ndim_deg == (3, 1): # Linear tetrahedron facet_dof_indices = numpy.zeros((4, 3), dtype=int) facet_dof_indices[0, :] = (1, 2, 3) facet_dof_indices[1, :] = (0, 2, 3) facet_dof_indices[2, :] = (0, 1, 3) facet_dof_indices[3, :] = (0, 1, 2) elif ndim_deg == (3, 2): # Quadratic tetrahedron facet_dof_indices = numpy.zeros((4, 6), dtype=int) facet_dof_indices[0, :] = (1, 2, 3, 4, 5, 6) facet_dof_indices[1, :] = (0, 2, 3, 4, 7, 8) facet_dof_indices[2, :] = (0, 1, 3, 5, 7, 9) facet_dof_indices[3, :] = (0, 1, 2, 6, 8, 9) else: raise OcellarisError( 'Unsupported element ndim=%d degree=%d' % ndim_deg, 'The boundary condition get_dof_region_marks ' 'code does not support this element', ) return facet_dof_indices
def register_dirichlet_condition(self, var_name, value, subdomains, subdomain_id): """ Add a Dirichlet condition to this variable """ if not isinstance(value, (float, int)): raise OcellarisError( 'Error in ConstantValue BC for %s' % var_name, 'The value %r is not a number' % value, ) df_value = dolfin.Constant(value) # Store the boundary condition for use in the solver bc = OcellarisDirichletBC(self.simulation, self.func_space, df_value, subdomains, subdomain_id) bcs = self.simulation.data['dirichlet_bcs'] bcs.setdefault(var_name, []).append(bc) self.simulation.log.info(' Constant value %r for %s' % (value, var_name))
def _run_cpp( self, taylor_arr, taylor_arr_old, alpha_arrs, global_min, global_max, boundary_dof_type, boundary_dof_value, ): """ Run the C++ implementation of the HierarchicalTaylor slope limiter The C++ versions are probably the best tested since they are fastest and hence most used """ funcs = { (2, 1): self.cpp_mod.hierarchical_taylor_slope_limiter_dg1_2D, (2, 2): self.cpp_mod.hierarchical_taylor_slope_limiter_dg2_2D, (3, 1): self.cpp_mod.hierarchical_taylor_slope_limiter_dg1_3D, (3, 2): self.cpp_mod.hierarchical_taylor_slope_limiter_dg2_3D, } key = (self.ndim, self.degree) if key not in funcs: raise OcellarisError( 'Unsupported dimension %d with degree %d' % key, 'Not supported in C++ version of the HierarchalTaylor limiter', ) # Update C++ input inp = self.input inp.set_global_bounds(global_min, global_max) inp.set_limit_cell(self.limit_cell) inp.set_boundary_values(boundary_dof_type, boundary_dof_value, self.enforce_boundary_conditions) limiter = funcs[key] limiter(inp.cpp_obj, taylor_arr, taylor_arr_old, *alpha_arrs)
def __init__(self, simulation): """ A simple height function multiphase model """ self.simulation = simulation simulation.log.info('Creating height function multiphase model') assert simulation.ncpu == 1, 'HeightFunctionMultiphaseModel does not run in parallel' # Define colour function V = simulation.data['Vc'] simulation.data['c'] = dolfin.Function(V) # The free surface mesh xmin = simulation.input.get_value( 'multiphase_solver/height_function_xmin', required_type='float') xmax = simulation.input.get_value( 'multiphase_solver/height_function_xmax', required_type='float') Nx = simulation.input.get_value('multiphase_solver/height_function_Nx', required_type='float') hinit = simulation.input.get_value( 'multiphase_solver/height_initial_h', required_type='float') self.eps = simulation.input.get_value( 'multiphase_solver/surface_thickness', required_type='float') hmin_code = simulation.input.get_value( 'multiphase_solver/height_min_code', required_type='string') hmax_code = simulation.input.get_value( 'multiphase_solver/height_max_code', required_type='string') # Runnable code, must define 'hmin' or 'hmax' arrays given input xpos array self.hmin = RunnablePythonString(simulation, hmin_code, 'multiphase_solver/height_min_code', 'hmin') self.hmax = RunnablePythonString(simulation, hmax_code, 'multiphase_solver/height_max_code', 'hmax') # Store colour function dof coordinates mesh = simulation.data['mesh'] gdim = mesh.geometry().dim() self.dof_pos = V.tabulate_dof_coordinates().reshape((-1, gdim)) # Create dummy dolfin Function to hold the height function (for restart file support) if Nx > V.dim(): raise OcellarisError( 'height_function_Nx too large', 'Maximum Nx for this colour function is %d' % V.dim(), ) simulation.data['height_function'] = hfunc = dolfin.Function(V) hfunc.vector()[:] = hinit hfunc.vector().apply('insert') simulation.data['height_function_x'] = numpy.linspace(xmin, xmax, Nx) self._update_colour() simulation.log.info(' Created surface mesh with %d elements' % (Nx - 1)) # Get the physical properties self.set_physical_properties(read_input=True) # Update the rho and nu fields before each time step simulation.hooks.add_pre_timestep_hook( self.update, 'HeightFunctionMultiphaseModel.update()') simulation.hooks.register_custom_hook_point('MultiPhaseModelUpdated')
def mark_cell_layers( simulation, V, layers=0, dof_region_marks=None, named_boundaries=None ): """ Return all cells on the boundary and all connected cells in a given number of layers surrounding the boundary cells. Vertex neighbours are used to determine a cells neighbours. The initial list of cells is taken from a dictionary mapping dof to boundary region number. All cells containing these dofs are taken as the zeroth layer. If no such dictionary is provided then get_dof_region_marks(simulation, V) is used, hence the cells containing the boundary facing facet are used as the zeroth level. If named_boundaries is given then only cells on the given named boundaries are included in the zeroth layer. The special name 'all' will include all boundary regions. @return: set of the cell numbers of marked cells """ if named_boundaries is None: named_boundaries = ['all'] if not named_boundaries: return set() if dof_region_marks is None: dof_region_marks = get_dof_region_marks(simulation, V) # Get all regions with names all_regions = {region.name: region.index for region in simulation.data['boundary']} all_idxs = {region.index: region.name for region in simulation.data['boundary']} # Verify that the boundary names are unique assert len(all_regions) == len(simulation.data['boundary']) assert len(all_regions) == len(all_idxs) # Check that all named regions correspond to boundary regions for rname in named_boundaries: if rname != 'all' and rname not in all_regions: raise OcellarisError( 'Unknown boundary region in input', '%r is not a boundary region' % rname, ) # Names of all boundary regions if 'all' not in named_boundaries: to_keep = [mark for mark, name in all_idxs.items() if name in named_boundaries] # Remove marks to unwanted bounbdary regions drm = { dof: [mark for mark in dof_marks if mark in to_keep] for dof, dof_marks in dof_region_marks.items() } # Remove dofs wih empty region mark list dof_region_marks = {k: v for k, v in drm.items() if v} # Mark initial zeroth layer cells mesh = simulation.data['mesh'] dm = V.dofmap() boundary_cells = set() for cell in dolfin.cells(mesh): dofs = dm.cell_dofs(cell.index()) for dof in dofs: if dof in dof_region_marks: boundary_cells.add(cell.index()) continue # Iteratively mark cells adjacent to boundary cells for _ in range(layers): boundary_cells_old = set(boundary_cells) for cell_index in boundary_cells_old: vertices = simulation.data['connectivity_CV'](cell_index) for vert_index in vertices: for nb in simulation.data['connectivity_VC'](vert_index): boundary_cells.add(nb) return boundary_cells
def run(self, use_weak_bcs=None): """ Perform slope limiting of DG Lagrange functions """ # No limiter needed for piecewice constant functions if self.degree == 0: return timer = df.Timer('Ocellaris HierarchalTaylorSlopeLimiter') # Update the Taylor function with the current Lagrange values lagrange_to_taylor(self.phi, self.taylor) taylor_arr = get_local(self.taylor) alpha_arrs = [alpha.vector().get_local() for alpha in self.alpha_funcs] # Get global bounds, see SlopeLimiterBase.set_initial_field() global_min, global_max = self.global_bounds # Update previous field values Taylor functions if self.phi_old is not None: lagrange_to_taylor(self.phi_old, self.taylor_old) taylor_arr_old = get_local(self.taylor_old) else: taylor_arr_old = taylor_arr # Get updated boundary conditions weak_vals = None use_weak_bcs = self.use_weak_bcs if use_weak_bcs is None else use_weak_bcs if use_weak_bcs: weak_vals = self.phi.vector().get_local() boundary_dof_type, boundary_dof_value = self.boundary_conditions.get_bcs( weak_vals) # Run the limiter implementation if self.use_cpp: self._run_cpp( taylor_arr, taylor_arr_old, alpha_arrs, global_min, global_max, boundary_dof_type, boundary_dof_value, ) elif self.degree == 1 and self.ndim == 2: self._run_dg1( taylor_arr, taylor_arr_old, alpha_arrs[0], global_min, global_max, boundary_dof_type, boundary_dof_value, ) elif self.degree == 2 and self.ndim == 2: self._run_dg2( taylor_arr, taylor_arr_old, alpha_arrs[0], alpha_arrs[1], global_min, global_max, boundary_dof_type, boundary_dof_value, ) else: raise OcellarisError( 'Unsupported dimension for Python version of the HierarchalTaylor limiter', 'Only 2D is supported', ) # Update the Lagrange function with the limited Taylor values set_local(self.taylor, taylor_arr, apply='insert') taylor_to_lagrange(self.taylor, self.phi) # Enforce boundary conditions if self.enforce_boundary_conditions: has_dbc = boundary_dof_type == self.boundary_conditions.BC_TYPE_DIRICHLET vals = self.phi.vector().get_local() vals[has_dbc] = boundary_dof_value[has_dbc] self.phi.vector().set_local(vals) self.phi.vector().apply('insert') # Update the secondary output arrays, alphas for alpha, alpha_arr in zip(self.alpha_funcs, alpha_arrs): alpha.vector().set_local(alpha_arr) alpha.vector().apply('insert') timer.stop()