def __init__(self, F_fluid, F_solid, w, wdot, bcs_mesh, bcs, dF_fluid_u, dF_fluid_udot, dF_solid_u, dF_solid_udot, update=None, report=None, *args, **kwargs): self.F_fluid_form = F_fluid self.dF_fluid_u_form = dF_fluid_u self.dF_fluid_udot_form = dF_fluid_udot self.F_solid_form = F_solid self.dF_solid_u_form = dF_solid_u self.dF_solid_udot_form = dF_solid_udot self.bcs_mesh = bcs_mesh self.bcs = bcs self.w = w self.wdot = wdot self.a = df.Constant(1.0) self.dF_fluid_form = self.dF_fluid_u_form + self.a * self.dF_fluid_udot_form self.dF_solid_form = self.dF_solid_u_form + self.a * self.dF_solid_udot_form self.report = report self.update = update self.form_compiler_parameters = {"optimize": True} if "form_compiler_parameters" in kwargs: self.form_compiler_parameters = kwargs["form_compiler_parameters"] self.assembler = df.SystemAssembler( self.dF_fluid_form + self.dF_solid_form, self.F_fluid_form + self.F_solid_form, self.bcs + self.bcs_mesh) self.x = df.as_backend_type(w.vector()) self.x_petsc = self.x.vec() self.xdot = df.as_backend_type(wdot.vector()) self.xdot_petsc = self.xdot.vec() self.b_petsc = self.x_petsc.duplicate() # We need to initialise the matrix size and local-to-global maps self.A_dolfin = df.PETScMatrix() self.assembler.init_global_tensor( self.A_dolfin, df.Form(self.dF_fluid_form + self.dF_fluid_form)) self.A_petsc = self.A_dolfin.mat() self.xx = self.A_petsc.createVecRight() self.xxdot = self.A_petsc.createVecRight() self.A1_dolfin = df.PETScMatrix() self.assembler.init_global_tensor( self.A1_dolfin, df.Form(self.dF_fluid_form + self.dF_fluid_form)) self.A2_dolfin = df.PETScMatrix() self.assembler.init_global_tensor( self.A2_dolfin, df.Form(self.dF_fluid_form + self.dF_fluid_form))
def _construct(self, field_inp): # Read the input self.name = field_inp.get_value('name', required_type='string') self.var_name = field_inp.get_value('variable_name', 'phi', 'string') self.stationary = field_inp.get_value('stationary', False, 'bool') self.radius = field_inp.get_value('radius', required_type='float') self.plot = field_inp.get_value('plot', False, 'bool') # Show the input sim = self.simulation sim.log.info('Creating a free surface zone field %r' % self.name) sim.log.info(' Variable: %r' % self.var_name) sim.log.info(' Stationary: %r' % self.stationary) # Create the level set model mesh = sim.data['mesh'] func_name = '%s_%s' % (self.name, self.var_name) self.V = dolfin.FunctionSpace(mesh, 'DG', 0) self.function = dolfin.Function(self.V) self.function.rename(func_name, func_name) sim.data[func_name] = self.function # Get the level set view level_set_view = sim.multi_phase_model.get_level_set_view() level_set_view.add_update_callback(self.update) # Form to compute the cell average distance to the free surface v = dolfin.TestFunction(self.V) ls = level_set_view.level_set_function cv = dolfin.CellVolume(self.V.mesh()) self.dist_form = dolfin.Form(ls * v / cv * dolfin.dx) if self.plot: sim.io.add_extra_output_function(self.function)
def __init__(self, simulation, u_conv): """ Given a velocity in DG, e.g DG2, produce a velocity in DGT0, i.e. a constant on each facet """ V = u_conv[0].function_space() V_dgt0 = dolfin.FunctionSpace(V.mesh(), 'DGT', 0) u = dolfin.TrialFunction(V_dgt0) v = dolfin.TestFunction(V_dgt0) ndim = simulation.ndim w = u_conv w_new = dolfin.as_vector( [dolfin.Function(V_dgt0) for _ in range(ndim)]) dot, avg, dS, ds = dolfin.dot, dolfin.avg, dolfin.dS, dolfin.ds a = dot(avg(u), avg(v)) * dS + dot(u, v) * ds L = [] for d in range(ndim): L.append(avg(w[d]) * avg(v) * dS + w[d] * v * ds) self.lhs = [dolfin.Form(Li) for Li in L] self.A = dolfin.assemble(a) self.solver = dolfin.PETScKrylovSolver('cg') self.velocity = simulation.data['u_conv_dgt0'] = w_new
def _interp(self, name, expr=None, func=None): """ Interpolate the expression into the given function """ sim = self.simulation # Determine the function space V = self.V quad_degree = None if name == 'c' and self.colour_projection_degree >= 0: # Perform projection for c instead of interpolation to better # capture the discontinuous nature of the colour field if 'Vc' not in sim.data: ocellaris_error( 'Error in wave field %r input' % self.name, 'Cannot specify colour_to_dg_degree when c is ' 'not used in the multiphase_solver.', ) V = sim.data['Vc'] quad_degree = self.colour_projection_degree # Get the expression if expr is None: expr = self._get_expression(name, quad_degree) # Get the function if func is None: if name not in self._functions: self._functions[name] = dolfin.Function(V) func = self._functions[name] if quad_degree is not None: if self.colour_projection_form is None: # Ensure that we can use the DG0 trick of dividing by the mass # matrix diagonal to get the projected value if V.ufl_element().degree() != 0: ocellaris_error( 'Error in wave field %r input' % self.name, 'The colour_to_dg_degree projection is ' 'currently only implemented when c is DG0', ) v = dolfin.TestFunction(V) dx = dolfin.dx(metadata={'quadrature_degree': quad_degree}) d = dolfin.CellVolume(V.mesh()) # mass matrix diagonal form = expr * v / d * dx self.colour_projection_form = dolfin.Form(form) # Perform projection by assembly (DG0 only!) dolfin.assemble(self.colour_projection_form, tensor=func.vector()) else: # Perform standard interpolation func.interpolate(expr)
def get_variable(self, name): if not name == self.var_name: ocellaris_error( 'Scalar field does not define %r' % name, 'This scalar field defines %r' % self.var_name, ) if self.func is not None: return self.func if self.colour_projection_degree >= 0: quad_degree = self.colour_projection_degree degree = None else: quad_degree = None degree = self.polydeg expr = OcellarisCppExpression( self.simulation, self.cpp_code, 'Scalar field %r' % self.name, degree=degree, update=False, quad_degree=quad_degree, ) self.func = dolfin.Function(self.V) if quad_degree is not None: # Ensure that we can use the DG0 trick of dividing by the mass # matrix diagonal to get the projected value if self.V.ufl_element().degree() != 0: ocellaris_error( 'Error in wave field %r input' % self.name, 'The colour_to_dg_degree projection is ' 'currently only implemented when c is DG0', ) v = dolfin.TestFunction(self.V) dx = dolfin.dx(metadata={'quadrature_degree': quad_degree}) d = dolfin.CellVolume(self.V.mesh()) # mass matrix diagonal form = expr * v / d * dx compiled_form = dolfin.Form(form) # Perform projection by assembly (DG0 only!) dolfin.assemble(compiled_form, tensor=self.func.vector()) else: # Perform standard interpolation self.func.interpolate(expr) return self.func
def tune_factors(self, setup=False): if setup: # Incoming wave data u_inlet = self.inflow_field.get_variable('uhoriz') c_inlet = self.inflow_field.get_variable('c') ds_inlet = self.simulation.data['ds'](self.inflow_ds_mark_id) # Outlet data u_outlet = self._get_expression('uhoriz') c_outlet = self._get_expression('c') ds_outlet = self.simulation.data['ds'](self.outflow_ds_mark_id) # Compute unit fluxes self.const_speed_above.assign(dolfin.Constant(1.0)) self.const_speed_below.assign(dolfin.Constant(1.0)) self.uf_above = dolfin.assemble( (1 - c_outlet) * u_outlet * ds_outlet) self.uf_below = dolfin.assemble(c_outlet * u_outlet * ds_outlet) self.form_inlet_above = dolfin.Form( (1 - c_inlet) * u_inlet * ds_inlet) self.form_inlet_below = dolfin.Form(c_inlet * u_inlet * ds_inlet) self.form_inlet_total = dolfin.Form(u_inlet * ds_inlet) self.form_outlet_above = dolfin.Form( (1 - c_outlet) * u_outlet * ds_outlet) self.form_outlet_below = dolfin.Form(c_outlet * u_outlet * ds_outlet) self.form_outlet_total = dolfin.Form(u_outlet * ds_outlet) import time t1 = time.time() # Compute flux above and below at the inlet inlet_flux_above = dolfin.assemble(self.form_inlet_above) inlet_flux_below = dolfin.assemble(self.form_inlet_below) inlet_flux_total = dolfin.assemble(self.form_inlet_total) is_motion = abs(inlet_flux_above) > 0 or abs(inlet_flux_below) > 0 # Estimate the fluxes at the outlet (this gets within 99.99% of total zero flux) self.const_speed_above.assign( dolfin.Constant(inlet_flux_above / self.uf_above)) self.const_speed_below.assign( dolfin.Constant(inlet_flux_below / self.uf_below)) if OPTIMISE_FLUXES and is_motion: # Use root finding to adjust the speed above the free surface # in order to get zero total volume flux in the domain def func_to_minimise(vel_above): "Compute the difference in total flux" # Adjust outflow speed above the FS self.const_speed_above.assign(dolfin.Constant(vel_above)) outlet_flux_total = dolfin.assemble(self.form_outlet_total) return (outlet_flux_total - inlet_flux_total)**2 # Starting values - use the basic unit fluxes x1 = inlet_flux_above / self.uf_above x0 = x1 * 0.99 xN, niter = find_root_secant(x0, x1, func_to_minimise) if False and abs(inlet_flux_below) > 0: outlet_flux_above = dolfin.assemble(self.form_outlet_above) outlet_flux_below = dolfin.assemble(self.form_outlet_below) outlet_flux_total = dolfin.assemble(self.form_outlet_total) print('uf ab & be', self.uf_above, self.uf_below) print( 'flux_above', inlet_flux_above, outlet_flux_above, outlet_flux_above / inlet_flux_above, ) print( 'flux_below', inlet_flux_below, outlet_flux_below, outlet_flux_below / inlet_flux_below, ) print( 'flux_total', inlet_flux_total, outlet_flux_total, outlet_flux_total / inlet_flux_total, ) print(xN - x0) print(niter) print('Took %g seconds' % (time.time() - t1))
def define_simple_equations(self): """ Setup weak forms for SIMPLE form """ sim = self.simulation self.Vuvw = sim.data['uvw_star'].function_space() Vp = sim.data['Vp'] # The trial and test functions in a coupled space (to be split) func_spaces = [self.Vuvw, Vp] e_mixed = dolfin.MixedElement([fs.ufl_element() for fs in func_spaces]) Vcoupled = dolfin.FunctionSpace(sim.data['mesh'], e_mixed) tests = dolfin.TestFunctions(Vcoupled) trials = dolfin.TrialFunctions(Vcoupled) # Split into components v = dolfin.as_vector(tests[0][:]) u = dolfin.as_vector(trials[0][:]) q = tests[-1] p = trials[-1] lm_trial = lm_test = None # Define the full coupled form and split it into subforms depending # on the test and trial functions eq = define_dg_equations( u, v, p, q, lm_trial, lm_test, self.simulation, include_hydrostatic_pressure=self.include_hydrostatic_pressure, incompressibility_flux_type=self.incompressibility_flux_type, use_grad_q_form=self.use_grad_q_form, use_grad_p_form=self.use_grad_p_form, use_stress_divergence_form=self.use_stress_divergence_form, ) mat, vec = split_form_into_matrix(eq, Vcoupled, Vcoupled, check_zeros=True) # Check matrix and vector shapes and that the matrix is a saddle point matrix assert mat.shape == (2, 2) assert vec.shape == (2, ) assert mat[ -1, -1] is None, 'Found p-q coupling, this is not a saddle point system!' # Store the forms self.eqA = mat[0, 0] self.eqB = mat[0, 1] self.eqC = mat[1, 0] self.eqD = vec[0] self.eqE = vec[1] if self.eqE is None: self.eqE = dolfin.TrialFunction(Vp) * dolfin.Constant( 0) * dolfin.dx if self.a_tilde_is_mass: # The mass matrix. Consistent with the implementation in define_dg_equations rho = sim.multi_phase_model.get_density(0) c1 = sim.data['time_coeffs'][0] dt = sim.data['dt'] eqM = rho * c1 / dt * dolfin.dot(u, v) * dolfin.dx matM, _vecM = split_form_into_matrix(eqM, Vcoupled, Vcoupled, check_zeros=True) self.eqM = dolfin.Form(matM[0, 0]) self.M = None
def to_dolfin_cpp_form(a): if isinstance(a, ufl.form.Form): return dolfin.Form(a) return a
def define_weak_forms(self): sim = self.simulation self.Vuvw = sim.data['uvw_star'].function_space() Vp = sim.data['Vp'] # The trial and test functions in a coupled space (to be split) func_spaces = [self.Vuvw, Vp] e_mixed = dolfin.MixedElement([fs.ufl_element() for fs in func_spaces]) Vcoupled = dolfin.FunctionSpace(sim.data['mesh'], e_mixed) tests = dolfin.TestFunctions(Vcoupled) trials = dolfin.TrialFunctions(Vcoupled) # Split into components v = dolfin.as_vector(tests[0][:]) u = dolfin.as_vector(trials[0][:]) q = tests[-1] p = trials[-1] lm_trial = lm_test = None # Define the full coupled form and split it into subforms depending # on the test and trial functions eq = define_dg_equations( u, v, p, q, lm_trial, lm_test, self.simulation, include_hydrostatic_pressure=self.hydrostatic_pressure. every_timestep, incompressibility_flux_type=self.incompressibility_flux_type, use_grad_q_form=self.use_grad_q_form, use_grad_p_form=self.use_grad_p_form, use_stress_divergence_form=self.use_stress_divergence_form, ) sim.log.info(' Splitting coupled form') mat, vec = split_form_into_matrix(eq, Vcoupled, Vcoupled, check_zeros=True) # Check matrix and vector shapes and that the matrix is a saddle point matrix assert mat.shape == (2, 2) assert vec.shape == (2, ) assert mat[ -1, -1] is None, 'Found p-q coupling, this is not a saddle point system!' # Compile and store the forms sim.log.info(' Compiling IPCS-A forms') self.eqA = dolfin.Form(mat[0, 0]) self.eqB = dolfin.Form(mat[0, 1]) self.eqC = dolfin.Form(mat[1, 0]) self.eqD = dolfin.Form(vec[0]) self.eqE = dolfin.Form(vec[1]) if vec[1] is not None else None # The mass matrix. Consistent with the implementation in define_dg_equations if self.splitting_approximation == SPLIT_APPROX_MASS_WITH_RHO: rho_for_M = sim.multi_phase_model.get_density(0) elif self.splitting_approximation == SPLIT_APPROX_MASS_MIN_RHO: min_rho, _max_rho = sim.multi_phase_model.get_density_range() rho_for_M = dolfin.Constant(min_rho) else: rho_for_M = None if rho_for_M is not None: c1 = sim.data['time_coeffs'][0] dt = sim.data['dt'] eqM = rho_for_M * c1 / dt * dolfin.dot(u, v) * dolfin.dx matM, _vecM = split_form_into_matrix(eqM, Vcoupled, Vcoupled, check_zeros=True) self.eqM = dolfin.Form(matM[0, 0]) # The mass matrix without density if self.make_unscaled_M: u2 = dolfin.as_vector(dolfin.TrialFunction(self.Vuvw)[:]) v2 = dolfin.as_vector(dolfin.TestFunction(self.Vuvw)[:]) a = dolfin.dot(u2, v2) * dolfin.dx Mus = assemble_into(a, None) self.M_unscaled_inv = invert_block_diagonal_matrix(self.Vuvw, Mus)
def define_advection_equation(self): """ Setup the advection equation for the colour function This implementation assembles the full LHS and RHS each time they are needed """ sim = self.simulation mesh = sim.data['mesh'] n = dolfin.FacetNormal(mesh) dS, dx = dolfin.dS(mesh), dolfin.dx(mesh) # Trial and test functions Vc = self.Vc c = dolfin.TrialFunction(Vc) d = dolfin.TestFunction(Vc) c1, c2, c3 = self.time_coeffs dt = self.dt u_conv = self.u_conv if not self.colour_is_discontinuous: # Continous Galerkin implementation of the advection equation # FIXME: add stabilization eq = (c1 * c + c2 * self.cp + c3 * self.cpp) / dt * d * dx + div( c * u_conv) * d * dx elif self.velocity_is_trace: # Upstream and downstream normal velocities w_nU = (dot(u_conv, n) + abs(dot(u_conv, n))) / 2 w_nD = (dot(u_conv, n) - abs(dot(u_conv, n))) / 2 if self.beta is not None: # Define the blended flux # The blending factor beta is not DG, so beta('+') == beta('-') b = self.beta('+') flux = (1 - b) * jump(c * w_nU) + b * jump(c * w_nD) else: flux = jump(c * w_nU) # Discontinuous Galerkin implementation of the advection equation eq = (c1 * c + c2 * self.cp + c3 * self.cpp) / dt * d * dx + flux * jump(d) * dS # On each facet either w_nD or w_nU will be 0, the other is multiplied # with the appropriate flux, either the value c going out of the domain # or the Dirichlet value coming into the domain for dbc in self.dirichlet_bcs: eq += w_nD * dbc.func() * d * dbc.ds() eq += w_nU * c * d * dbc.ds() elif self.beta is not None: # Upstream and downstream normal velocities w_nU = (dot(u_conv, n) + abs(dot(u_conv, n))) / 2 w_nD = (dot(u_conv, n) - abs(dot(u_conv, n))) / 2 if self.beta is not None: # Define the blended flux # The blending factor beta is not DG, so beta('+') == beta('-') b = self.beta('+') flux = (1 - b) * jump(c * w_nU) + b * jump(c * w_nD) else: flux = jump(c * w_nU) # Discontinuous Galerkin implementation of the advection equation eq = ((c1 * c + c2 * self.cp + c3 * self.cpp) / dt * d * dx - dot(c * u_conv, grad(d)) * dx + flux * jump(d) * dS) # Enforce Dirichlet BCs weakly for dbc in self.dirichlet_bcs: eq += w_nD * dbc.func() * d * dbc.ds() eq += w_nU * c * d * dbc.ds() else: # Downstream normal velocities w_nD = (dot(u_conv, n) - abs(dot(u_conv, n))) / 2 # Discontinuous Galerkin implementation of the advection equation eq = (c1 * c + c2 * self.cp + c3 * self.cpp) / dt * d * dx # Convection integrated by parts two times to bring back the original # div form (this means we must subtract and add all fluxes) eq += div(c * u_conv) * d * dx # Replace downwind flux with upwind flux on downwind internal facets eq -= jump(w_nD * d) * jump(c) * dS # Replace downwind flux with upwind BC flux on downwind external facets for dbc in self.dirichlet_bcs: # Subtract the "normal" downwind flux eq -= w_nD * c * d * dbc.ds() # Add the boundary value upwind flux eq += w_nD * dbc.func() * d * dbc.ds() # Penalty forcing zones for fz in self.forcing_zones: eq += fz.penalty * fz.beta * (c - fz.target) * d * dx a, L = dolfin.system(eq) self.form_lhs = dolfin.Form(a) self.form_rhs = dolfin.Form(L) self.tensor_lhs = None self.tensor_rhs = None