def NormalCurve(mesh, outside=None, tol=1E-2): '''In plane we can get things by rotating the tangent to get unit normal''' # The idea is to use a given point to figure out wind number and orient # accordingly if outside is None: outside = np.max(mesh.coordinates(), axis=0) + np.ones(2) assert outside.shape == (2, ) n, = wind_number(mesh, np.array([outside])) if abs(n) < tol: # Orient with R = df.Constant(((0, 1), (-1, 0))) else: R = df.Constant(((0, -1), (1, 0))) t = TangentCurve(mesh) V = t.function_space() v = df.TestFunction(V) n = df.Function(V) # DG0 project the rotated tangent df.assemble(df.inner(df.dot(R, t) / df.CellVolume(mesh), v) * df.dx, tensor=n.vector()) return n
def efield_DG0(mesh, phi): Q = df.VectorFunctionSpace(mesh, 'DG', 0) q = df.TestFunction(Q) M = (1.0 / df.CellVolume(mesh)) * df.inner(-df.grad(phi), q) * df.dx E_dg0 = df.assemble(M) return df.Function(Q, E_dg0)
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 CellCentroid(mesh): '''[DG0]^d function that evals on cell to its center of mass''' V = df.VectorFunctionSpace(mesh, 'DG', 0) v = df.TestFunction(V) hK = df.CellVolume(mesh) x = df.SpatialCoordinate(mesh) c = df.Function(V) df.assemble((1 / hK) * df.inner(x, v) * df.dx, tensor=c.vector()) return c
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 compute(self, inputs, outputs): pde_problem = self.options['pde_problem'] # form = self.options['form'] rho = self.options['rho'] expression = self.options['expression'] self._set_values(inputs) von_Mises_max = df.project( expression, self.density_function_space).vector().get_local().max() self.form = (1 / df.CellVolume(self.mesh) * df.exp(rho * (expression - von_Mises_max))) * df.dx outputs['von_mises_max'] = 1 / rho * df.ln(df.assemble( self.form)) + von_Mises_max
def update_mesh_data(self, connectivity_changed=True): """ Some precomputed values must be calculated before the timestepping and updated every time the mesh changes """ if connectivity_changed: init_connectivity(self) precompute_cell_data(self) precompute_facet_data(self) # Work around missing consensus on what CellDiameter is for bendy cells mesh = self.data['mesh'] if mesh.ufl_coordinate_element().degree() > 1: h = dolfin.CellVolume(mesh)**(1 / mesh.topology().dim()) else: h = dolfin.CellDiameter(mesh) self.data['h'] = h
def compute_derivative(self, inputs): pde_problem = self.options['pde_problem'] expression = self.options['expression'] self._set_values(inputs) von_Mises_max = df.project( expression, self.density_function_space).vector().get_local().max() rho = self.options['rho'] self.form = (1 / df.CellVolume(self.mesh) * df.exp(rho * (expression - von_Mises_max))) * df.dx print('von Mises max:--------', von_Mises_max) derivative_form_s = df.derivative( self.form, pde_problem.states_dict['mixed_states']['function']) derivative_numpy_s = 1 / (rho * df.assemble( self.form)) * df.assemble(derivative_form_s).get_local() derivative_form_d = df.derivative( self.form, pde_problem.inputs_dict['density']['function']) derivative_form_d = 1 / (rho * df.assemble( self.form)) * df.assemble(derivative_form_d).get_local() return derivative_numpy_s, derivative_form_d
def __init__(self, mesh, arithmetic_mean=False): assert df.parameters['linear_algebra_backend'] == 'PETSc' self.arithmetic_mean = arithmetic_mean tdim = mesh.topology().dim() gdim = mesh.geometry().dim() self.cv = df.CellVolume(mesh) self.W = df.VectorFunctionSpace(mesh, 'CG', 1) Q = df.VectorFunctionSpace(mesh, 'DG', 0) p, self.q = df.TrialFunction(Q), df.TestFunction(Q) v = df.TestFunction(self.W) ones = df.assemble((1. / self.cv) * df.inner(df.Constant( (1, ) * gdim), self.q) * df.dx) dX = df.dx( metadata={ 'form_compiler_parameters': { 'quadrature_degree': 1, 'quadrature_scheme': 'vertex' } }) if self.arithmetic_mean: A = df.assemble( (1. / self.cv) * df.Constant(tdim + 1) * df.inner(p, v) * dX) else: A = df.assemble(df.Constant(tdim + 1) * df.inner(p, v) * dX) Av = df.Function(self.W).vector() A.mult(ones, Av) Av = df.as_backend_type(Av).vec() Av.reciprocal() mat = df.as_backend_type(A).mat() mat.diagonalScale(L=Av) self.A = A
def define_pressure_equation(self): """ Setup the pressure Poisson equation This implementation assembles the full LHS and RHS each time they are needed """ sim = self.simulation Vp = sim.data['Vp'] p_star = sim.data['p'] u_star = sim.data['u'] # Trial and test functions p = dolfin.TrialFunction(Vp) q = dolfin.TestFunction(Vp) c1 = sim.data['time_coeffs'][0] dt = sim.data['dt'] mesh = sim.data['mesh'] n = dolfin.FacetNormal(mesh) # Fluid properties mpm = sim.multi_phase_model mu = mpm.get_laminar_dynamic_viscosity(0) rho = sim.data['rho'] # Lagrange multiplicator to remove the pressure null space # ∫ p dx = 0 assert not self.use_lagrange_multiplicator, 'NOT IMPLEMENTED YET' # Weak form of the Poisson eq. with discontinuous elements # -∇⋅∇p = - γ_1/Δt ρ ∇⋅u^* K = 1.0 / rho a = K * dot(grad(p), grad(q)) * dx L = K * dot(grad(p_star), grad(q)) * dx # RHS, ∇⋅u^* if self.incompressibility_flux_type == 'central': u_flux = avg(u_star) elif self.incompressibility_flux_type == 'upwind': switch = dolfin.conditional( dolfin.gt(abs(dot(u_star, n))('+'), 0.0), 1.0, 0.0 ) u_flux = switch * u_star('+') + (1 - switch) * u_star('-') L += c1 / dt * dot(u_star, grad(q)) * dx L -= c1 / dt * dot(u_flux, n('+')) * jump(q) * dS # Symmetric Interior Penalty method for -∇⋅∇p a -= dot(n('+'), avg(K * grad(p))) * jump(q) * dS a -= dot(n('+'), avg(K * grad(q))) * jump(p) * dS # Symmetric Interior Penalty method for -∇⋅∇p^* L -= dot(n('+'), avg(K * grad(p_star))) * jump(q) * dS L -= dot(n('+'), avg(K * grad(q))) * jump(p_star) * dS # Weak continuity penalty_dS, penalty_ds = self.calculate_penalties() # Symmetric Interior Penalty coercivity term a += penalty_dS * jump(p) * jump(q) * dS # L += penalty_dS*jump(p_star)*jump(q)*dS # Collect Dirichlet and outlet boundary values dirichlet_vals_and_ds = [] for dbc in sim.data['dirichlet_bcs'].get('p', []): dirichlet_vals_and_ds.append((dbc.func(), dbc.ds())) for obc in sim.data['outlet_bcs']: p_ = mu * dot(dot(grad(u_star), n), n) dirichlet_vals_and_ds.append((p_, obc.ds())) # Apply Dirichlet boundary conditions for p_bc, dds in dirichlet_vals_and_ds: # SIPG for -∇⋅∇p a -= dot(n, K * grad(p)) * q * dds a -= dot(n, K * grad(q)) * p * dds L -= dot(n, K * grad(q)) * p_bc * dds # SIPG for -∇⋅∇p^* L -= dot(n, K * grad(p_star)) * q * dds L -= dot(n, K * grad(q)) * p_star * dds # Weak Dirichlet a += penalty_ds * p * q * dds L += penalty_ds * p_bc * q * dds # Weak Dirichlet for p^* # L += penalty_ds*p_star*q*dds # L -= penalty_ds*p_bc*q*dds # Neumann boundary conditions neumann_bcs = sim.data['neumann_bcs'].get('p', []) for nbc in neumann_bcs: # Neumann boundary conditions on p and p_star cancel # L += (nbc.func() - dot(n, grad(p_star)))*q*nbc.ds() pass # Use boundary conditions for the velocity for the # term from integration by parts of div(u_star) for d in range(sim.ndim): dirichlet_bcs = sim.data['dirichlet_bcs'].get('u%d' % d, []) neumann_bcs = sim.data['neumann_bcs'].get('u%d' % d, []) for dbc in dirichlet_bcs: u_bc = dbc.func() L -= c1 / dt * u_bc * n[d] * q * dbc.ds() for nbc in neumann_bcs: L -= c1 / dt * u_star[d] * n[d] * q * nbc.ds() # ALE mesh velocities if sim.mesh_morpher.active: cvol_new = dolfin.CellVolume(mesh) cvol_old = sim.data['cvolp'] # Divergence of u should balance expansion/contraction of the cell K # ∇⋅u = -∂K/∂t (See below for definition of the ∇⋅u term) L -= (cvol_new - cvol_old) / dt * q * dx self.form_lhs = a self.form_rhs = L
# sigm = lambda_*df.div(displacements_function)* I + 2*mu*w_ij # s = sigm - (1./3)*df.tr(sigm)*I # von_Mises = df.sqrt(3./2*df.inner(s, s)) # von_Mises_form = (1/df.CellVolume(mesh)) * von_Mises * df.TestFunction(density_function_space) * df.dx # T = df.TensorFunctionSpace(mesh, "CG", 1) # T.vector.set_local() T_00 = df.Constant(20) w_ij = 0.5 * (df.grad(displacements_function) + df.grad( displacements_function).T) - C * ALPHA * I * (temperature_function - T_00) sigm = lambda_ * df.div(displacements_function) * I + 2 * mu * w_ij s = sigm - (1. / 3) * df.tr(sigm) * I scalar_ = 5e8 # scalar_ = 5e5 von_Mises = df.sqrt(3. / 2 * df.inner(s / scalar_, s / scalar_)) von_Mises_form = (1 / df.CellVolume(mesh)) * von_Mises * df.TestFunction( density_function_space) * df.dx # von_Mises_form = von_Mises * df.TestFunction(density_function_space) /volume * df.dx pde_problem.add_field_output('von_Mises', von_Mises_form, 'mixed_states', 'density') x ''' 4. 3. Add bcs ''' bc_displacements = df.DirichletBC( mixed_fs.sub(0).sub(0), df.Constant((0.0)), MidVBoundary()) bc_displacements_1 = df.DirichletBC( mixed_fs.sub(0).sub(1), df.Constant((0.0)), MidHBoundary())
def define_dg_equations( u, v, p, q, lm_trial, lm_test, simulation, include_hydrostatic_pressure, incompressibility_flux_type, use_grad_q_form, use_grad_p_form, use_stress_divergence_form, velocity_continuity_factor_D12=0, pressure_continuity_factor=0, ): """ Define the coupled equations. Also used by the SIMPLE and IPCS-A solvers Weak form of the Navier-Stokes eq. with discontinuous elements :type simulation: ocellaris.Simulation """ sim = simulation show = sim.log.info show(' Creating DG weak form with BCs') show(' include_hydrostatic_pressure = %r' % include_hydrostatic_pressure) show(' incompressibility_flux_type = %s' % incompressibility_flux_type) show(' use_grad_q_form = %r' % use_grad_q_form) show(' use_grad_p_form = %r' % use_grad_p_form) show(' use_stress_divergence_form = %r' % use_stress_divergence_form) show(' velocity_continuity_factor_D12 = %r' % velocity_continuity_factor_D12) show(' pressure_continuity_factor = %r' % pressure_continuity_factor) mpm = sim.multi_phase_model mesh = sim.data['mesh'] u_conv = sim.data['u_conv'] dx = dolfin.dx(domain=mesh) dS = dolfin.dS(domain=mesh) c1, c2, c3 = sim.data['time_coeffs'] dt = sim.data['dt'] g = sim.data['g'] n = dolfin.FacetNormal(mesh) x = dolfin.SpatialCoordinate(mesh) # Fluid properties rho = mpm.get_density(0) nu = mpm.get_laminar_kinematic_viscosity(0) mu = mpm.get_laminar_dynamic_viscosity(0) # Hydrostatic pressure correction if include_hydrostatic_pressure: p += sim.data['p_hydrostatic'] # Start building the coupled equations eq = 0 # ALE mesh velocities if sim.mesh_morpher.active: u_mesh = sim.data['u_mesh'] # Either modify the convective velocity or just include the mesh # velocity on cell integral form. Only activate one of the lines below # PS: currently the UFL form splitter (used in e.g. IPCS-A) has a # problem with line number 2, but the Coupled solver handles # both options with approximately the same resulting convergence # errors on the Taylor-Green test case (TODO: fix form splitter) u_conv -= u_mesh # eq -= dot(div(rho * dolfin.outer(u, u_mesh)), v) * dx # Divergence of u should balance expansion/contraction of the cell K # ∇⋅u = -∂x/∂t (See below for definition of the ∇⋅u term) # THIS IS SOMEWHAT EXPERIMENTAL cvol_new = dolfin.CellVolume(mesh) cvol_old = sim.data['cvolp'] eq += (cvol_new - cvol_old) / dt * q * dx # Elliptic penalties penalty_dS, penalty_ds, D11, D12 = navier_stokes_stabilization_penalties( sim, nu, velocity_continuity_factor_D12, pressure_continuity_factor) yh = 1 / penalty_ds # Upwind and downwind velocities w_nU = (dot(u_conv, n) + abs(dot(u_conv, n))) / 2.0 w_nD = (dot(u_conv, n) - abs(dot(u_conv, n))) / 2.0 # Lagrange multiplicator to remove the pressure null space # ∫ p dx = 0 if lm_trial is not None: eq = (p * lm_test + q * lm_trial) * dx # Momentum equations for d in range(sim.ndim): up = sim.data['up%d' % d] upp = sim.data['upp%d' % d] # Divergence free criterion # ∇⋅u = 0 if incompressibility_flux_type == 'central': u_hat_p = avg(u[d]) elif incompressibility_flux_type == 'upwind': assert use_grad_q_form, 'Upwind only implemented for grad_q_form' switch = dolfin.conditional(dolfin.gt(w_nU('+'), 0.0), 1.0, 0.0) u_hat_p = switch * u[d]('+') + (1 - switch) * u[d]('-') if use_grad_q_form: eq -= u[d] * q.dx(d) * dx eq += (u_hat_p + D12[d] * jump(u, n)) * jump(q) * n[d]('+') * dS else: eq += q * u[d].dx(d) * dx eq -= (avg(q) - dot(D12, jump(q, n))) * jump(u[d]) * n[d]('+') * dS # Time derivative # ∂(ρu)/∂t eq += rho * (c1 * u[d] + c2 * up + c3 * upp) / dt * v[d] * dx # Convection: # -w⋅∇(ρu) flux_nU = u[d] * w_nU flux = jump(flux_nU) eq -= u[d] * dot(grad(rho * v[d]), u_conv) * dx eq += flux * jump(rho * v[d]) * dS # Stabilizing term when w is not divergence free eq += 1 / 2 * div(u_conv) * u[d] * v[d] * dx # Diffusion: # -∇⋅μ∇u eq += mu * dot(grad(u[d]), grad(v[d])) * dx # Symmetric Interior Penalty method for -∇⋅μ∇u eq -= avg(mu) * dot(n('+'), avg(grad(u[d]))) * jump(v[d]) * dS eq -= avg(mu) * dot(n('+'), avg(grad(v[d]))) * jump(u[d]) * dS # Symmetric Interior Penalty coercivity term eq += penalty_dS * jump(u[d]) * jump(v[d]) * dS # -∇⋅μ(∇u)^T if use_stress_divergence_form: eq += mu * dot(u.dx(d), grad(v[d])) * dx eq -= avg(mu) * dot(n('+'), avg(u.dx(d))) * jump(v[d]) * dS eq -= avg(mu) * dot(n('+'), avg(v.dx(d))) * jump(u[d]) * dS # Pressure # ∇p if use_grad_p_form: eq += v[d] * p.dx(d) * dx eq -= (avg(v[d]) + D12[d] * jump(v, n)) * jump(p) * n[d]('+') * dS else: eq -= p * v[d].dx(d) * dx eq += (avg(p) - dot(D12, jump(p, n))) * jump(v[d]) * n[d]('+') * dS # Pressure continuity stabilization. Needed for equal order discretization if D11 is not None: eq += D11 * dot(jump(p, n), jump(q, n)) * dS # Body force (gravity) # ρ g eq -= rho * g[d] * v[d] * dx # Other sources for f in sim.data['momentum_sources']: eq -= f[d] * v[d] * dx # Penalty forcing zones for fz in sim.data['forcing_zones'].get('u', []): eq += fz.penalty * fz.beta * (u[d] - fz.target[d]) * v[d] * dx # Boundary conditions that do not couple the velocity components # The BCs are imposed for each velocity component u[d] separately eq += add_dirichlet_bcs(sim, d, u, p, v, q, rho, mu, n, w_nU, w_nD, penalty_ds, use_grad_q_form, use_grad_p_form) eq += add_neumann_bcs(sim, d, u, p, v, q, rho, mu, n, w_nU, w_nD, penalty_ds, use_grad_q_form, use_grad_p_form) eq += add_robin_bcs(sim, d, u, p, v, q, rho, mu, n, w_nU, w_nD, yh, use_grad_q_form, use_grad_p_form) eq += add_outlet_bcs(sim, d, u, p, v, q, rho, mu, n, w_nU, w_nD, g, x, use_grad_q_form, use_grad_p_form) # Boundary conditions that couple the velocity components # Decomposing the velocity into wall normal and parallel parts eq += add_slip_bcs(sim, u, p, v, q, rho, mu, n, w_nU, w_nD, penalty_ds, use_grad_q_form, use_grad_p_form) return eq
def compute_cell_values(f): mesh = f.function_space().mesh() V = dolfin.FunctionSpace(mesh, "DG", 0) v = dolfin.TestFunction(V) h = dolfin.CellVolume(mesh) return dolfin.assemble(f * v / h * dolfin.dx).array()
def __init__(self, mesh, orientation): if orientation is None: # We assume convex domain and take center as ... orientation = mesh.coordinates().mean(axis=0) if isinstance(orientation, df.Mesh): # We set surface normal as outer with respect to orientation mesh assert orientation.id() in mesh.parent_entity_map n = df.FacetNormal(orientation) hA = df.FacetArea(orientation) # Project normal, we have a function on mesh DLT = df.VectorFunctionSpace(orientation, 'Discontinuous Lagrange Trace', 0) n_ = df.Function(DLT) df.assemble((1 / hA) * df.inner(n, df.TestFunction(DLT)) * df.ds, tensor=n_.vector()) # Now we get it to manifold dx_ = df.Measure('dx', domain=mesh) V = df.VectorFunctionSpace(mesh, 'DG', 0) df.Function.__init__(self, V) hK = df.CellVolume(mesh) n_vec = xii.ii_convert( xii.ii_assemble( (1 / hK) * df.inner(xii.Trace(n_, mesh), df.TestFunction(V)) * dx_)) self.vector()[:] = n_vec return None # Manifold assumption assert 1 <= mesh.topology().dim() < mesh.geometry().dim() gdim = mesh.geometry().dim() # Orientation from inside point if isinstance(orientation, (list, np.ndarray, tuple)): assert len(orientation) == gdim kwargs = {'x0%d' % i: val for i, val in enumerate(orientation)} orientation = ['x[%d] - x0%d' % (i, i) for i in range(gdim)] orientation = df.Expression(orientation, degree=1, **kwargs) assert orientation.ufl_shape == (gdim, ) V = df.VectorFunctionSpace(mesh, 'DG', 0, gdim) df.Function.__init__(self, V) n_values = self.vector().get_local() X = mesh.coordinates() dd = X[np.argmin(X[:, 0])] - X[np.argmax(X[:, 0])] values = [] R = np.array([[0, -1], [1, 0]]) for cell in df.cells(mesh): n = cell.cell_normal().array()[:gdim] x = cell.midpoint().array()[:gdim] # Disagree? if np.inner(orientation(x), n) < 0: n *= -1. values.append(n / np.linalg.norm(n)) values = np.array(values) for sub in range(gdim): dofs = V.sub(sub).dofmap().dofs() n_values[dofs] = values[:, sub] self.vector().set_local(n_values) self.vector().apply('insert')
def initialize_variables(self): r""" Initialize the model variables to default values. The variables defined here are: Various things : * ``self.element`` -- the finite-element * ``self.top_dim`` -- the topological dimension * ``self.dofmap`` -- :class:`~dolfin.cpp.fem.DofMap` for converting between vertex to nodal indicies * ``self.h`` -- Cell diameter formed by calling :func:`~dolfin.functions.specialfunctions.CellDiameter` with ``self.mesh`` Grid velocity vector :math:`\mathbf{u}_i = [u\ v\ w]^{\intercal}`: * ``self.U_mag`` -- velocity vector magnitude * ``self.U3`` -- velocity vector * ``self.u`` -- :math:`x`-component of velocity vector * ``self.v`` -- :math:`y`-component of velocity vector * ``self.w`` -- :math:`z`-component of velocity vector Grid acceleration vector :math:`\mathbf{a}_i = [a_x\ a_y\ a_z]^{\intercal}`: * ``self.a_mag`` -- acceleration vector magnitude * ``self.a3`` -- acceleration vector * ``self.a_x`` -- :math:`x`-component of acceleration vector * ``self.a_y`` -- :math:`y`-component of acceleration vector * ``self.a_z`` -- :math:`z`-component of acceleration vector Grid internal force vector :math:`\mathbf{f}_i^{\mathrm{int}} = [f_x^{\mathrm{int}}\ f_y^{\mathrm{int}}\ f_z^{\mathrm{int}}]^{\intercal}`: * ``self.f_int_mag`` -- internal force vector magnitude * ``self.f_int`` -- internal force vector * ``self.f_int_x`` -- :math:`x`-component of internal force vector * ``self.f_int_y`` -- :math:`y`-component of internal force vector * ``self.f_int_z`` -- :math:`z`-component of internal force vector Grid mass :math:`m_i`: * ``self.m`` -- mass :math:`m_i` * ``self.m0`` -- inital mass :math:`m_i^0` """ s = "::: initializing grid variables :::" print_text(s, cls=self.this) # the finite-element used : self.element = self.Q.element() # topological dimension : self.top_dim = self.element.geometric_dimension() # map from verticies to nodes : self.dofmap = self.Q.dofmap() # for finding vertices sitting on boundary : self.d2v = dl.dof_to_vertex_map(self.Q) # list of arrays of vertices and bcs set by self.set_boundary_conditions() : self.bc_vrt = None self.bc_val = None # cell diameter : self.h = dl.project(dl.CellDiameter(self.mesh), self.Q) # cell volume : self.Ve = dl.project(dl.CellVolume(self.mesh), self.Q) # grid velocity : self.U_mag = dl.Function(self.Q, name='U_mag') self.U3 = dl.Function(self.Q3, name='U3') u, v, w = self.U3.split() u.rename('u', '') v.rename('v', '') w.rename('w', '') self.u = u self.v = v self.w = w # grid acceleration : self.a_mag = dl.Function(self.Q, name='a_mag') self.a3 = dl.Function(self.Q3, name='a3') a_x, a_y, a_z = self.a3.split() a_x.rename('a_x', '') a_y.rename('a_y', '') a_z.rename('a_z', '') self.a_x = a_x self.a_y = a_y self.a_z = a_z # grid internal force vector : self.f_int_mag = dl.Function(self.Q, name='f_int_mag') self.f_int = dl.Function(self.Q3, name='f_int') f_int_x, f_int_y, f_int_z = self.f_int.split() f_int_x.rename('f_int_x', '') f_int_y.rename('f_int_y', '') f_int_z.rename('f_int_z', '') self.f_int_x = f_int_x self.f_int_y = f_int_y self.f_int_z = f_int_z # grid mass : self.m = dl.Function(self.Q, name='m') self.m0 = dl.Function(self.Q, name='m0') # function assigners speed assigning up : self.assu = dl.FunctionAssigner(self.u.function_space(), self.Q) self.assv = dl.FunctionAssigner(self.v.function_space(), self.Q) self.assw = dl.FunctionAssigner(self.w.function_space(), self.Q) self.assa_x = dl.FunctionAssigner(self.a_x.function_space(), self.Q) self.assa_y = dl.FunctionAssigner(self.a_y.function_space(), self.Q) self.assa_z = dl.FunctionAssigner(self.a_z.function_space(), self.Q) self.assf_int_x = dl.FunctionAssigner(self.f_int_x.function_space(), self.Q) self.assf_int_y = dl.FunctionAssigner(self.f_int_y.function_space(), self.Q) self.assf_int_z = dl.FunctionAssigner(self.f_int_z.function_space(), self.Q) self.assm = dl.FunctionAssigner(self.m.function_space(), self.Q) # save the number of degrees of freedom : self.dofs = self.m.vector().size()
import dolfin as df # from punc import load_mesh fname = "/home/diako/Documents/cpp/punc/mesh/2D/circle_in_square_res1" mesh = df.Mesh(fname + ".xml") tdim = mesh.topology().dim() gdim = mesh.geometry().dim() cv = df.CellVolume(mesh) V = df.FunctionSpace(mesh, 'CG', 1) Q = df.FunctionSpace(mesh, 'DG', 0) p, q = df.TrialFunction(Q), df.TestFunction(Q) v = df.TestFunction(V) ones = df.assemble((1. / cv) * df.inner(df.Constant(1), q) * df.dx) dX = df.dx( metadata={ 'form_compiler_parameters': { 'quadrature_degree': 1, 'quadrature_scheme': 'vertex' } }) A = df.assemble(df.Constant(tdim + 1) * df.inner(p, v) * dX) Av = df.Function(V).vector() A.mult(ones, Av) print(Av.size())
def define_coupled_equation(self): """ Setup the coupled Navier-Stokes equation This implementation assembles the full LHS and RHS each time they are needed """ sim = self.simulation mpm = sim.multi_phase_model mesh = sim.data['mesh'] Vcoupled = sim.data['Vcoupled'] u_conv = sim.data['u_conv'] # Unpack the coupled trial and test functions uc = dolfin.TrialFunction(Vcoupled) vc = dolfin.TestFunction(Vcoupled) ulist = [] vlist = [] ndim = self.simulation.ndim for d in range(ndim): ulist.append(uc[d]) vlist.append(vc[d]) u = dolfin.as_vector(ulist) v = dolfin.as_vector(vlist) p = uc[ndim] q = vc[ndim] c1, c2, c3 = sim.data['time_coeffs'] dt = sim.data['dt'] g = sim.data['g'] n = dolfin.FacetNormal(mesh) # Fluid properties rho = mpm.get_density(0) mu = mpm.get_laminar_dynamic_viscosity(0) # Hydrostatic pressure correction if self.include_hydrostatic_pressure: p += sim.data['p_hydrostatic'] # Start building the coupled equations eq = 0 # ALE mesh velocities if sim.mesh_morpher.active: u_mesh = sim.data['u_mesh'] # Either modify the convective velocity or just include the mesh # velocity on cell integral form. Only activate one of these lines # u_conv -= u_mesh eq -= dot(div(rho * dolfin.outer(u, u_mesh)), v) * dx # Divergence of u should balance expansion/contraction of the cell K # ∇⋅u = -∂x/∂t (See below for definition of the ∇⋅u term) cvol_new = dolfin.CellVolume(mesh) cvol_old = sim.data['cvolp'] eq += (cvol_new - cvol_old) / dt * q * dx # Lagrange multiplicator to remove the pressure null space # ∫ p dx = 0 if self.use_lagrange_multiplicator: lm_trial = uc[ndim + 1] lm_test = vc[ndim + 1] eq = (p * lm_test + q * lm_trial) * dx # Momentum equations for d in range(sim.ndim): up = sim.data['up%d' % d] upp = sim.data['upp%d' % d] # Divergence free criterion # ∇⋅u = 0 eq += u[d].dx(d) * q * dx # Time derivative # ∂u/∂t eq += rho * (c1 * u[d] + c2 * up + c3 * upp) / dt * v[d] * dx # Convection # ∇⋅(ρ u ⊗ u_conv) eq += rho * dot(u_conv, grad(u[d])) * v[d] * dx # Diffusion # -∇⋅μ(∇u) eq += mu * dot(grad(u[d]), grad(v[d])) * dx # -∇⋅μ(∇u)^T if self.use_stress_divergence_form: eq += mu * dot(u.dx(d), grad(v[d])) * dx # Pressure # ∇p eq -= v[d].dx(d) * p * dx # Body force (gravity) # ρ g eq -= rho * g[d] * v[d] * dx # Other sources for f in sim.data['momentum_sources']: eq -= f[d] * v[d] * dx # Neumann boundary conditions neumann_bcs_pressure = sim.data['neumann_bcs'].get('p', []) for nbc in neumann_bcs_pressure: eq += p * v[d] * n[d] * nbc.ds() # Outlet boundary for obc in sim.data['outlet_bcs']: # Diffusion mu_dudn = p * n[d] eq -= mu_dudn * v[d] * obc.ds() # Pressure p_ = mu * dot(dot(grad(u), n), n) eq += p_ * v[d] * n[d] * obc.ds() a, L = dolfin.system(eq) self.form_lhs = a self.form_rhs = L self.tensor_lhs = None self.tensor_rhs = None