def test_taylor_hood_cube(): pytest.xfail("Problem with Mixed Function Spaces") meshc = UnitCubeMesh(MPI.comm_world, 2, 2, 2) meshf = UnitCubeMesh(MPI.comm_world, 3, 4, 5) Ve = VectorElement("CG", meshc.ufl_cell(), 2) Qe = FiniteElement("CG", meshc.ufl_cell(), 1) Ze = MixedElement([Ve, Qe]) Zc = FunctionSpace(meshc, Ze) Zf = FunctionSpace(meshf, Ze) z = Expression( ("x[0]*x[1]", "x[1]*x[2]", "x[2]*x[0]", "x[0] + 3*x[1] + x[2]"), degree=2) zc = interpolate(z, Zc) zf = interpolate(z, Zf) mat = PETScDMCollection.create_transfer_matrix(Zc, Zf) Zuc = Function(Zf) mat.mult(zc.vector(), Zuc.vector()) Zuc.vector().update_ghost_values() diff = Function(Zf) diff.assign(Zuc - zf) assert diff.vector().norm("l2") < 1.0e-12
def assemble_solution(self, t): # returns """ :param t: time :return: Womersley flow (analytic solution) at time t analytic solution at any time is a steady parabolic flow + linear combination of 8 modes modes were precomputed as 8 functions on given mesh and stored in hdf5 file """ if self.tc is not None: self.tc.start('assembleSol') sol = Function(self.solutionSpace) # analytic solution has zero x and y components dofs2 = self.solutionSpace.sub(2).dofmap().dofs( ) # gives field of indices corresponding to z axis sol.assign(Constant(("0.0", "0.0", "0.0"))) # QQ not needed sol.vector()[dofs2] += self.factor * self.bessel_parabolic.vector( ).array() # parabolic part of sol for idx in range(8): # add modes of Womersley sol sol.vector()[dofs2] += self.factor * cos( self.coefs_exp[idx] * pi * t) * self.bessel_real[idx].vector().array() sol.vector()[dofs2] += self.factor * -sin( self.coefs_exp[idx] * pi * t) * self.bessel_complex[idx].vector().array() if self.tc is not None: self.tc.end('assembleSol') return sol
def test_failsafe_sweep(): interpolate_expression = Expression('x[0]', degree=1) mesh = UnitSquareMesh(5, 5) V = FunctionSpace(mesh, "DG", 1) v = Function(V) v.assign(interpolate_expression) np_min, np_max = 1, 2 np_failsafe = 4 # Initialize particles x = RandomRectangle(Point(0.0, 0.0), Point(1., 1.)).generate([100, 100]) s = assign_particle_values(x, interpolate_expression) # Broadcast to other procs x = comm.bcast(x, root=0) s = comm.bcast(s, root=0) property_idx = 1 p = particles(x, [s], mesh) AD = AddDelete(p, np_min, np_max, [v]) AD.do_sweep_failsafe(np_failsafe) # Must recover linear lstsq_rho = l2projection(p, V, property_idx) lstsq_rho.project(v.cpp_object()) error = sqrt( assemble( (v - interpolate_expression) * (v - interpolate_expression) * dx)) assert len(p.positions() == mesh.num_cells() * np_failsafe) assert error < 1e-12
def test_closed_boundary(advection_scheme): # FIXME: rk3 scheme does not bounces off the wall properly xmin, xmax = 0., 1. ymin, ymax = 0., 1. mesh = RectangleMesh(Point(xmin, ymin), Point(xmax, ymax), 10, 10) # Particle x = np.array([[0.975, 0.475]]) # Given velocity field: vexpr = Constant((1., 0.)) # Given time do_step: dt = 0.05 # Then bounced position is x_bounced = np.array([[0.975, 0.475]]) p = particles(x, [x, x], mesh) V = VectorFunctionSpace(mesh, "CG", 1) v = Function(V) v.assign(vexpr) # Different boundary parts bound_left = UnitSquareLeft() bound_right = UnitSquareRight() bound_top = UnitSquareTop() bound_bottom = UnitSquareBottom() # Mark all facets facet_marker = MeshFunction('size_t', mesh, mesh.topology().dim() - 1) facet_marker.set_all(0) # Mark as closed bound_right.mark(facet_marker, 1) # Mark other boundaries as open bound_left.mark(facet_marker, 2) bound_top.mark(facet_marker, 2) bound_bottom.mark(facet_marker, 2) if advection_scheme == 'euler': ap = advect_particles(p, V, v, facet_marker) elif advection_scheme == 'rk2': ap = advect_rk2(p, V, v, facet_marker) elif advection_scheme == 'rk3': ap = advect_rk3(p, V, v, facet_marker) else: assert False # Do one timestep, particle must bounce from wall of ap.do_step(dt) xpE = p.positions() # Check if particle correctly bounced off from closed wall xpE_root = comm.gather(xpE, root=0) if comm.rank == 0: xpE_root = np.float64(np.vstack(xpE_root)) error = np.linalg.norm(x_bounced - xpE_root) assert(error < 1e-10)
def test_taylor_hood_cube(): pytest.xfail("Problem with Mixed Function Spaces") meshc = UnitCubeMesh(MPI.comm_world, 2, 2, 2) meshf = UnitCubeMesh(MPI.comm_world, 3, 4, 5) Ve = VectorElement("CG", meshc.ufl_cell(), 2) Qe = FiniteElement("CG", meshc.ufl_cell(), 1) Ze = MixedElement([Ve, Qe]) Zc = FunctionSpace(meshc, Ze) Zf = FunctionSpace(meshf, Ze) def z(values, x): values[:, 0] = x[:, 0] * x[:, 1] values[:, 1] = x[:, 1] * x[:, 2] values[:, 2] = x[:, 2] * x[:, 0] values[:, 3] = x[:, 0] + 3.0 * x[:, 1] + x[:, 2] zc = interpolate(z, Zc) zf = interpolate(z, Zf) mat = PETScDMCollection.create_transfer_matrix(Zc, Zf) Zuc = Function(Zf) mat.mult(zc.vector, Zuc.vector) Zuc.vector.update_ghost_values() diff = Function(Zf) diff.assign(Zuc - zf) assert diff.vector.norm("l2") < 1.0e-12
def mk_scheme(N, Vname, Vorder, cpp_expr, expr_args, convection_inp, dim=2, comm=None): if comm is None: comm = MPI.comm_world parameters['ghost_mode'] = 'shared_vertex' if dim == 2: mesh = UnitSquareMesh(comm, N, N) else: mesh = UnitCubeMesh(comm, N, N, N) V = FunctionSpace(mesh, Vname, Vorder) C = Function(V) e = Expression(cpp_expr, element=V.ufl_element(), **expr_args) C.interpolate(e) D = Function(V) D.assign(C) sim = Simulation() sim.set_mesh(mesh) sim.data['constrained_domain'] = None sim.data['C'] = C for key, value in convection_inp.items(): sim.input.set_value('convection/C/%s' % key, value) scheme_name = convection_inp['convection_scheme'] return get_convection_scheme(scheme_name)(sim, 'C')
def __init__(self, V): u = Function(V) u.assign(Constant('1.0')) self.one = u.vector() self.Mdiag = self.one.copy() self.Mdiag2 = self.one.copy() self.invMdiag = self.one.copy()
def test_advect_periodic(advection_scheme): # FIXME: this unit test is sensitive to the ordering of the particle # array, i.e. xp0_root and xpE_root may contain exactly the same entries # but only in a different order. This will return an error right now xmin, xmax = 0., 1. ymin, ymax = 0., 1. pres = 3 mesh = RectangleMesh(Point(xmin, ymin), Point(xmax, ymax), 10, 10) lims = np.array([[xmin, xmin, ymin, ymax], [xmax, xmax, ymin, ymax], [xmin, xmax, ymin, ymin], [xmin, xmax, ymax, ymax]]) vexpr = Constant((1., 1.)) V = VectorFunctionSpace(mesh, "CG", 1) x = RandomRectangle(Point(0.05, 0.05), Point(0.15, 0.15)).generate([pres, pres]) x = comm.bcast(x, root=0) dt = 0.05 v = Function(V) v.assign(vexpr) p = particles(x, [x*0, x**2], mesh) if advection_scheme == 'euler': ap = advect_particles(p, V, v, 'periodic', lims.flatten()) elif advection_scheme == 'rk2': ap = advect_rk2(p, V, v, 'periodic', lims.flatten()) elif advection_scheme == 'rk3': ap = advect_rk3(p, V, v, 'periodic', lims.flatten()) else: assert False xp0 = p.positions() t = 0. while t < 1.-1e-12: ap.do_step(dt) t += dt xpE = p.positions() # Check if position correct xp0_root = comm.gather(xp0, root=0) xpE_root = comm.gather(xpE, root=0) num_particles = p.number_of_particles() if comm.Get_rank() == 0: xp0_root = np.float32(np.vstack(xp0_root)) xpE_root = np.float32(np.vstack(xpE_root)) # Sort on x positions xp0_root = xp0_root[xp0_root[:, 0].argsort(), :] xpE_root = xpE_root[xpE_root[:, 0].argsort(), :] error = np.linalg.norm(xp0_root - xpE_root) assert error < 1e-10 assert num_particles - pres**2 == 0
def test_advect_periodic(advection_scheme): xmin, ymin, zmin = 0., 0., 0. xmax, ymax, zmax = 1., 1., 1. pres = 10 mesh = UnitCubeMesh(10, 10, 10) lims = np.array([[xmin, xmin, ymin, ymax, zmin, zmax], [xmax, xmax, ymin, ymax, zmin, zmax], [xmin, xmax, ymin, ymin, zmin, zmax], [xmin, xmax, ymax, ymax, zmin, zmax], [xmin, xmax, ymin, ymax, zmin, zmin], [xmin, xmax, ymin, ymax, zmax, zmax]]) vexpr = Constant((1., 1., 1.)) V = VectorFunctionSpace(mesh, "CG", 1) v = Function(V) v.assign(vexpr) x = RandomBox(Point(0., 0., 0.), Point(1., 1., 1.)).generate([pres, pres, pres]) x = comm.bcast(x, root=0) dt = 0.05 p = particles(x, [x * 0, x**2], mesh) if advection_scheme == 'euler': ap = advect_particles(p, V, v, 'periodic', lims.flatten()) elif advection_scheme == 'rk2': ap = advect_rk2(p, V, v, 'periodic', lims.flatten()) elif advection_scheme == 'rk3': ap = advect_rk3(p, V, v, 'periodic', lims.flatten()) else: assert False xp0 = p.positions() t = 0. while t < 1. - 1e-12: ap.do_step(dt) t += dt xpE = p.positions() xp0_root = comm.gather(xp0, root=0) xpE_root = comm.gather(xpE, root=0) assert len(xp0) == len(xpE) num_particles = p.number_of_particles() if comm.Get_rank() == 0: xp0_root = np.float32(np.vstack(xp0_root)) xpE_root = np.float32(np.vstack(xpE_root)) # Sort on x positions xp0_root = xp0_root[xp0_root[:, 0].argsort(), :] xpE_root = xpE_root[xpE_root[:, 0].argsort(), :] error = np.linalg.norm(xp0_root - xpE_root) assert error < 1e-10 assert num_particles - pres**3 == 0
def test_open_boundary(advection_scheme): xmin, xmax = 0.0, 1.0 ymin, ymax = 0.0, 1.0 pres = 3 mesh = RectangleMesh(Point(xmin, ymin), Point(xmax, ymax), 10, 10) # Particle x = RandomRectangle(Point(0.955, 0.45), Point(1.0, 0.55)).generate([pres, pres]) x = comm.bcast(x, root=0) # Given velocity field: vexpr = Constant((1.0, 1.0)) # Given time do_step: dt = 0.05 p = particles(x, [x, x], mesh) V = VectorFunctionSpace(mesh, "CG", 1) v = Function(V) v.assign(vexpr) # Different boundary parts bound_left = UnitSquareLeft() bound_right = UnitSquareRight() bound_top = UnitSquareTop() bound_bottom = UnitSquareBottom() # Mark all facets facet_marker = MeshFunction("size_t", mesh, mesh.topology().dim() - 1) facet_marker.set_all(0) # Mark as open bound_right.mark(facet_marker, 2) # Mark other boundaries as closed bound_left.mark(facet_marker, 1) bound_top.mark(facet_marker, 1) bound_bottom.mark(facet_marker, 1) if advection_scheme == "euler": ap = advect_particles(p, V, v, facet_marker) elif advection_scheme == "rk2": ap = advect_rk2(p, V, v, facet_marker) elif advection_scheme == "rk3": ap = advect_rk3(p, V, v, facet_marker) else: assert False # Do one timestep, particle must bounce from wall of ap.do_step(dt) num_particles = p.number_of_particles() # Check if all particles left domain if comm.rank == 0: assert (num_particles == 0)
def test_advect_periodic_facet_marker(advection_scheme): xmin, xmax = 0.0, 1.0 ymin, ymax = 0.0, 1.0 mesh = RectangleMesh(Point(xmin, ymin), Point(xmax, ymax), 10, 10) facet_marker = MeshFunction("size_t", mesh, mesh.topology().dim() - 1) facet_marker.set_all(0) boundaries = Boundaries() boundaries.mark(facet_marker, 3) lims = np.array([ [xmin, xmin, ymin, ymax], [xmax, xmax, ymin, ymax], [xmin, xmax, ymin, ymin], [xmin, xmax, ymax, ymax], ]) vexpr = Constant((1.0, 1.0)) V = VectorFunctionSpace(mesh, "CG", 1) x = RandomRectangle(Point(0.05, 0.05), Point(0.15, 0.15)).generate([3, 3]) x = comm.bcast(x, root=0) dt = 0.05 v = Function(V) v.assign(vexpr) p = particles(x, [x * 0, x**2], mesh) if advection_scheme == "euler": ap = advect_particles(p, V, v, facet_marker, lims.flatten()) elif advection_scheme == "rk2": ap = advect_rk2(p, V, v, facet_marker, lims.flatten()) elif advection_scheme == "rk3": ap = advect_rk3(p, V, v, facet_marker, lims.flatten()) else: assert False xp0 = p.positions() t = 0.0 while t < 1.0 - 1e-12: ap.do_step(dt) t += dt xpE = p.positions() # Check if position correct xp0_root = comm.gather(xp0, root=0) xpE_root = comm.gather(xpE, root=0) if comm.Get_rank() == 0: xp0_root = np.float32(np.vstack(xp0_root)) xpE_root = np.float32(np.vstack(xpE_root)) error = np.linalg.norm(xp0_root - xpE_root) assert error < 1e-10
def gaussian_distribution(mb, mu, sigma, function=None, lumping=True, nsteps=100): "Gaussian distribution via heat equation" tend = 0.5 * sigma**2 dt = Constant(tend / nsteps, name="smooth") # prepare the problem P1e = FiniteElement("CG", mb.ufl_cell(), 1) Ve = FunctionSpace(mb, P1e) u, v = TrialFunction(Ve), TestFunction(Ve) uold = Function(Ve) if lumping: # diffusion K = assemble(dt * inner(grad(u), grad(v)) * dx) # we use mass lumping to avoid negative values Md = assemble(action(u * v * dx, Constant(1.0))) # full matrix (divide my mass) M = Matrix(K) M.zero() M.set_diagonal(Md) A = M + K else: a = u * v * dx + dt * inner(grad(u), grad(v)) * dx L = uold * v * dx A = assemble(a) # initial conditions dist = function or Function(Ve) dist.vector().zero() PointSource(Ve, mu, 1.0).apply(dist.vector()) # iterations for t in range(nsteps): uold.assign(dist) if lumping: solve(A, dist.vector(), M * uold.vector()) else: b = assemble(L) solve(A, dist.vector(), b) # normalize area = assemble(dist * dx) dist.vector()[:] /= area if function is None: return dist
def test_bounded_domain_boundary(xlims, ylims, advection_scheme): xmin, xmax = xlims ymin, ymax = ylims pres = 1 mesh = RectangleMesh(Point(xmin, ymin), Point(xmax, ymax), 10, 10) ymin += 0.0025 lims = np.array([xmin, xmax, ymin, ymax]) v_arr = np.array([-1.0, -1.0]) vexpr = Constant(v_arr) V = VectorFunctionSpace(mesh, "CG", 1) x = RandomRectangle(Point(0.05, 0.05), Point(0.15, 0.15)).generate([pres, pres]) dt = 0.005 v = Function(V) v.assign(vexpr) p = particles(x, [x], mesh) if advection_scheme == 'euler': ap = advect_particles(p, V, v, 'bounded', lims.flatten()) elif advection_scheme == 'rk2': ap = advect_rk2(p, V, v, 'bounded', lims.flatten()) elif advection_scheme == 'rk3': ap = advect_rk3(p, V, v, 'bounded', lims.flatten()) else: assert False original_num_particles = p.number_of_particles() t = 0. while t < 3.0 - 1e-12: ap.do_step(dt) t += dt assert p.number_of_particles() == original_num_particles xpn = np.array(p.get_property(0)).reshape((-1, 2)) x0 = np.array(p.get_property(1)).reshape((-1, 2)) analytical_position = x0 + t * v_arr analytical_position[:, 0] = np.maximum( np.minimum(xmax, analytical_position[:, 0]), xmin) analytical_position[:, 1] = np.maximum( np.minimum(ymax, analytical_position[:, 1]), ymin) error = np.abs(xpn - analytical_position) assert np.all(np.abs(error) < 1e-12)
def _compute_time_errors(problem, method, mesh_sizes, Dt, plot_error=False): mesh_generator, solution, ProblemClass, cell_type = problem() # Translate data into FEniCS expressions. fenics_sol = Expression(smp.printing.ccode(solution['value']), degree=solution['degree'], t=0.0, cell=cell_type ) # Compute the problem errors = {'theta': numpy.empty((len(mesh_sizes), len(Dt)))} # Create initial state. # Deepcopy the expression into theta0. Specify the cell to allow for # more involved operations with it (e.g., grad()). theta0 = Expression(fenics_sol.cppcode, degree=solution['degree'], t=0.0, cell=cell_type ) for k, mesh_size in enumerate(mesh_sizes): mesh = mesh_generator(mesh_size) V = FunctionSpace(mesh, 'CG', 1) theta_approx = Function(V) theta0p = project(theta0, V) stepper = method(ProblemClass(V)) if plot_error: error = Function(V) for j, dt in enumerate(Dt): # TODO We are facing a little bit of a problem here, being the # fact that the time stepper only accept elements from V as u0. # In principle, though, this isn't necessary or required. We # could allow for arbitrary expressions here, but then the API # would need changing for problem.lhs(t, u). # Think about this. stepper.step(theta_approx, theta0p, 0.0, dt, tol=1.0e-12, verbose=False ) fenics_sol.t = dt # # NOTE # When using errornorm(), it is quite likely to see a good part # of the error being due to the spatial discretization. Some # analyses "get rid" of this effect by (sometimes implicitly) # projecting the exact solution onto the discrete function # space. errors['theta'][k][j] = errornorm(fenics_sol, theta_approx) if plot_error: error.assign(project(fenics_sol - theta_approx, V)) plot(error, title='error (dt=%e)' % dt) interactive() return errors, stepper.name, stepper.order
def assemble_solution(self, t): # returns Womersley sol for time t if self.tc is not None: self.tc.start('assembleSol') sol = Function(self.solutionSpace) dofs2 = self.solutionSpace.sub(2).dofmap().dofs() # gives field of indices corresponding to z axis sol.assign(Constant(("0.0", "0.0", "0.0"))) # QQ not needed sol.vector()[dofs2] += self.factor * self.bessel_parabolic.vector().array() # parabolic part of sol for idx in range(8): # add modes of Womersley sol sol.vector()[dofs2] += self.factor * cos(self.coefs_exp[idx] * pi * t) * self.bessel_real[idx].vector().array() sol.vector()[dofs2] += self.factor * -sin(self.coefs_exp[idx] * pi * t) * self.bessel_complex[idx].vector().array() if self.tc is not None: self.tc.end('assembleSol') return sol
def test_advect_open(advection_scheme): pres = 3 mesh = UnitCubeMesh(10, 10, 10) # Particle x = RandomBox(Point(0.955, 0.45, 0.5), Point(0.99, 0.55, 0.6)).generate([pres, pres, pres]) x = comm.bcast(x, root=0) # Given velocity field: vexpr = Constant((1.0, 1.0, 1.0)) # Given time do_step: dt = 0.05 p = particles(x, [x, x], mesh) V = VectorFunctionSpace(mesh, "CG", 1) v = Function(V) v.assign(vexpr) # Different boundary parts bounds = Boundaries() bound_right = UnitCubeRight() # Mark all facets facet_marker = MeshFunction("size_t", mesh, mesh.topology().dim() - 1) facet_marker.set_all(0) bounds.mark(facet_marker, 1) bound_right.mark(facet_marker, 2) # Mark as open bound_right.mark(facet_marker, 2) if advection_scheme == "euler": ap = advect_particles(p, V, v, facet_marker) elif advection_scheme == "rk2": ap = advect_rk2(p, V, v, facet_marker) elif advection_scheme == "rk3": ap = advect_rk3(p, V, v, facet_marker) else: assert False # Do one timestep, particle must bounce from wall of ap.do_step(dt) num_particles = p.number_of_particles() # Check if all particles left domain if comm.rank == 0: assert num_particles == 0
def test_advect_particle(advection_scheme): if comm.rank == 0: print('Run advect_particle') # Rotate one particle, and compute the error mesh = UnitSquareMesh(10, 10) # Particle x = np.array([[0.25, 0.25]]) dt_list = [0.08, 0.04, 0.02, 0.01, 0.005] # Velocity field vexpr = Expression(('-pi*(x[1] - 0.5)', 'pi*(x[0]-0.5)'), degree=3) V = VectorFunctionSpace(mesh, "CG", 1) v = Function(V) v.assign(vexpr) error_list = [] for dt in dt_list: p = particles(x, [x, x], mesh) if advection_scheme == 'euler': ap = advect_particles(p, V, v, 'closed') elif advection_scheme == 'rk2': ap = advect_rk2(p, V, v, 'closed') elif advection_scheme == 'rk3': ap = advect_rk3(p, V, v, 'closed') else: assert False xp_0 = p.positions() t = 0. while t < 2.-1e-12: ap.do_step(dt) t += dt xp_end = p.positions() error_list.append(np.linalg.norm(xp_0 - xp_end)) if not all(eps == 0 for eps in error_list): rate = compute_convergence(dt_list, error_list) if advection_scheme == 'euler': # First order for euler assert any(i > 0.9 for i in rate) elif advection_scheme == 'rk2': # Second order for rk2 assert any(i > 1.95 for i in rate) elif advection_scheme == 'rk3': # Third order for rk3 assert any(i > 2.9 for i in rate)
def compute_velocity_correction( ui, p0, p1, u_bcs, rho, mu, dt, rotational_form, my_dx, tol, verbose ): """Compute the velocity correction according to .. math:: U = u_0 - \\frac{dt}{\\rho} \\nabla (p_1-p_0). """ W = ui.function_space() P = p1.function_space() u = TrialFunction(W) v = TestFunction(W) a3 = dot(u, v) * my_dx phi = Function(P) phi.assign(p1) if p0: phi -= p0 if rotational_form: r = SpatialCoordinate(W.mesh())[0] div_ui = 1 / r * (r * ui[0]).dx(0) + ui[1].dx(1) phi += mu * div_ui L3 = dot(ui, v) * my_dx - dt / rho * (phi.dx(0) * v[0] + phi.dx(1) * v[1]) * my_dx u1 = Function(W) solve( a3 == L3, u1, bcs=u_bcs, solver_parameters={ "linear_solver": "iterative", "symmetric": True, "preconditioner": "hypre_amg", "krylov_solver": { "relative_tolerance": tol, "absolute_tolerance": 0.0, "maximum_iterations": 100, "monitor_convergence": verbose, }, }, ) # u = project(ui - k/rho * grad(phi), V) # div_u = 1/r * div(r*u) r = SpatialCoordinate(W.mesh())[0] div_u1 = 1.0 / r * (r * u1[0]).dx(0) + u1[1].dx(1) info("||u||_div = {:e}".format(sqrt(assemble(div_u1 * div_u1 * my_dx)))) return u1
def _compute_time_errors(problem, method, mesh_sizes, Dt, plot_error=False): mesh_generator, solution, ProblemClass, _ = problem() # Translate data into FEniCS expressions. fenics_sol = Expression(sympy.printing.ccode(solution), degree=MAX_DEGREE, t=0.0) # Compute the problem errors = numpy.empty((len(mesh_sizes), len(Dt))) # Create initial state. # Deepcopy the expression into theta0. Specify the cell to allow for # more involved operations with it (e.g., grad()). theta0 = Expression(fenics_sol.cppcode, degree=MAX_DEGREE, t=0.0) for k, mesh_size in enumerate(mesh_sizes): mesh = mesh_generator(mesh_size) # Choose the function space such that the exact solution can be # represented as well as possible. V = FunctionSpace(mesh, 'CG', 4) theta_approx = Function(V) theta0p = project(theta0, V) stepper = method(ProblemClass(V)) if plot_error: error = Function(V) for j, dt in enumerate(Dt): # TODO We are facing a little bit of a problem here, being the fact # that the time stepper only accept elements from V as u0. In # principle, though, this isn't necessary or required. We could # allow for arbitrary expressions here, but then the API would need # changing for problem.lhs(t, u). Think about this. theta_approx.assign(stepper.step(theta0p, 0.0, dt)) fenics_sol.t = dt # NOTE # When using errornorm(), it is quite likely to see a good part of # the error being due to the spatial discretization. Some analyses # "get rid" of this effect by (sometimes implicitly) projecting the # exact solution onto the discrete function space. errors[k][j] = errornorm(fenics_sol, theta_approx) if plot_error: error.assign(project(fenics_sol - theta_approx, V)) plot(error, title='error (dt={:e})'.format(dt)) plt.show() return errors
def assemble_solution(self, t): # returns """ :param t: time :return: Womersley flow (analytic solution) at time t analytic solution at any time is a steady parabolic flow + linear combination of 8 modes modes were precomputed as 8 functions on given mesh and stored in hdf5 file """ if self.tc is not None: self.tc.start('assembleSol') sol = Function(self.solutionSpace) # analytic solution has zero x and y components dofs2 = self.solutionSpace.sub(2).dofmap().dofs() # gives field of indices corresponding to z axis sol.assign(Constant(("0.0", "0.0", "0.0"))) # QQ not needed sol.vector()[dofs2] += self.factor * self.bessel_parabolic.vector().array() # parabolic part of sol for idx in range(8): # add modes of Womersley sol sol.vector()[dofs2] += self.factor * cos(self.coefs_exp[idx] * pi * t) * self.bessel_real[idx].vector().array() sol.vector()[dofs2] += self.factor * -sin(self.coefs_exp[idx] * pi * t) * self.bessel_complex[idx].vector().array() if self.tc is not None: self.tc.end('assembleSol') return sol
def test_l2_projection_3D(polynomial_order, in_expression): xmin, ymin, zmin = 0.0, 0.0, 0.0 xmax, ymax, zmax = 1.0, 1.0, 1.0 nx = 25 property_idx = 1 mesh = BoxMesh(Point(xmin, ymin, zmin), Point(xmax, ymax, zmax), nx, nx, nx) interpolate_expression = Expression(in_expression, degree=3) if len(interpolate_expression.ufl_shape) == 0: V = FunctionSpace(mesh, "DG", polynomial_order) elif len(interpolate_expression.ufl_shape) == 1: V = VectorFunctionSpace(mesh, "DG", polynomial_order) v_exact = Function(V) v_exact.assign(interpolate_expression) x = RandomBox(Point(0.0, 0.0, 0.0), Point(1.0, 1.0, 1.0)).generate([4, 4, 4]) s = assign_particle_values(x, interpolate_expression) # Just make a complicated particle, possibly with scalars and vectors mixed p = particles(x, [s], mesh) # Do AddDelete sweep AD = AddDelete(p, 13, 15, [v_exact]) AD.do_sweep() vh = Function(V) lstsq_vh = l2projection(p, V, property_idx) lstsq_vh.project(vh.cpp_object()) error_sq = abs(assemble(dot(v_exact - vh, v_exact - vh) * dx)) if comm.Get_rank() == 0: assert error_sq < 1e-13
def solve(self, problem): self.problem = problem doSave = problem.doSave save_this_step = False onlyVel = problem.saveOnlyVel dt = self.metadata['dt'] nu = Constant(self.problem.nu) # TODO check proper use of watches self.tc.init_watch('init', 'Initialization', True, count_to_percent=False) self.tc.init_watch('rhs', 'Assembled right hand side', True, count_to_percent=True) self.tc.init_watch('updateBC', 'Updated velocity BC', True, count_to_percent=True) self.tc.init_watch('applybc1', 'Applied velocity BC 1st step', True, count_to_percent=True) self.tc.init_watch('applybc3', 'Applied velocity BC 3rd step', True, count_to_percent=True) self.tc.init_watch('applybcP', 'Applied pressure BC or othogonalized rhs', True, count_to_percent=True) self.tc.init_watch('assembleMatrices', 'Initial matrix assembly', False, count_to_percent=True) self.tc.init_watch('solve 1', 'Running solver on 1st step', True, count_to_percent=True) self.tc.init_watch('solve 2', 'Running solver on 2nd step', True, count_to_percent=True) self.tc.init_watch('solve 3', 'Running solver on 3rd step', True, count_to_percent=True) self.tc.init_watch('solve 4', 'Running solver on 4th step', True, count_to_percent=True) self.tc.init_watch('assembleA1', 'Assembled A1 matrix (without stabiliz.)', True, count_to_percent=True) self.tc.init_watch('assembleA1stab', 'Assembled A1 stabilization', True, count_to_percent=True) self.tc.init_watch('next', 'Next step assignments', True, count_to_percent=True) self.tc.init_watch('saveVel', 'Saved velocity', True) self.tc.start('init') # Define function spaces (P2-P1) mesh = self.problem.mesh self.V = VectorFunctionSpace(mesh, "Lagrange", 2) # velocity self.Q = FunctionSpace(mesh, "Lagrange", 1) # pressure self.PS = FunctionSpace(mesh, "Lagrange", 2) # partial solution (must be same order as V) self.D = FunctionSpace(mesh, "Lagrange", 1) # velocity divergence space if self.bc == 'lagrange': L = FunctionSpace(mesh, "R", 0) QL = self.Q*L problem.initialize(self.V, self.Q, self.PS, self.D) # Define trial and test functions u = TrialFunction(self.V) v = TestFunction(self.V) if self.bc == 'lagrange': (pQL, rQL) = TrialFunction(QL) (qQL, lQL) = TestFunction(QL) else: p = TrialFunction(self.Q) q = TestFunction(self.Q) n = FacetNormal(mesh) I = Identity(u.geometric_dimension()) # Initial conditions: u0 velocity at previous time step u1 velocity two time steps back p0 previous pressure [u1, u0, p0] = self.problem.get_initial_conditions([{'type': 'v', 'time': -dt}, {'type': 'v', 'time': 0.0}, {'type': 'p', 'time': 0.0}]) if doSave: problem.save_vel(False, u0, 0.0) problem.save_vel(True, u0, 0.0) u_ = Function(self.V) # current tentative velocity u_cor = Function(self.V) # current corrected velocity if self.bc == 'lagrange': p_QL = Function(QL) # current pressure or pressure help function from rotation scheme pQ = Function(self.Q) # auxiliary function for conversion between QL.sub(0) and Q else: p_ = Function(self.Q) # current pressure or pressure help function from rotation scheme p_mod = Function(self.Q) # current modified pressure from rotation scheme # Define coefficients k = Constant(self.metadata['dt']) f = Constant((0, 0, 0)) # Define forms # step 1: Tentative velocity, solve to u_ u_ext = 1.5*u0 - 0.5*u1 # extrapolation for convection term # Stabilisation h = CellSize(mesh) # CBC delta: if self.cbcDelta: delta = Constant(self.stabCoef)*h/(sqrt(inner(u_ext, u_ext))+h) else: delta = Constant(self.stabCoef)*h**2/(2*nu*k + k*h*inner(u_ext, u_ext)+h**2) if self.use_full_SUPG: v1 = v + delta*0.5*k*dot(grad(v), u_ext) parameters['form_compiler']['quadrature_degree'] = 6 else: v1 = v def nonlinearity(function): if self.use_ema: return 2*inner(dot(sym(grad(function)), u_ext), v1) * dx + inner(div(function)*u_ext, v1) * dx # return 2*inner(dot(sym(grad(function)), u_ext), v) * dx + inner(div(u_ext)*function, v) * dx # QQ implement this way? else: return inner(dot(grad(function), u_ext), v1) * dx def diffusion(fce): if self.useLaplace: return nu*inner(grad(fce), grad(v1)) * dx else: form = inner(nu * 2 * sym(grad(fce)), sym(grad(v1))) * dx if self.bcv == 'CDN': # IMP will work only if p=0 on output, or we must add term # inner(p0*n, v)*problem.get_outflow_measure_form() to avoid boundary layer return form if self.bcv == 'LAP': return form - inner(nu*dot(grad(fce).T, n), v1) * problem.get_outflow_measure_form() if self.bcv == 'DDN': # IMP will work only if p=0 on output, or we must add term # inner(p0*n, v)*problem.get_outflow_measure_form() to avoid boundary layer return form # additional term must be added to non-constant part def pressure_rhs(): if self.useLaplace or self.bcv == 'LAP': return inner(p0, div(v1)) * dx - inner(p0*n, v1) * problem.get_outflow_measure_form() # NT term inner(inner(p, n), v) is 0 when p=0 on outflow else: return inner(p0, div(v1)) * dx a1_const = (1./k)*inner(u, v1)*dx + diffusion(0.5*u) a1_change = nonlinearity(0.5*u) if self.bcv == 'DDN': # IMP Problem: Does not penalize influx for current step, only for the next one # IMP this can lead to oscilation: DDN correct next step, but then u_ext is OK so in next step DDN is not used, leading to new influx... # u and u_ext cannot be switched, min_value is nonlinear function a1_change += -0.5*min_value(Constant(0.), inner(u_ext, n))*inner(u, v1)*problem.get_outflow_measure_form() # IMP works only with uflacs compiler L1 = (1./k)*inner(u0, v1)*dx - nonlinearity(0.5*u0) - diffusion(0.5*u0) + pressure_rhs() if self.bcv == 'DDN': L1 += 0.5*min_value(0., inner(u_ext, n))*inner(u0, v1)*problem.get_outflow_measure_form() # Non-consistent SUPG stabilisation if self.stabilize and not self.use_full_SUPG: # a1_stab = delta*inner(dot(grad(u), u_ext), dot(grad(v), u_ext))*dx a1_stab = 0.5*delta*inner(dot(grad(u), u_ext), dot(grad(v), u_ext))*dx(None, {'quadrature_degree': 6}) # NT optional: use Crank Nicolson in stabilisation term: change RHS # L1 += -0.5*delta*inner(dot(grad(u0), u_ext), dot(grad(v), u_ext))*dx(None, {'quadrature_degree': 6}) outflow_area = Constant(problem.outflow_area) need_outflow = Constant(0.0) if self.useRotationScheme: # Rotation scheme if self.bc == 'lagrange': F2 = inner(grad(pQL), grad(qQL))*dx + (1./k)*qQL*div(u_)*dx + pQL*lQL*dx + qQL*rQL*dx else: F2 = inner(grad(p), grad(q))*dx + (1./k)*q*div(u_)*dx else: # Projection, solve to p_ if self.bc == 'lagrange': F2 = inner(grad(pQL - p0), grad(qQL))*dx + (1./k)*qQL*div(u_)*dx + pQL*lQL*dx + qQL*rQL*dx else: if self.forceOutflow and problem.can_force_outflow: info('Forcing outflow.') F2 = inner(grad(p - p0), grad(q))*dx + (1./k)*q*div(u_)*dx for m in problem.get_outflow_measures(): F2 += (1./k)*(1./outflow_area)*need_outflow*q*m else: F2 = inner(grad(p - p0), grad(q))*dx + (1./k)*q*div(u_)*dx a2, L2 = system(F2) # step 3: Finalize, solve to u_ if self.useRotationScheme: # Rotation scheme if self.bc == 'lagrange': F3 = (1./k)*inner(u - u_, v)*dx + inner(grad(p_QL.sub(0)), v)*dx else: F3 = (1./k)*inner(u - u_, v)*dx + inner(grad(p_), v)*dx else: if self.bc == 'lagrange': F3 = (1./k)*inner(u - u_, v)*dx + inner(grad(p_QL.sub(0) - p0), v)*dx else: F3 = (1./k)*inner(u - u_, v)*dx + inner(grad(p_ - p0), v)*dx a3, L3 = system(F3) if self.useRotationScheme: # Rotation scheme: modify pressure if self.bc == 'lagrange': pr = TrialFunction(self.Q) qr = TestFunction(self.Q) F4 = (pr - p0 - p_QL.sub(0) + nu*div(u_))*qr*dx else: F4 = (p - p0 - p_ + nu*div(u_))*q*dx # TODO zkusit, jestli to nebude rychlejsi? nepocitat soustavu, ale p.assign(...), nutno project(div(u),Q) coz je pocitani podobne soustavy # TODO zkusit v project zadat solver_type='lu' >> primy resic by mel byt efektivnejsi a4, L4 = system(F4) # Assemble matrices self.tc.start('assembleMatrices') A1_const = assemble(a1_const) # need to be here, so A1 stays one Python object during repeated assembly A1_change = A1_const.copy() # copy to get matrix with same sparse structure (data will be overwriten) if self.stabilize and not self.use_full_SUPG: A1_stab = A1_const.copy() # copy to get matrix with same sparse structure (data will be overwriten) A2 = assemble(a2) A3 = assemble(a3) if self.useRotationScheme: A4 = assemble(a4) self.tc.end('assembleMatrices') if self.solvers == 'direct': self.solver_vel_tent = LUSolver('mumps') self.solver_vel_cor = LUSolver('mumps') self.solver_p = LUSolver('umfpack') if self.useRotationScheme: self.solver_rot = LUSolver('umfpack') else: # NT not needed, chosen not to use hypre_parasails # if self.prec_v == 'hypre_parasails': # in FEniCS 1.6.0 inaccessible using KrylovSolver class # self.solver_vel_tent = PETScKrylovSolver('gmres') # PETSc4py object # self.solver_vel_tent.ksp().getPC().setType('hypre') # PETScOptions.set('pc_hypre_type', 'parasails') # # this is global setting, but preconditioners for pressure solvers are set by their constructors # else: self.solver_vel_tent = KrylovSolver('gmres', self.prec_v) # nonsymetric > gmres # IMP cannot use 'ilu' in parallel (choose different default option) self.solver_vel_cor = KrylovSolver('cg', 'hypre_amg') # nonsymetric > gmres self.solver_p = KrylovSolver('cg', self.prec_p) # symmetric > CG if self.useRotationScheme: self.solver_rot = KrylovSolver('cg', self.prec_p) solver_options = {'monitor_convergence': True, 'maximum_iterations': 1000, 'nonzero_initial_guess': True} # 'nonzero_initial_guess': True with solver.solbe(A, u, b) means that # Solver will use anything stored in u as an initial guess # Get the nullspace if there are no pressure boundary conditions foo = Function(self.Q) # auxiliary vector for setting pressure nullspace if self.bc in ['nullspace', 'nullspace_s']: null_vec = Vector(foo.vector()) self.Q.dofmap().set(null_vec, 1.0) null_vec *= 1.0/null_vec.norm('l2') self.null_space = VectorSpaceBasis([null_vec]) if self.bc == 'nullspace': as_backend_type(A2).set_nullspace(self.null_space) # apply global options for Krylov solvers self.solver_vel_tent.parameters['relative_tolerance'] = 10 ** (-self.precision_rel_v_tent) self.solver_vel_tent.parameters['absolute_tolerance'] = 10 ** (-self.precision_abs_v_tent) self.solver_vel_cor.parameters['relative_tolerance'] = 10E-12 self.solver_vel_cor.parameters['absolute_tolerance'] = 10E-4 self.solver_p.parameters['relative_tolerance'] = 10**(-self.precision_p) self.solver_p.parameters['absolute_tolerance'] = 10E-10 if self.useRotationScheme: self.solver_rot.parameters['relative_tolerance'] = 10**(-self.precision_p) self.solver_rot.parameters['absolute_tolerance'] = 10E-10 if self.solvers == 'krylov': for solver in [self.solver_vel_tent, self.solver_vel_cor, self.solver_p, self.solver_rot] if \ self.useRotationScheme else [self.solver_vel_tent, self.solver_vel_cor, self.solver_p]: for key, value in solver_options.items(): try: solver.parameters[key] = value except KeyError: info('Invalid option %s for KrylovSolver' % key) return 1 solver.parameters['preconditioner']['structure'] = 'same' # matrices A2-A4 do not change, so we can reuse preconditioners self.solver_vel_tent.parameters['preconditioner']['structure'] = 'same_nonzero_pattern' # matrix A1 changes every time step, so change of preconditioner must be allowed if self.bc == 'lagrange': fa = FunctionAssigner(self.Q, QL.sub(0)) # boundary conditions bcu, bcp = problem.get_boundary_conditions(self.bc == 'outflow', self.V, self.Q) self.tc.end('init') # Time-stepping info("Running of Incremental pressure correction scheme n. 1") ttime = self.metadata['time'] t = dt step = 1 while t < (ttime + dt/2.0): info("t = %f" % t) self.problem.update_time(t, step) if self.MPI_rank == 0: problem.write_status_file(t) if doSave: save_this_step = problem.save_this_step # DDN debug # u_ext_in = assemble(inner(u_ext, n)*problem.get_outflow_measure_form()) # DDN_triggered = assemble(min_value(Constant(0.), inner(u_ext, n))*problem.get_outflow_measure_form()) # print('DDN: u_ext*n dSout = ', u_ext_in) # print('DDN: negative part of u_ext*n dSout = ', DDN_triggered) # assemble matrix (it depends on solution) self.tc.start('assembleA1') assemble(a1_change, tensor=A1_change) # assembling into existing matrix is faster than assembling new one A1 = A1_const.copy() # we dont want to change A1_const A1.axpy(1, A1_change, True) self.tc.end('assembleA1') self.tc.start('assembleA1stab') if self.stabilize and not self.use_full_SUPG: assemble(a1_stab, tensor=A1_stab) # assembling into existing matrix is faster than assembling new one A1.axpy(1, A1_stab, True) self.tc.end('assembleA1stab') # Compute tentative velocity step begin("Computing tentative velocity") self.tc.start('rhs') b = assemble(L1) self.tc.end('rhs') self.tc.start('applybc1') [bc.apply(A1, b) for bc in bcu] self.tc.end('applybc1') try: self.tc.start('solve 1') self.solver_vel_tent.solve(A1, u_.vector(), b) self.tc.end('solve 1') if save_this_step: self.tc.start('saveVel') problem.save_vel(True, u_, t) self.tc.end('saveVel') if save_this_step and not onlyVel: problem.save_div(True, u_) problem.compute_err(True, u_, t) problem.compute_div(True, u_) except RuntimeError as inst: problem.report_fail(t) return 1 end() # DDN debug # u_ext_in = assemble(inner(u_, n)*problem.get_outflow_measure_form()) # DDN_triggered = assemble(min_value(Constant(0.), inner(u_, n))*problem.get_outflow_measure_form()) # print('DDN: u_tent*n dSout = ', u_ext_in) # print('DDN: negative part of u_tent*n dSout = ', DDN_triggered) if self.useRotationScheme: begin("Computing tentative pressure") else: begin("Computing pressure") if self.forceOutflow and problem.can_force_outflow: out = problem.compute_outflow(u_) info('Tentative outflow: %f' % out) n_o = -problem.last_inflow-out info('Needed outflow: %f' % n_o) need_outflow.assign(n_o) self.tc.start('rhs') b = assemble(L2) self.tc.end('rhs') self.tc.start('applybcP') [bc.apply(A2, b) for bc in bcp] if self.bc in ['nullspace', 'nullspace_s']: self.null_space.orthogonalize(b) self.tc.end('applybcP') try: self.tc.start('solve 2') if self.bc == 'lagrange': self.solver_p.solve(A2, p_QL.vector(), b) else: self.solver_p.solve(A2, p_.vector(), b) self.tc.end('solve 2') except RuntimeError as inst: problem.report_fail(t) return 1 if self.useRotationScheme: foo = Function(self.Q) if self.bc == 'lagrange': fa.assign(pQ, p_QL.sub(0)) foo.assign(pQ + p0) else: foo.assign(p_+p0) problem.averaging_pressure(foo) if save_this_step and not onlyVel: problem.save_pressure(True, foo) else: if self.bc == 'lagrange': fa.assign(pQ, p_QL.sub(0)) problem.averaging_pressure(pQ) if save_this_step and not onlyVel: problem.save_pressure(False, pQ) else: # we do not want to change p=0 on outflow, it conflicts with do-nothing conditions foo = Function(self.Q) foo.assign(p_) problem.averaging_pressure(foo) if save_this_step and not onlyVel: problem.save_pressure(False, foo) end() begin("Computing corrected velocity") self.tc.start('rhs') b = assemble(L3) self.tc.end('rhs') if not self.B: self.tc.start('applybc3') [bc.apply(A3, b) for bc in bcu] self.tc.end('applybc3') try: self.tc.start('solve 3') self.solver_vel_cor.solve(A3, u_cor.vector(), b) self.tc.end('solve 3') problem.compute_err(False, u_cor, t) problem.compute_div(False, u_cor) except RuntimeError as inst: problem.report_fail(t) return 1 if save_this_step: self.tc.start('saveVel') problem.save_vel(False, u_cor, t) self.tc.end('saveVel') if save_this_step and not onlyVel: problem.save_div(False, u_cor) end() # DDN debug # u_ext_in = assemble(inner(u_cor, n)*problem.get_outflow_measure_form()) # DDN_triggered = assemble(min_value(Constant(0.), inner(u_cor, n))*problem.get_outflow_measure_form()) # print('DDN: u_cor*n dSout = ', u_ext_in) # print('DDN: negative part of u_cor*n dSout = ', DDN_triggered) if self.useRotationScheme: begin("Rotation scheme pressure correction") self.tc.start('rhs') b = assemble(L4) self.tc.end('rhs') try: self.tc.start('solve 4') self.solver_rot.solve(A4, p_mod.vector(), b) self.tc.end('solve 4') except RuntimeError as inst: problem.report_fail(t) return 1 problem.averaging_pressure(p_mod) if save_this_step and not onlyVel: problem.save_pressure(False, p_mod) end() # compute functionals (e. g. forces) problem.compute_functionals(u_cor, p_mod if self.useRotationScheme else (pQ if self.bc == 'lagrange' else p_), t) # Move to next time step self.tc.start('next') u1.assign(u0) u0.assign(u_cor) u_.assign(u_cor) # use corretced velocity as initial guess in first step if self.useRotationScheme: p0.assign(p_mod) else: if self.bc == 'lagrange': p0.assign(pQ) else: p0.assign(p_) t = round(t + dt, 6) # round time step to 0.000001 step += 1 self.tc.end('next') info("Finished: Incremental pressure correction scheme n. 1") problem.report() return 0
def solve_fixed_point( mesh, W_element, P_element, Q_element, u0, p0, theta0, kappa, rho, mu, cp, g, extra_force, heat_source, u_bcs, p_bcs, theta_dirichlet_bcs, theta_neumann_bcs, my_dx, my_ds, max_iter, tol ): # Solve the coupled heat-Stokes equation approximately. Do this # iteratively by solving the heat equation, then solving Stokes with the # updated heat, the heat equation with the updated velocity and so forth # until the change is 'small'. WP = FunctionSpace(mesh, MixedElement([W_element, P_element])) Q = FunctionSpace(mesh, Q_element) # Initialize functions. up0 = Function(WP) u0, p0 = up0.split() theta1 = Function(Q) for _ in range(max_iter): heat_problem = heat.Heat( Q, kappa=kappa, rho=rho(theta0), cp=cp, convection=u0, source=heat_source, dirichlet_bcs=theta_dirichlet_bcs, neumann_bcs=theta_neumann_bcs, my_dx=my_dx, my_ds=my_ds ) theta1.assign(heat_problem.solve_stationary()) # Solve problem for velocity, pressure. f = rho(theta0) * g # coupling if extra_force: f += as_vector((extra_force[0], extra_force[1], 0.0)) # up1 = up0.copy() stokes.stokes_solve( up0, mu, u_bcs, p_bcs, f, my_dx=my_dx, tol=1.0e-10, verbose=False, maxiter=1000 ) # from dolfin import plot # plot(u0) # plot(theta0) theta_diff = errornorm(theta0, theta1) info('||theta - theta0|| = {:e}'.format(theta_diff)) # info('||u - u0|| = {:e}'.format(u_diff)) # info('||p - p0|| = {:e}'.format(p_diff)) # diff = theta_diff + u_diff + p_diff diff = theta_diff info('sum = {:e}'.format(diff)) # # Show the iterates. # plot(theta0, title='theta0') # plot(u0, title='u0') # interactive() # #exit() if diff < tol: break theta0.assign(theta1) # Create a *deep* copy of u0, p0, to be able to deal with them as # actually separate entities. u0, p0 = up0.split(deepcopy=True) return u0, p0, theta0
print("Starting computation with grid resolution " + str(nx)) output_field = File(outdir + "psi_h" + "_nx" + str(nx) + ".pvd") # Compute num steps till completion num_steps = np.rint(Tend / float(dt)) # Generate mesh mesh = RectangleMesh.create( [Point(xmin, ymin), Point(xmax, ymax)], [nx, nx], CellType.Type.triangle ) # Velocity and initial condition V = VectorFunctionSpace(mesh, "CG", 1) uh = Function(V) uh.assign(Expression((ux, vy), degree=1)) psi0_expression = SineHump(center=[0.5, 0.5], U=[float(ux), float(vy)], time=0.0, degree=6) # Generate particles x = RegularRectangle(Point(xmin, ymin), Point(xmax, ymax)).generate([pres, pres]) s = np.zeros((len(x), 1), dtype=np.float_) # Initialize particles with position x and scalar property s at the mesh p = particles(x, [s], mesh) property_idx = 1 # Scalar quantity is stored at slot 1 # Initialize advection class, simple forward Euler suffices ap = advect_particles(p, V, uh, "periodic", lims.flatten()) # Define the variational (projection problem)
class OSI(Field): @classmethod def default_params(cls): params = Field.default_params() params.update( finalize=True, ) return params def add_fields(self): params = self.params.copy_recursive() params["save"] = False params["plot"] = False #params["callback"] = False #params.pop("finalize") fields = [] #fields.append(WSS(params=params)) #return fields f = TimeIntegral("WSS", params=params, label="OSI") fields.append(f) fields.append(Magnitude(f, params=params)) f = Magnitude("WSS", params=params) fields.append(f) fields.append(TimeIntegral(f, params=params, label="OSI")) #f = TimeIntegral("WSS", label="OSI") #fields.append(f) #fields.append(Magnitude(f)) #f = Magnitude("WSS") #fields.append(f) #fields.append(TimeIntegral(f, label="OSI")) return fields def before_first_compute(self, get): tau = get("WSS") self.osi = Function(tau.sub(0).function_space().collapse()) def compute(self, get): # Requires the fields Magnitude(TimeIntegral("WSS", label="OSI")) and # TimeIntegral(Magnitude("WSS"), label="OSI") #self.mag_ta_wss = get("Magnitude_TimeIntegral_WSS_OSI") #self.ta_mag_wss = get("TimeIntegral_Magnitude_WSS_OSI") self.mag_ta_wss = get("Magnitude_TimeIntegral_WSS-OSI") self.ta_mag_wss = get("TimeIntegral_Magnitude_WSS-OSI") if self.params.finalize: return None elif self.mag_ta_wss == None or self.ta_mag_wss == None: return None else: expr = conditional(self.ta_mag_wss < 1e-15, 0.0, 0.5 * (1.0 - self.mag_ta_wss / self.ta_mag_wss)) self.osi.assign(project(expr, self.osi.function_space())) return self.osi def after_last_compute(self, get): self.mag_ta_wss = get("Magnitude_TimeIntegral_WSS-OSI") self.ta_mag_wss = get("TimeIntegral_Magnitude_WSS-OSI") #print self.name, " Calling after_last_compute" expr = conditional(self.ta_mag_wss < 1e-15, 0.0, 0.5 * (1.0 - self.mag_ta_wss / self.ta_mag_wss)) self.osi.assign(project(expr, self.osi.function_space())) return self.osi
class GeneralProblem(object): def __init__(self, args, tc, metadata): self.MPI_rank = MPI.rank(mpi_comm_world()) self.metadata = metadata # need to be specified in subclass init before calling this init self.problem_code = self.problem_code self.metadata['pcode'] = self.problem_code self.has_analytic_solution = self.has_analytic_solution self.metadata['hasAnalyticSolution'] = self.has_analytic_solution self.args = args self.tc = tc self.tc.init_watch('saveP', 'Saved pressure', True) self.tc.init_watch('saveVel', 'Saved velocity', True) self.tc.init_watch('averageP', 'Averaged pressure', True) self.tc.init_watch('updateBC', 'Updated velocity BC', True) self.tc.init_watch('div', 'Computed and saved divergence', True) self.tc.init_watch('divNorm', 'Computed norm of divergence', True) # If it is sensible (and implemented) to force pressure gradient on outflow boundary # 1. set self.outflow_area in initialize # 2. implement self.compute_outflow and get_outflow_measures self.can_force_outflow = False self.outflow_area = None self.normal = None self.mesh = None self.facet_function = None self.mesh_volume = None self.outflow_measures = [] # stopping criteria (for relative H1 velocity error norm) (if known) self.divergence_treshold = 10 # used for writing status files .run to monitor progress during computation: self.last_status_functional = 0.0 self.status_functional_str = 'to be defined in Problem class' self.stepsInSecond = None self.volume = None self.vSpace = None self.vFunction = None self.divSpace = None self.divFunction = None self.pSpace = None self.pFunction = None self.solutionSpace = None self.solution = None self.actual_time = 0.0 self.step_number = 0 self.save_this_step = False self.isWholeSecond = None self.N1 = None self.N0 = None self.vel_normalization_factor = [] self.pg_normalization_factor = [] self.p_normalization_factor = [] self.scale_factor = [] self.analytic_v_norm_L2 = None self.analytic_v_norm_H1 = None self.analytic_v_norm_H1w = None # dictionaries for output files self.fileDict = {'u': {'name': 'velocity'}, 'p': {'name': 'pressure'}, # 'pg': {'name': 'pressure_grad'}, 'd': {'name': 'divergence'}} self.fileDictTent = {'u2': {'name': 'velocity_tent'}, 'd2': {'name': 'divergence_tent'}} self.fileDictDiff = {'uD': {'name': 'velocity_diff'}, 'pD': {'name': 'pressure_diff'}, 'pgD': {'name': 'pressure_grad_diff'}} self.fileDictTentDiff = {'u2D': {'name': 'velocity_tent_diff'}} self.fileDictTentP = {'p2': {'name': 'pressure_tent'}} # 'pg2': {'name': 'pressure_grad_tent'}} self.fileDictTentPDiff = {'p2D': {'name': 'pressure_tent_diff'}} # 'pg2D': {'name': 'pressure_grad_tent_diff'}} self.fileDictLDSG = {'ldsg': {'name': 'ldsg'}, 'ldsg2': {'name': 'ldsg_tent'}} self.fileDictWSS = {'wss': {'name': 'wss'}, } # lists of functionals and other scalar output data self.time_list = [] # list of times, when error is measured (used in report) self.second_list = [] self.listDict = {} # list of fuctionals # dictionary of data lists {list, name, abbreviation, add scaled row to report} # normalisation coefficients (time-independent) are added to some lists to be used in normalized data series # coefficients are lists (updated during initialisation, so we cannot use float type) # coefs are equal to average of respective value of analytic solution # norm lists (time-dependent normalisation coefficients) are added to some lists to be used in relative data # series (to remove natural pulsation of error due to change in volume flow rate) # slist - lists for cycle-averaged values # L2(0) means L2 difference of pressures taken with zero average self.listDict = { 'd': {'list': [], 'name': 'corrected velocity L2 divergence', 'abrev': 'DC', 'scale': self.scale_factor, 'slist': []}, 'd2': {'list': [], 'name': 'tentative velocity L2 divergence', 'abrev': 'DT', 'scale': self.scale_factor, 'slist': []}, 'pg': {'list': [], 'name': 'computed pressure gradient', 'abrev': 'PG', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor}, 'pg2': {'list': [], 'name': 'computed pressure tent gradient', 'abrev': 'PTG', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor}, # 'dsg-l': {'list': [], 'name': 'div(2sym(grad(u))-laplace(u))', 'abrev': 'DSG-L'}, # div(2sym(grad(u))-laplace(u)) # 'dgt': {'list': [], 'name': 'div(transpose(grad(u)))', 'abrev': 'DGT'}, # div(transpose(grad(u))) } if self.has_analytic_solution: self.listDict.update({ 'u_L2': {'list': [], 'name': 'corrected velocity L2 error', 'abrev': 'CE_L2', 'scale': self.scale_factor, 'relative': 'av_norm_L2', 'slist': [], 'norm': self.vel_normalization_factor}, 'u2L2': {'list': [], 'name': 'tentative velocity L2 error', 'abrev': 'TE_L2', 'scale': self.scale_factor, 'relative': 'av_norm_L2', 'slist': [], 'norm': self.vel_normalization_factor}, 'u_L2test': {'list': [], 'name': 'test corrected L2 velocity error', 'abrev': 'TestCE_L2', 'scale': self.scale_factor}, 'u2L2test': {'list': [], 'name': 'test tentative L2 velocity error', 'abrev': 'TestTE_L2', 'scale': self.scale_factor}, 'u_H1': {'list': [], 'name': 'corrected velocity H1 error', 'abrev': 'CE_H1', 'scale': self.scale_factor, 'relative': 'av_norm_H1', 'slist': []}, 'u2H1': {'list': [], 'name': 'tentative velocity H1 error', 'abrev': 'TE_H1', 'scale': self.scale_factor, 'relative': 'av_norm_H1', 'slist': []}, 'u_H1test': {'list': [], 'name': 'test corrected H1 velocity error', 'abrev': 'TestCE_H1', 'scale': self.scale_factor}, 'u2H1test': {'list': [], 'name': 'test tentative H1 velocity error', 'abrev': 'TestTE_H1', 'scale': self.scale_factor}, 'apg': {'list': [], 'name': 'analytic pressure gradient', 'abrev': 'APG', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor}, 'av_norm_L2': {'list': [], 'name': 'analytic velocity L2 norm', 'abrev': 'AVN_L2'}, 'av_norm_H1': {'list': [], 'name': 'analytic velocity H1 norm', 'abrev': 'AVN_H1'}, 'ap_norm': {'list': [], 'name': 'analytic pressure norm', 'abrev': 'APN'}, 'p': {'list': [], 'name': 'pressure L2(0) error', 'abrev': 'PE', 'scale': self.scale_factor, 'slist': [], 'norm': self.p_normalization_factor}, 'pgE': {'list': [], 'name': 'computed pressure gradient error', 'abrev': 'PGE', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor, 'slist': []}, 'pgEA': {'list': [], 'name': 'computed absolute pressure gradient error', 'abrev': 'PGEA', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor}, 'p2': {'list': [], 'name': 'pressure tent L2(0) error', 'abrev': 'PTE', 'scale': self.scale_factor, 'slist': [], 'norm': self.p_normalization_factor}, 'pgE2': {'list': [], 'name': 'computed tent pressure tent gradient error', 'abrev': 'PTGE', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor, 'slist': []}, 'pgEA2': {'list': [], 'name': 'computed absolute pressure tent gradient error', 'abrev': 'PTGEA', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor} }) # parse arguments self.nu_factor = args.nu self.onset = args.onset self.onset_factor = 0 self.doSave = False self.saveOnlyVel = False self.doSaveDiff = False self.save_nth = args.savespace option = args.save if option == 'doSave' or option == 'diff' or option == 'only_vel': self.doSave = True if option == 'diff': self.doSaveDiff = True info('Saving velocity differences.') if option == 'only_vel': self.saveOnlyVel = True info('Saving only velocity profiles.') info('Saving solution ON.') elif option == 'noSave': self.doSave = False info('Saving solution OFF.') self.doErrControl = None self.testErrControl = False if args.error == "noEC": self.doErrControl = False info("Error control omitted") else: self.doErrControl = True if args.error == "test": self.testErrControl = True info("Error control in testing mode") else: info("Error control on") self.str_dir_name = "%s_%s_results" % (self.problem_code, metadata['name']) self.metadata['dir'] = self.str_dir_name # create directory, needed because of using "with open(..." construction later if not os.path.exists(self.str_dir_name) and self.MPI_rank == 0: os.mkdir(self.str_dir_name) @staticmethod def setup_parser_options(parser): parser.add_argument('-e', '--error', help='Error control mode', choices=['doEC', 'noEC', 'test'], default='doEC') parser.add_argument('-S', '--save', help='Save solution mode', choices=['doSave', 'noSave', 'diff', 'only_vel'], default='noSave') parser.add_argument('--savespace', help='save only n-th step in first cycle', type=int, default=1) # doSave: create .xdmf files with velocity, pressure, divergence # diff: save also difference vel-sol # noSave: do not create .xdmf files with velocity, pressure, divergence parser.add_argument('--nu', help='kinematic viscosity factor', type=float, default=1.0) parser.add_argument('--onset', help='boundary condition onset length', type=float, default=0.0) parser.add_argument('--ldsg', help='save laplace(u) - div(2sym(grad(u))) difference', action='store_true') parser.add_argument('--wss', help='compute wall shrear stress', action='store_true') @staticmethod def loadMesh(mesh): f = HDF5File(mpi_comm_world(), 'meshes/'+mesh+'.hdf5', 'r') mesh = Mesh() f.read(mesh, 'mesh', False) facet_function = MeshFunction("size_t", mesh) f.read(facet_function, 'facet_function') return mesh, facet_function def initialize(self, V, Q, PS, D): self.vSpace = V self.divSpace = D self.pSpace = Q self.solutionSpace = V self.vFunction = Function(V) self.divFunction = Function(D) self.pFunction = Function(Q) self.volume = assemble(interpolate(Expression("1.0"), Q) * dx) if self.doSave: # self.pgSpace = VectorFunctionSpace(mesh, "DG", 0) # self.pgFunction = Function(self.pgSpace) self.initialize_xdmf_files() self.stepsInSecond = int(round(1.0 / self.metadata['dt'])) info('stepsInSecond = %d' % self.stepsInSecond) def initialize_xdmf_files(self): info(' Initializing output files.') # for creating paraview scripts self.metadata['filename_base'] = self.problem_code + '_' + self.metadata['name'] # assemble file dictionary if self.doSaveDiff: self.fileDict.update(self.fileDictDiff) if self.metadata['hasTentativeV']: self.fileDict.update(self.fileDictTent) if self.doSaveDiff: self.fileDict.update(self.fileDictTentDiff) if self.metadata['hasTentativeP']: self.fileDict.update(self.fileDictTentP) if self.doSaveDiff: self.fileDict.update(self.fileDictTentPDiff) if self.args.ldsg: self.fileDict.update(self.fileDictLDSG) if self.args.wss: self.fileDict.update(self.fileDictWSS) # create files for key, value in self.fileDict.iteritems(): value['file'] = XDMFFile(mpi_comm_world(), self.str_dir_name + "/" + self.problem_code + '_' + self.metadata['name'] + value['name'] + ".xdmf") value['file'].parameters['rewrite_function_mesh'] = False # saves lots of space (for use with static mesh) # method for saving divergence (ensuring, that it will be one time line in ParaView) def save_div(self, is_tent, field): self.tc.start('div') self.divFunction.assign(project(div(field), self.divSpace)) self.fileDict['d2' if is_tent else 'd']['file'] << self.divFunction self.tc.end('div') def compute_div(self, is_tent, velocity): self.tc.start('divNorm') div_list = self.listDict['d2' if is_tent else 'd']['list'] div_list.append(norm(velocity, 'Hdiv0')) if self.isWholeSecond: self.listDict['d2' if is_tent else 'd']['slist'].append( sum([i*i for i in div_list[self.N0:self.N1]])/self.stepsInSecond) self.tc.end('divNorm') # method for saving velocity (ensuring, that it will be one time line in ParaView) def save_vel(self, is_tent, field, t): self.vFunction.assign(field) self.fileDict['u2' if is_tent else 'u']['file'] << self.vFunction if self.doSaveDiff: self.vFunction.assign((1.0 / self.vel_normalization_factor[0]) * (field - self.solution)) self.fileDict['u2D' if is_tent else 'uD']['file'] << self.vFunction if self.args.ldsg: # info(div(2.*sym(grad(field))-grad(field)).ufl_shape) form = div(2.*sym(grad(field))-grad(field)) self.pFunction.assign(project(sqrt_ufl(inner(form, form)), self.pSpace)) self.fileDict['ldsg2' if is_tent else 'ldsg']['file'] << self.pFunction # self.vFunction.assign(project(div(2.*sym(grad(field))-grad(field)), self.vSpace)) # self.fileDict['ldsg2' if is_tent else 'ldsg']['file'] << self.vFunction def compute_err(self, is_tent, velocity, t): if self.doErrControl and self.has_analytic_solution: er_list_L2 = self.listDict['u2L2' if is_tent else 'u_L2']['list'] er_list_H1 = self.listDict['u2H1' if is_tent else 'u_H1']['list'] self.tc.start('errorV') errorL2_sq = assemble(inner(velocity - self.solution, velocity - self.solution) * dx) # faster than errornorm errorH1seminorm_sq = assemble(inner(grad(velocity - self.solution), grad(velocity - self.solution)) * dx) # faster than errornorm info(' H1 seminorm error: %f' % sqrt(errorH1seminorm_sq)) errorL2 = sqrt(errorL2_sq) errorH1 = sqrt(errorL2_sq + errorH1seminorm_sq) info(" Relative L2 error in velocity = %f" % (errorL2 / self.analytic_v_norm_L2)) self.last_error = errorH1 / self.analytic_v_norm_H1 self.last_status_functional = self.last_error info(" Relative H1 error in velocity = %f" % self.last_error) er_list_L2.append(errorL2) er_list_H1.append(errorH1) self.tc.end('errorV') if self.testErrControl: er_list_test_H1 = self.listDict['u2H1test' if is_tent else 'u_H1test']['list'] er_list_test_L2 = self.listDict['u2L2test' if is_tent else 'u_L2test']['list'] self.tc.start('errorVtest') er_list_test_L2.append(errornorm(velocity, self.solution, norm_type='L2', degree_rise=0)) er_list_test_H1.append(errornorm(velocity, self.solution, norm_type='H1', degree_rise=0)) self.tc.end('errorVtest') if self.isWholeSecond: self.listDict['u2L2' if is_tent else 'u_L2']['slist'].append( sqrt(sum([i*i for i in er_list_L2[self.N0:self.N1]])/self.stepsInSecond)) self.listDict['u2H1' if is_tent else 'u_H1']['slist'].append( sqrt(sum([i*i for i in er_list_H1[self.N0:self.N1]])/self.stepsInSecond)) # stopping criteria if self.last_error > self.divergence_treshold: raise RuntimeError('STOPPED: Failed divergence test!') def averaging_pressure(self, pressure): self.tc.start('averageP') # averaging pressure (substract average) p_average = assemble((1.0/self.volume) * pressure * dx) info('Average pressure: %f' % p_average) p_average_function = interpolate(Expression("p", p=p_average), self.pSpace) # info(p_average_function, pressure, pressure_Q) pressure.assign(pressure - p_average_function) self.tc.end('averageP') def save_pressure(self, is_tent, pressure): self.tc.start('saveP') self.fileDict['p2' if is_tent else 'p']['file'] << pressure # pg = project((1.0 / self.pg_normalization_factor[0]) * grad(pressure), self.pgSpace) # NT normalisation factor defined only in Womersley # self.pgFunction.assign(pg) # self.fileDict['pg2' if is_tent else 'pg'][0] << self.pgFunction self.tc.end('saveP') def get_boundary_conditions(self, use_pressure_BC, v_space, p_space): pass def get_initial_conditions(self, function_list): """ :param function_list: [{'type': 'v'/'p', 'time':-0.1},...] :return: velocities and pressures in selected times """ pass def get_v_solution(self, t): pass def get_p_solution(self, t): pass def update_time(self, actual_time, step_number): self.actual_time = actual_time self.step_number = step_number self.time_list.append(self.actual_time) if self.onset < 0.001 or self.actual_time > self.onset: self.onset_factor = 1. else: self.onset_factor = (1. - cos(pi * actual_time / self.onset))*0.5 info('Onset factor: %f' % self.onset_factor) # save only n-th step in first second if self.doSave: if self.save_nth == 1 or actual_time > (1. - self.metadata['dt']/2.) or self.step_number % self.save_nth == 0: self.save_this_step = True else: self.save_this_step = False def compute_functionals(self, velocity, pressure, t): if self.args.wss: info('Computing stress tensor') I = Identity(velocity.geometric_dimension()) T = TensorFunctionSpace(self.mesh, 'Lagrange', 1) stress = project(-pressure*I + 2*sym(grad(velocity)), T) info('Generating boundary mesh') wall_mesh = BoundaryMesh(self.mesh, 'exterior') # wall_mesh = SubMesh(self.mesh, self.facet_function, 1) # QQ why does not work? # plot(wall_mesh, interactive=True) info(' Boundary mesh geometric dim: %d' % wall_mesh.geometry().dim()) info(' Boundary mesh topologic dim: %d' % wall_mesh.topology().dim()) info('Projecting stress to boundary mesh') Tb = TensorFunctionSpace(wall_mesh, 'Lagrange', 1) stress_b = interpolate(stress, Tb) self.fileDict['wss']['file'] << stress_b if False: # does not work info('Computing WSS') n = FacetNormal(wall_mesh) info(stress_b, True) # wss = stress_b*n - inner(stress_b*n, n)*n wss = dot(stress_b, n) - inner(dot(stress_b, n), n)*n # equivalent Vb = VectorFunctionSpace(wall_mesh, 'Lagrange', 1) Sb = FunctionSpace(wall_mesh, 'Lagrange', 1) # wss_func = project(wss, Vb) wss_norm = project(sqrt(inner(wss, wss)), Sb) plot(wss_norm, interactive=True) # following was used to test laplace and stress formulation differences # dsgml = sqrt(assemble((1./self.mesh_volume)*inner(div(2*sym(grad(velocity))-grad(velocity)), div(2*sym(grad(velocity))-grad(velocity)))*dx)) # dgt = sqrt(assemble((1./self.mesh_volume)*inner(div(transpose(grad(velocity))), div(transpose(grad(velocity))))*dx)) # self.listDict['dsg-l']['list'].append(dsgml) # self.listDict['dgt']['list'].append(dgt) def compute_outflow(self, velocity): out = assemble(inner(velocity, self.normal)*self.get_outflow_measure_form()) return out def get_outflow_measures(self): pass def get_outflow_measure_form(self): pass def get_metadata_to_save(self): return str(cPickle.dumps(self.metadata)).replace('\n', '$') def report(self): total = toc() md = self.metadata # compare errors measured by assemble and errornorm # TODO implement generally (if listDict[fcional]['testable']) # if self.testErrControl: # for e in [[self.listDict['u_L2']['list'], self.listDict['u_L2test']['list'], 'L2'], # [self.listDict['u_H1']['list'], self.listDict['u_H1test']['list'], 'H1']]: # print('test ', e[2], sum([abs(e[0][i]-e[1][i]) for i in range(len(self.time_list))])) # report error norm, norm of div, and pressure gradients for individual time steps with open(self.str_dir_name + "/report_time_lines.csv", 'w') as reportFile: report_writer = csv.writer(reportFile, delimiter=';', escapechar='\\', quoting=csv.QUOTE_NONE) # report_writer.writerow(self.problem.get_metadata_to_save()) report_writer.writerow(["name", "what", "time"] + self.time_list) for key in self.listDict: l = self.listDict[key] if l['list']: abrev = l['abrev'] report_writer.writerow([md['name'], l['name'], abrev] + l['list']) if 'scale' in l: temp_list = [i/l['scale'][0] for i in l['list']] report_writer.writerow([md['name'], "scaled " + l['name'], abrev+"s"] + temp_list + ['scale factor:' + str(l['scale'])]) if 'norm' in l: if l['norm']: temp_list = [i/l['norm'][0] for i in l['list']] report_writer.writerow([md['name'], "normalized " + l['name'], abrev+"n"] + temp_list) else: info('Norm missing:' + str(l)) l['normalized_list_sec'] = [] if 'relative' in l: norm_list = self.listDict[l['relative']]['list'] temp_list = [l['list'][i]/norm_list[i] for i in range(0, len(l['list']))] self.listDict[key]['relative_list'] = temp_list report_writer.writerow([md['name'], "relative " + l['name'], abrev+"r"] + temp_list) # report error norm, norm of div, and pressure gradients averaged over seconds with open(self.str_dir_name + "/report_seconds.csv", 'w') as reportFile: report_writer = csv.writer(reportFile, delimiter=';', escapechar='|', quoting=csv.QUOTE_NONE) # report_writer.writerow(self.problem.get_metadata_to_save()) report_writer.writerow(["name", "what", "time"] + self.second_list) for key in self.listDict.iterkeys(): l = self.listDict[key] if 'slist' in l: abrev = l['abrev'] value = l['slist'] report_writer.writerow([md['name'], l['name'], abrev] + value) if 'scale' in l: temp_list = [i/l['scale'][0] for i in value] report_writer.writerow([md['name'], "scaled " + l['name'], abrev+"s"] + temp_list) if 'norm' in l: if l['norm']: temp_list = [i/l['norm'][0] for i in value] l['normalized_list_sec'] = temp_list report_writer.writerow([md['name'], "normalized " + l['name'], abrev+"n"] + temp_list) else: info('Norm missing:' + str(l)) l['normalized_list_sec'] = [] if 'relative_list' in l: temp_list = [] # info('relative second list of'+ str(l['abrev'])) for sec in self.second_list: N0 = (sec-1)*self.stepsInSecond N1 = sec*self.stepsInSecond # info([sec, N0, N1]) temp_list.append(sqrt(sum([i*i for i in l['relative_list'][N0:N1]])/float(self.stepsInSecond))) l['relative_list_sec'] = temp_list report_writer.writerow([md['name'], "relative " + l['name'], abrev+"r"] + temp_list) header_row = ["name", 'metadata', "totalTimeHours"] data_row = [md['name'], self.get_metadata_to_save(), total / 3600.0] for key in ['u_L2', 'u_H1', 'u_H1w', 'p', 'u2L2', 'u2H1', 'u2H1w', 'p2', 'pgE', 'pgE2', 'd', 'd2', 'force_wall']: if key in self.listDict: l = self.listDict[key] header_row += ['last_cycle_'+l['abrev']] data_row += [l['slist'][-1]] if l['slist'] else [0] if 'relative_list_sec' in l and l['relative_list_sec']: header_row += ['last_cycle_'+l['abrev']+'r'] data_row += [l['relative_list_sec'][-1]] elif key in ['p', 'p2']: header_row += ['last_cycle_'+l['abrev']+'n'] data_row += [l['normalized_list_sec'][-1]] if l['normalized_list_sec'] else [0] # report without header with open(self.str_dir_name + "/report.csv", 'w') as reportFile: report_writer = csv.writer(reportFile, delimiter=';', escapechar='|', quoting=csv.QUOTE_NONE) report_writer.writerow(data_row) # report with header with open(self.str_dir_name + "/report_h.csv", 'w') as reportFile: report_writer = csv.writer(reportFile, delimiter=';', escapechar='|', quoting=csv.QUOTE_NONE) report_writer.writerow(header_row) report_writer.writerow(data_row) # report time cotrol with open(self.str_dir_name + "/report_timecontrol.csv", 'w') as reportFile: self.tc.report(reportFile, self.metadata['name']) self.remove_status_file() # create file showing all was done well f = open(md['name'] + "_OK.report", "w") f.close() def report_fail(self, t): print("Runtime error:", sys.exc_info()[1]) print("Traceback:") traceback.print_tb(sys.exc_info()[2]) f = open(self.metadata['name'] + "_failed_at_%5.3f.report" % t, "w") f.write(traceback.format_exc()) f.close() self.remove_status_file() def write_status_file(self, t): self.tc.start('status') f = open(self.metadata['name'] + ".run", "w") progress = t/self.metadata['time'] f.write('t = %5.3f (dt=%3dms)\nprogress = %3.0f %%\n%s = %5.3f\n' % (t, self.metadata['dt_ms'], 100*progress, self.status_functional_str, self.last_status_functional)) f.close() self.tc.end('status') def remove_status_file(self): if self.MPI_rank == 0: try: os.remove(self.metadata['name'] + ".run") except OSError: info('.run file probably not created')
def compute_time_errors(problem, MethodClass, mesh_sizes, Dt): mesh_generator, solution, f, mu, rho, cell_type = problem() # Compute the problem errors = { "u": numpy.empty((len(mesh_sizes), len(Dt))), "p": numpy.empty((len(mesh_sizes), len(Dt))), } for k, mesh_size in enumerate(mesh_sizes): info("") info("") with Message("Computing for mesh size {}...".format(mesh_size)): mesh = mesh_generator(mesh_size) # Define all expression with `domain`, see # <https://bitbucket.org/fenics-project/ufl/issues/96>. # # Translate data into FEniCS expressions. sol_u = Expression( (ccode(solution["u"]["value"][0]), ccode(solution["u"]["value"][1])), degree=_truncate_degree(solution["u"]["degree"]), t=0.0, domain=mesh, ) sol_p = Expression( ccode(solution["p"]["value"]), degree=_truncate_degree(solution["p"]["degree"]), t=0.0, domain=mesh, ) fenics_rhs0 = Expression( (ccode(f["value"][0]), ccode(f["value"][1])), degree=_truncate_degree(f["degree"]), t=0.0, mu=mu, rho=rho, domain=mesh, ) # Deep-copy expression to be able to provide f0, f1 for the # Dirichlet boundary conditions later on. fenics_rhs1 = Expression( fenics_rhs0.cppcode, degree=_truncate_degree(f["degree"]), t=0.0, mu=mu, rho=rho, domain=mesh, ) # Create initial states. W = VectorFunctionSpace(mesh, "CG", 2) P = FunctionSpace(mesh, "CG", 1) p0 = Expression( sol_p.cppcode, degree=_truncate_degree(solution["p"]["degree"]), t=0.0, domain=mesh, ) mesh_area = assemble(1.0 * dx(mesh)) method = MethodClass( time_step_method="backward euler", # time_step_method='crank-nicolson', # stabilization=None # stabilization='SUPG' ) u1 = Function(W) p1 = Function(P) err_p = Function(P) divu1 = Function(P) for j, dt in enumerate(Dt): # Prepare previous states for multistepping. u = { 0: Expression( sol_u.cppcode, degree=_truncate_degree(solution["u"]["degree"]), t=0.0, cell=cell_type, ) } sol_u.t = dt u_bcs = [DirichletBC(W, sol_u, "on_boundary")] sol_p.t = dt # p_bcs = [DirichletBC(P, sol_p, 'on_boundary')] p_bcs = [] fenics_rhs0.t = 0.0 fenics_rhs1.t = dt u1, p1 = method.step( Constant(dt), u, p0, W, P, u_bcs, p_bcs, Constant(rho), Constant(mu), f={0: fenics_rhs0, 1: fenics_rhs1}, verbose=False, tol=1.0e-10, ) sol_u.t = dt sol_p.t = dt errors["u"][k][j] = errornorm(sol_u, u1) # The pressure is only determined up to a constant which makes # it a bit harder to define what the error is. For our # purposes, choose an alpha_0\in\R such that # # alpha0 = argmin ||e - alpha||^2 # # with e := sol_p - p. # This alpha0 is unique and explicitly given by # # alpha0 = 1/(2|Omega|) \int (e + e*) # = 1/|Omega| \int Re(e), # # i.e., the mean error in \Omega. alpha = +assemble(sol_p * dx(mesh)) - assemble(p1 * dx(mesh)) alpha /= mesh_area # We would like to perform # p1 += alpha. # To avoid creating a temporary function every time, assume # that p1 lives in a function space where the coefficients # represent actual function values. This is true for CG # elements, for example. In that case, we can just add any # number to the vector of p1. p1.vector()[:] += alpha errors["p"][k][j] = errornorm(sol_p, p1) show_plots = False if show_plots: plot(p1, title="p1", mesh=mesh) plot(sol_p, title="sol_p", mesh=mesh) err_p.vector()[:] = p1.vector() sol_interp = interpolate(sol_p, P) err_p.vector()[:] -= sol_interp.vector() # plot(sol_p - p1, title='p1 - sol_p', mesh=mesh) plot(err_p, title="p1 - sol_p", mesh=mesh) # r = SpatialCoordinate(mesh)[0] # divu1 = 1 / r * (r * u1[0]).dx(0) + u1[1].dx(1) divu1.assign(project(u1[0].dx(0) + u1[1].dx(1), P)) plot(divu1, title="div(u1)") return errors
num_steps = np.rint(Tend / float(dt)) # Generate mesh mesh = Mesh("./../../meshes/circle_0.xml") n = nx while n > 1: mesh = refine(mesh) n /= 2 output_field = XDMFFile(mesh.mpi_comm(), outdir + "psi_h_nx" + str(nx) + ".xdmf") # Velocity and initial condition V = VectorFunctionSpace(mesh, "DG", 3) uh = Function(V) uh.assign(Expression(("-Uh*x[1]", "Uh*x[0]"), Uh=Uh, degree=3)) psi0_expression = GaussianPulse(center=(xc, yc), sigma=float(sigma), U=[Uh, Uh], time=0.0, height=1.0, degree=3) # Generate particles x = RandomCircle(Point(x0, y0), r).generate([pres, pres]) s = np.zeros((len(x), 1), dtype=np.float_) # Initialize particles with position x and scalar property s at the mesh p = particles(x, [s], mesh) property_idx = 1 # Scalar quantity is stored at slot 1
# Advective velocity # Swirling deformation advection (see LeVeque) ux = 'pow(sin(pi*x[0]), 2) * sin(2*pi*x[1])' vy = '-pow(sin(pi*x[1]), 2) * sin(2*pi*x[0])' gt_plus = '0.5 * cos(pi*t)' gt_min = '-0.5 * cos(pi*t)' u_expr = Expression((ux+'*'+gt_min, vy+'*'+gt_min), degree=2, t=0.) u_expre_neg = Expression((ux+'*'+gt_plus, vy+'*'+gt_plus), degree=2, t=0.) # Mesh velocity umesh = Function(Vcg) # Advective velocity uh = Function(V) uh.assign(u_expr) # Total velocity uadvect = uh - umesh # Now throw in the particles x = RandomRectangle(Point(xmin, ymin), Point(xmax, ymax)).generate([pres, pres]) s = assign_particle_values(x, CosineHill(radius=0.25, center=[0.25, 0.5], amplitude=1.0, degree=1)) p = particles(x, [s], mesh) # Define projections problem FuncSpace_adv = {'FuncSpace_local': Q_Rho, 'FuncSpace_lambda': T_1, 'FuncSpace_bar': Qbar} FormsPDE = FormsPDEMap(mesh, FuncSpace_adv, beta_map=Constant(1e-8)) forms_pde = FormsPDE.forms_theta_linear(phih0, uadvect, dt, Constant(1.0), zeta=Constant(0.), h=Constant(0.))
def solve_fixed_point(mesh, W_element, P_element, Q_element, u0, p0, theta0, kappa, rho, mu, cp, g, extra_force, heat_source, u_bcs, p_bcs, theta_dirichlet_bcs, theta_neumann_bcs, my_dx, my_ds, max_iter, tol): # Solve the coupled heat-Stokes equation approximately. Do this # iteratively by solving the heat equation, then solving Stokes with the # updated heat, the heat equation with the updated velocity and so forth # until the change is 'small'. WP = FunctionSpace(mesh, MixedElement([W_element, P_element])) Q = FunctionSpace(mesh, Q_element) # Initialize functions. up0 = Function(WP) u0, p0 = up0.split() theta1 = Function(Q) for _ in range(max_iter): heat_problem = heat.Heat(Q, kappa=kappa, rho=rho(theta0), cp=cp, convection=u0, source=heat_source, dirichlet_bcs=theta_dirichlet_bcs, neumann_bcs=theta_neumann_bcs, my_dx=my_dx, my_ds=my_ds) theta1.assign(heat_problem.solve_stationary()) # Solve problem for velocity, pressure. f = rho(theta0) * g # coupling if extra_force: f += as_vector((extra_force[0], extra_force[1], 0.0)) # up1 = up0.copy() stokes.stokes_solve(up0, mu, u_bcs, p_bcs, f, my_dx=my_dx, tol=1.0e-10, verbose=False, maxiter=1000) # from dolfin import plot # plot(u0) # plot(theta0) theta_diff = errornorm(theta0, theta1) info('||theta - theta0|| = {:e}'.format(theta_diff)) # info('||u - u0|| = {:e}'.format(u_diff)) # info('||p - p0|| = {:e}'.format(p_diff)) # diff = theta_diff + u_diff + p_diff diff = theta_diff info('sum = {:e}'.format(diff)) # # Show the iterates. # plot(theta0, title='theta0') # plot(u0, title='u0') # interactive() # #exit() if diff < tol: break theta0.assign(theta1) # Create a *deep* copy of u0, p0, to be able to deal with them as # actually separate entities. u0, p0 = up0.split(deepcopy=True) return u0, p0, theta0
def solve(self, problem): self.problem = problem doSave = problem.doSave save_this_step = False onlyVel = problem.saveOnlyVel dt = self.metadata['dt'] nu = Constant(self.problem.nu) self.tc.init_watch('init', 'Initialization', True, count_to_percent=False) self.tc.init_watch('rhs', 'Assembled right hand side', True, count_to_percent=True) self.tc.init_watch('applybc1', 'Applied velocity BC 1st step', True, count_to_percent=True) self.tc.init_watch('applybc3', 'Applied velocity BC 3rd step', True, count_to_percent=True) self.tc.init_watch('applybcP', 'Applied pressure BC or othogonalized rhs', True, count_to_percent=True) self.tc.init_watch('assembleMatrices', 'Initial matrix assembly', False, count_to_percent=True) self.tc.init_watch('solve 1', 'Running solver on 1st step', True, count_to_percent=True) self.tc.init_watch('solve 2', 'Running solver on 2nd step', True, count_to_percent=True) self.tc.init_watch('solve 3', 'Running solver on 3rd step', True, count_to_percent=True) self.tc.init_watch('solve 4', 'Running solver on 4th step', True, count_to_percent=True) self.tc.init_watch('assembleA1', 'Assembled A1 matrix (without stabiliz.)', True, count_to_percent=True) self.tc.init_watch('assembleA1stab', 'Assembled A1 stabilization', True, count_to_percent=True) self.tc.init_watch('next', 'Next step assignments', True, count_to_percent=True) self.tc.init_watch('saveVel', 'Saved velocity', True) self.tc.start('init') # Define function spaces (P2-P1) mesh = self.problem.mesh self.V = VectorFunctionSpace(mesh, "Lagrange", 2) # velocity self.Q = FunctionSpace(mesh, "Lagrange", 1) # pressure self.PS = FunctionSpace( mesh, "Lagrange", 2) # partial solution (must be same order as V) self.D = FunctionSpace(mesh, "Lagrange", 1) # velocity divergence space problem.initialize(self.V, self.Q, self.PS, self.D) # Define trial and test functions u = TrialFunction(self.V) v = TestFunction(self.V) p = TrialFunction(self.Q) q = TestFunction(self.Q) n = FacetNormal(mesh) I = Identity(find_geometric_dimension(u)) # Initial conditions: u0 velocity at previous time step u1 velocity two time steps back p0 previous pressure [u1, u0, p0] = self.problem.get_initial_conditions([{ 'type': 'v', 'time': -dt }, { 'type': 'v', 'time': 0.0 }, { 'type': 'p', 'time': 0.0 }]) u_ = Function(self.V) # current tentative velocity u_cor = Function(self.V) # current corrected velocity p_ = Function( self.Q ) # current pressure or pressure help function from rotation scheme p_mod = Function( self.Q) # current modified pressure from rotation scheme # Define coefficients k = Constant(self.metadata['dt']) f = Constant((0, 0, 0)) # Define forms # step 1: Tentative velocity, solve to u_ u_ext = 1.5 * u0 - 0.5 * u1 # extrapolation for convection term # Stabilisation h = CellSize(mesh) if self.args.cbc_tau: # used in Simula cbcflow project tau = Constant(self.stabCoef) * h / (sqrt(inner(u_ext, u_ext)) + h) else: # proposed in R. Codina: On stabilized finite element methods for linear systems of # convection-diffusion-reaction equations. tau = Constant(self.stabCoef) * k * h**2 / ( 2 * nu * k + k * h * sqrt(DOLFIN_EPS + inner(u_ext, u_ext)) + h**2) # DOLFIN_EPS is added because of FEniCS bug that inner(u_ext, u_ext) can be negative when u_ext = 0 if self.use_full_SUPG: v1 = v + tau * 0.5 * dot(grad(v), u_ext) parameters['form_compiler']['quadrature_degree'] = 6 else: v1 = v def nonlinearity(function): if self.args.ema: return 2 * inner(dot(sym(grad(function)), u_ext), v1 ) * dx + inner(div(function) * u_ext, v1) * dx else: return inner(dot(grad(function), u_ext), v1) * dx def diffusion(fce): if self.useLaplace: return nu * inner(grad(fce), grad(v1)) * dx else: form = inner(nu * 2 * sym(grad(fce)), sym(grad(v1))) * dx if self.bcv == 'CDN': return form if self.bcv == 'LAP': return form - inner(nu * dot(grad(fce).T, n), v1 ) * problem.get_outflow_measure_form() if self.bcv == 'DDN': return form # additional term must be added to non-constant part def pressure_rhs(): if self.args.bc == 'outflow': return inner(p0, div(v1)) * dx else: return inner(p0, div(v1)) * dx - inner( p0 * n, v1) * problem.get_outflow_measure_form() a1_const = (1. / k) * inner(u, v1) * dx + diffusion(0.5 * u) a1_change = nonlinearity(0.5 * u) if self.bcv == 'DDN': # does not penalize influx for current step, only for the next one # this can lead to oscilation: # DDN correct next step, but then u_ext is OK so in next step DDN is not used, leading to new influx... # u and u_ext cannot be switched, min_value is nonlinear function a1_change += -0.5 * min_value(Constant(0.), inner( u_ext, n)) * inner(u, v1) * problem.get_outflow_measure_form() # NT works only with uflacs compiler L1 = (1. / k) * inner(u0, v1) * dx - nonlinearity( 0.5 * u0) - diffusion(0.5 * u0) + pressure_rhs() if self.bcv == 'DDN': L1 += 0.5 * min_value(0., inner(u_ext, n)) * inner( u0, v1) * problem.get_outflow_measure_form() # Non-consistent SUPG stabilisation if self.stabilize and not self.use_full_SUPG: # a1_stab = tau*inner(dot(grad(u), u_ext), dot(grad(v), u_ext))*dx a1_stab = 0.5 * tau * inner(dot(grad(u), u_ext), dot( grad(v), u_ext)) * dx(None, {'quadrature_degree': 6}) # optional: to use Crank Nicolson in stabilisation term following change of RHS is needed: # L1 += -0.5*tau*inner(dot(grad(u0), u_ext), dot(grad(v), u_ext))*dx(None, {'quadrature_degree': 6}) outflow_area = Constant(problem.outflow_area) need_outflow = Constant(0.0) if self.useRotationScheme: # Rotation scheme F2 = inner(grad(p), grad(q)) * dx + (1. / k) * q * div(u_) * dx else: # Projection, solve to p_ if self.forceOutflow and problem.can_force_outflow: info('Forcing outflow.') F2 = inner(grad(p - p0), grad(q)) * dx + (1. / k) * q * div(u_) * dx for m in problem.get_outflow_measures(): F2 += (1. / k) * (1. / outflow_area) * need_outflow * q * m else: F2 = inner(grad(p - p0), grad(q)) * dx + (1. / k) * q * div(u_) * dx a2, L2 = system(F2) # step 3: Finalize, solve to u_ if self.useRotationScheme: # Rotation scheme F3 = (1. / k) * inner(u - u_, v) * dx + inner(grad(p_), v) * dx else: F3 = (1. / k) * inner(u - u_, v) * dx + inner(grad(p_ - p0), v) * dx a3, L3 = system(F3) if self.useRotationScheme: # Rotation scheme: modify pressure F4 = (p - p0 - p_ + nu * div(u_)) * q * dx a4, L4 = system(F4) # Assemble matrices self.tc.start('assembleMatrices') A1_const = assemble( a1_const ) # must be here, so A1 stays one Python object during repeated assembly A1_change = A1_const.copy( ) # copy to get matrix with same sparse structure (data will be overwritten) if self.stabilize and not self.use_full_SUPG: A1_stab = A1_const.copy( ) # copy to get matrix with same sparse structure (data will be overwritten) A2 = assemble(a2) A3 = assemble(a3) if self.useRotationScheme: A4 = assemble(a4) self.tc.end('assembleMatrices') if self.solvers == 'direct': self.solver_vel_tent = LUSolver('mumps') self.solver_vel_cor = LUSolver('mumps') self.solver_p = LUSolver('mumps') if self.useRotationScheme: self.solver_rot = LUSolver('mumps') else: # NT 2016-1 KrylovSolver >> PETScKrylovSolver # not needed, chosen not to use hypre_parasails: # if self.prec_v == 'hypre_parasails': # in FEniCS 1.6.0 inaccessible using KrylovSolver class # self.solver_vel_tent = PETScKrylovSolver('gmres') # PETSc4py object # self.solver_vel_tent.ksp().getPC().setType('hypre') # PETScOptions.set('pc_hypre_type', 'parasails') # # this is global setting, but preconditioners for pressure solvers are set by their constructors # else: self.solver_vel_tent = PETScKrylovSolver( 'gmres', self.args.precV) # nonsymetric > gmres # cannot use 'ilu' in parallel self.solver_vel_cor = PETScKrylovSolver('cg', self.args.precVC) self.solver_p = PETScKrylovSolver( self.args.solP, self.args.precP) # almost (up to BC) symmetric > CG if self.useRotationScheme: self.solver_rot = PETScKrylovSolver('cg', 'hypre_amg') # setup Krylov solvers if self.solvers == 'krylov': # Get the nullspace if there are no pressure boundary conditions foo = Function( self.Q) # auxiliary vector for setting pressure nullspace if self.args.bc == 'nullspace': null_vec = Vector(foo.vector()) self.Q.dofmap().set(null_vec, 1.0) null_vec *= 1.0 / null_vec.norm('l2') self.null_space = VectorSpaceBasis([null_vec]) as_backend_type(A2).set_nullspace(self.null_space) # apply global options for Krylov solvers solver_options = { 'monitor_convergence': True, 'maximum_iterations': 10000, 'nonzero_initial_guess': True } # 'nonzero_initial_guess': True with solver.solve(A, u, b) means that # Solver will use anything stored in u as an initial guess for solver in [self.solver_vel_tent, self.solver_vel_cor, self.solver_rot, self.solver_p] if \ self.useRotationScheme else [self.solver_vel_tent, self.solver_vel_cor, self.solver_p]: for key, value in solver_options.items(): try: solver.parameters[key] = value except KeyError: info('Invalid option %s for KrylovSolver' % key) return 1 if self.args.solP == 'richardson': self.solver_p.parameters['monitor_convergence'] = False self.solver_vel_tent.parameters['relative_tolerance'] = 10**( -self.args.prv1) self.solver_vel_tent.parameters['absolute_tolerance'] = 10**( -self.args.pav1) self.solver_vel_cor.parameters['relative_tolerance'] = 10E-12 self.solver_vel_cor.parameters['absolute_tolerance'] = 10E-4 self.solver_p.parameters['relative_tolerance'] = 10**( -self.args.prp) self.solver_p.parameters['absolute_tolerance'] = 10**( -self.args.pap) if self.useRotationScheme: self.solver_rot.parameters['relative_tolerance'] = 10E-10 self.solver_rot.parameters['absolute_tolerance'] = 10E-10 if self.args.Vrestart > 0: self.solver_vel_tent.parameters['gmres'][ 'restart'] = self.args.Vrestart if self.args.solP == 'gmres' and self.args.Prestart > 0: self.solver_p.parameters['gmres'][ 'restart'] = self.args.Prestart # boundary conditions bcu, bcp = problem.get_boundary_conditions(self.args.bc == 'outflow', self.V, self.Q) self.tc.end('init') # Time-stepping info("Running of Incremental pressure correction scheme n. 1") ttime = self.metadata['time'] t = dt step = 1 # debug function if problem.args.debug_rot: plot_cor_v = Function(self.V) while t < (ttime + dt / 2.0): self.problem.update_time(t, step) if self.MPI_rank == 0: problem.write_status_file(t) if doSave: save_this_step = problem.save_this_step # assemble matrix (it depends on solution) self.tc.start('assembleA1') assemble( a1_change, tensor=A1_change ) # assembling into existing matrix is faster than assembling new one A1 = A1_const.copy() # we dont want to change A1_const A1.axpy(1, A1_change, True) self.tc.end('assembleA1') self.tc.start('assembleA1stab') if self.stabilize and not self.use_full_SUPG: assemble( a1_stab, tensor=A1_stab ) # assembling into existing matrix is faster than assembling new one A1.axpy(1, A1_stab, True) self.tc.end('assembleA1stab') # Compute tentative velocity step begin("Computing tentative velocity") self.tc.start('rhs') b = assemble(L1) self.tc.end('rhs') self.tc.start('applybc1') [bc.apply(A1, b) for bc in bcu] self.tc.end('applybc1') try: self.tc.start('solve 1') self.solver_vel_tent.solve(A1, u_.vector(), b) self.tc.end('solve 1') if save_this_step: self.tc.start('saveVel') problem.save_vel(True, u_) self.tc.end('saveVel') if save_this_step and not onlyVel: problem.save_div(True, u_) problem.compute_err(True, u_, t) problem.compute_div(True, u_) except RuntimeError as inst: problem.report_fail(t) return 1 end() if self.useRotationScheme: begin("Computing tentative pressure") else: begin("Computing pressure") if self.forceOutflow and problem.can_force_outflow: out = problem.compute_outflow(u_) info('Tentative outflow: %f' % out) n_o = -problem.last_inflow - out info('Needed outflow: %f' % n_o) need_outflow.assign(n_o) self.tc.start('rhs') b = assemble(L2) self.tc.end('rhs') self.tc.start('applybcP') [bc.apply(A2, b) for bc in bcp] if self.args.bc == 'nullspace': self.null_space.orthogonalize(b) self.tc.end('applybcP') try: self.tc.start('solve 2') self.solver_p.solve(A2, p_.vector(), b) self.tc.end('solve 2') except RuntimeError as inst: problem.report_fail(t) return 1 if self.useRotationScheme: foo = Function(self.Q) foo.assign(p_ + p0) if save_this_step and not onlyVel: problem.averaging_pressure(foo) problem.save_pressure(True, foo) else: foo = Function(self.Q) foo.assign(p_) # we do not want to change p_ by averaging if save_this_step and not onlyVel: problem.averaging_pressure(foo) problem.save_pressure(False, foo) end() begin("Computing corrected velocity") self.tc.start('rhs') b = assemble(L3) self.tc.end('rhs') if not self.args.B: self.tc.start('applybc3') [bc.apply(A3, b) for bc in bcu] self.tc.end('applybc3') try: self.tc.start('solve 3') self.solver_vel_cor.solve(A3, u_cor.vector(), b) self.tc.end('solve 3') problem.compute_err(False, u_cor, t) problem.compute_div(False, u_cor) except RuntimeError as inst: problem.report_fail(t) return 1 if save_this_step: self.tc.start('saveVel') problem.save_vel(False, u_cor) self.tc.end('saveVel') if save_this_step and not onlyVel: problem.save_div(False, u_cor) end() if self.useRotationScheme: begin("Rotation scheme pressure correction") self.tc.start('rhs') b = assemble(L4) self.tc.end('rhs') try: self.tc.start('solve 4') self.solver_rot.solve(A4, p_mod.vector(), b) self.tc.end('solve 4') except RuntimeError as inst: problem.report_fail(t) return 1 if save_this_step and not onlyVel: problem.averaging_pressure(p_mod) problem.save_pressure(False, p_mod) end() if problem.args.debug_rot: # save applied pressure correction (expressed as a term added to RHS of next tentative vel. step) # see comment next to argument definition plot_cor_v.assign(project(k * grad(nu * div(u_)), self.V)) problem.fileDict['grad_cor']['file'].write(plot_cor_v, t) # compute functionals (e. g. forces) problem.compute_functionals( u_cor, p_mod if self.useRotationScheme else p_, t, step) # Move to next time step self.tc.start('next') u1.assign(u0) u0.assign(u_cor) u_.assign( u_cor) # use corrected velocity as initial guess in first step if self.useRotationScheme: p0.assign(p_mod) else: p0.assign(p_) t = round(t + dt, 6) # round time step to 0.000001 step += 1 self.tc.end('next') info("Finished: Incremental pressure correction scheme n. 1") problem.report() return 0
def compute_time_errors(problem, method, mesh_sizes, Dt): mesh_generator, solution, f, mu, rho, cell_type = problem() # Translate data into FEniCS expressions. u = solution['u'] sol_u = Expression((ccode(u['value'][0]), ccode(u['value'][1])), degree=_truncate_degree(u['degree']), t=0.0) p = solution['p'] sol_p = Expression(ccode(p['value']), degree=_truncate_degree(p['degree']), t=0.0) # Deep-copy expression to be able to provide f0, f1 for the Dirichlet- # boundary conditions later on. fenics_rhs0 = Expression((ccode(f['value'][0]), ccode(f['value'][1])), degree=_truncate_degree(f['degree']), t=0.0, mu=mu, rho=rho) fenics_rhs1 = Expression(fenics_rhs0.cppcode, element=fenics_rhs0.ufl_element(), **fenics_rhs0.user_parameters) # Create initial states. p = Expression(sol_p.cppcode, degree=_truncate_degree(solution['p']['degree']), t=0.0, cell=cell_type) # Compute the problem errors = { 'u': numpy.empty((len(mesh_sizes), len(Dt))), 'p': numpy.empty((len(mesh_sizes), len(Dt))) } for k, mesh_size in enumerate(mesh_sizes): print() print() print('Computing for mesh size %r...' % mesh_size) mesh = mesh_generator(mesh_size) mesh_area = assemble(1.0 * dx(mesh)) W = VectorFunctionSpace(mesh, 'CG', 2) P = FunctionSpace(mesh, 'CG', 1) u1 = Function(W) p1 = Function(P) err_p = Function(P) divu1 = Function(P) for j, dt in enumerate(Dt): # Prepare previous states for multistepping. u_1 = Expression(sol_u.cppcode, degree=_truncate_degree(solution['u']['degree']), t=-dt, cell=cell_type) u_1 = project(u_1, W) u0 = Expression( sol_u.cppcode, degree=_truncate_degree(solution['u']['degree']), t=0.0, # t=0.5*dt, cell=cell_type) u0 = project(u0, W) sol_u.t = dt u_bcs = [DirichletBC(W, sol_u, 'on_boundary')] sol_p.t = dt p0 = project(p, P) p_bcs = [] fenics_rhs0.t = 0.0 fenics_rhs1.t = dt u1, p1 = method.step(Constant(dt), { -1: u_1, 0: u0 }, p0, u_bcs=u_bcs, p_bcs=p_bcs, rho=Constant(rho), mu=Constant(mu), f={ 0: fenics_rhs0, 1: fenics_rhs1 }, verbose=False, tol=1.0e-10) # plot(sol_u, mesh=mesh, title='u_sol') # plot(sol_p, mesh=mesh, title='p_sol') # plot(u1, title='u') # plot(p1, title='p') # from dolfin import div # plot(div(u1), title='div(u)') # plot(p1 - sol_p, title='p_h - p') # interactive() sol_u.t = dt sol_p.t = dt errors['u'][k][j] = errornorm(sol_u, u1) # The pressure is only determined up to a constant which makes # it a bit harder to define what the error is. For our # purposes, choose an alpha_0\in\R such that # # alpha0 = argmin ||e - alpha||^2 # # with e := sol_p - p. # This alpha0 is unique and explicitly given by # # alpha0 = 1/(2|Omega|) \int (e + e*) # = 1/|Omega| \int Re(e), # # i.e., the mean error in \Omega. alpha = (+assemble(sol_p * dx(mesh)) - assemble(p1 * dx(mesh))) alpha /= mesh_area # We would like to perform # p1 += alpha. # To avoid creating a temporary function every time, assume # that p1 lives in a function space where the coefficients # represent actual function values. This is true for CG # elements, for example. In that case, we can just add any # number to the vector of p1. p1.vector()[:] += alpha errors['p'][k][j] = errornorm(sol_p, p1) show_plots = False if show_plots: plot(p1, title='p1', mesh=mesh) plot(sol_p, title='sol_p', mesh=mesh) err_p.vector()[:] = p1.vector() sol_interp = interpolate(sol_p, P) err_p.vector()[:] -= sol_interp.vector() # plot(sol_p - p1, title='p1 - sol_p', mesh=mesh) plot(err_p, title='p1 - sol_p', mesh=mesh) # r = Expression('x[0]', degree=1, cell=triangle) # divu1 = 1 / r * (r * u1[0]).dx(0) + u1[1].dx(1) divu1.assign(project(u1[0].dx(0) + u1[1].dx(1), P)) plot(divu1, title='div(u1)') interactive() return errors
def compute_time_errors(problem, MethodClass, mesh_sizes, Dt): mesh_generator, solution, f, mu, rho, cell_type = problem() # Translate data into FEniCS expressions. sol_u = Expression((smp.printing.ccode(solution['u']['value'][0]), smp.printing.ccode(solution['u']['value'][1]) ), degree=_truncate_degree(solution['u']['degree']), t=0.0, cell=cell_type ) sol_p = Expression(smp.printing.ccode(solution['p']['value']), degree=_truncate_degree(solution['p']['degree']), t=0.0, cell=cell_type ) fenics_rhs0 = Expression((smp.printing.ccode(f['value'][0]), smp.printing.ccode(f['value'][1]) ), degree=_truncate_degree(f['degree']), t=0.0, mu=mu, rho=rho, cell=cell_type ) # Deep-copy expression to be able to provide f0, f1 for the Dirichlet- # boundary conditions later on. fenics_rhs1 = Expression(fenics_rhs0.cppcode, degree=_truncate_degree(f['degree']), t=0.0, mu=mu, rho=rho, cell=cell_type ) # Create initial states. p0 = Expression( sol_p.cppcode, degree=_truncate_degree(solution['p']['degree']), t=0.0, cell=cell_type ) # Compute the problem errors = {'u': numpy.empty((len(mesh_sizes), len(Dt))), 'p': numpy.empty((len(mesh_sizes), len(Dt))) } for k, mesh_size in enumerate(mesh_sizes): info('') info('') with Message('Computing for mesh size %r...' % mesh_size): mesh = mesh_generator(mesh_size) mesh_area = assemble(1.0 * dx(mesh)) W = VectorFunctionSpace(mesh, 'CG', 2) P = FunctionSpace(mesh, 'CG', 1) method = MethodClass(W, P, rho, mu, theta=1.0, #theta=0.5, stabilization=None #stabilization='SUPG' ) u1 = Function(W) p1 = Function(P) err_p = Function(P) divu1 = Function(P) for j, dt in enumerate(Dt): # Prepare previous states for multistepping. u = [Expression( sol_u.cppcode, degree=_truncate_degree(solution['u']['degree']), t=0.0, cell=cell_type ), # Expression( #sol_u.cppcode, #degree=_truncate_degree(solution['u']['degree']), #t=0.5*dt, #cell=cell_type #) ] sol_u.t = dt u_bcs = [DirichletBC(W, sol_u, 'on_boundary')] sol_p.t = dt #p_bcs = [DirichletBC(P, sol_p, 'on_boundary')] p_bcs = [] fenics_rhs0.t = 0.0 fenics_rhs1.t = dt method.step(dt, u1, p1, u, p0, u_bcs=u_bcs, p_bcs=p_bcs, f0=fenics_rhs0, f1=fenics_rhs1, verbose=False, tol=1.0e-10 ) sol_u.t = dt sol_p.t = dt errors['u'][k][j] = errornorm(sol_u, u1) # The pressure is only determined up to a constant which makes # it a bit harder to define what the error is. For our # purposes, choose an alpha_0\in\R such that # # alpha0 = argmin ||e - alpha||^2 # # with e := sol_p - p. # This alpha0 is unique and explicitly given by # # alpha0 = 1/(2|Omega|) \int (e + e*) # = 1/|Omega| \int Re(e), # # i.e., the mean error in \Omega. alpha = assemble(sol_p * dx(mesh)) \ - assemble(p1 * dx(mesh)) alpha /= mesh_area # We would like to perform # p1 += alpha. # To avoid creating a temporary function every time, assume # that p1 lives in a function space where the coefficients # represent actual function values. This is true for CG # elements, for example. In that case, we can just add any # number to the vector of p1. p1.vector()[:] += alpha errors['p'][k][j] = errornorm(sol_p, p1) show_plots = False if show_plots: plot(p1, title='p1', mesh=mesh) plot(sol_p, title='sol_p', mesh=mesh) err_p.vector()[:] = p1.vector() sol_interp = interpolate(sol_p, P) err_p.vector()[:] -= sol_interp.vector() #plot(sol_p - p1, title='p1 - sol_p', mesh=mesh) plot(err_p, title='p1 - sol_p', mesh=mesh) #r = Expression('x[0]', degree=1, cell=triangle) #divu1 = 1 / r * (r * u1[0]).dx(0) + u1[1].dx(1) divu1.assign(project(u1[0].dx(0) + u1[1].dx(1), P)) plot(divu1, title='div(u1)') interactive() return errors
def run(self): outdir = self.parameters['outdir'] savelag = self.parameters['savelag'] solver = self.solver stability = self.stability alpha = solver.alpha u = solver.u load_steps = self.load_steps alpha_old = Function(alpha.function_space()) self.time_data_i = [] stable = None; negev = -1; mineig = np.inf; iteration = 0 diff = alpha.copy(deepcopy=True) for it, load in enumerate(load_steps): self.load_param.t = load alpha_old.assign(alpha) print('') ColorPrint.print_warn('Solving load = {:.2f}'.format(load)) self.time_data_i, am_iter = solver.solve() diff.vector()[:] = alpha.vector() - alpha_old.vector() try: assert all(alpha.vector()[:]>=alpha_old.vector()[:]) except AssertionError: print('check alpha.vector()[:]>=alpha_old.vector()') try: assert all(solver.problem_alpha.lb.vector()[:]==alpha_old.vector()[:]) except AssertionError: print('check all(solver.problem_alpha.lb.vector()[:]==alpha_old.vector()[:])') if bool(strtobool(str(stability.parameters['checkstability']))): (stable, negev) = stability.solve(solver.problem_alpha.lb) ColorPrint.print_pass('Current state is{}stable'.format(' ' if stable else ' not ')) if hasattr(stability, 'eigs') and len(stability.eigs)>0 and min(stability.eigs)<0: pp.plot_eigenmodes(stability.eigendata, alpha, load, outdir) self.user_postprocess_stability(load) else: solver.update() alpha.copy(deepcopy = True) if stable: solver.update() elif not stable and not bool(strtobool(str(stability.parameters['continuation']))): solver.update() elif not stable and bool(strtobool(str(stability.parameters['continuation']))): while stable == False: adm_pert = np.where(np.array([e['en_diff'] for e in stability.eigendata]) < 0)[0] if len(adm_pert)==0: ColorPrint.print_warn('No admissible perturbations found') ColorPrint.print_pass('Continuing load program') break else: continuation_data = [] steepest = np.argmin([e['en_diff'] for e in stability.eigendata]) if self.parameters['perturbation_choice']=='first': mode = 0 elif self.parameters['perturbation_choice'] == 'steepest': mode = steepest elif isinstance(self.parameters['perturbation_choice'], int): mode = self.parameters['perturbation_choice'] perturbation_v = stability.eigendata[mode]['v_n'] perturbation_beta = stability.eigendata[mode]['beta_n'] hstar = stability.eigendata[mode]['hstar'] perturbation_v.rename('step displacement perturbation', 'step displacement perturbation') perturbation_beta.rename('step damage perturbation', 'step damage perturbation') ColorPrint.print_pass('Perturbation choice {}'.format(self.parameters['perturbation_choice'])) ColorPrint.print_pass('Perturbing current state with mode {} Delta E={:.5%} (estimated)'.format(mode, stability.eigendata[mode]['en_diff'])) ColorPrint.print_pass('...... chosen mode {} vs. steepest {} Delta E={:.5%} (estimated)'.format(mode, steepest, stability.eigendata[mode]['en_diff']/stability.eigendata[steepest]['en_diff'])) ColorPrint.print_pass('...... steepest descent mode {} Delta E={:.5%} (estimated)'.format(steepest,stability.eigendata[steepest]['en_diff'])) # perturb current state self.compile_continuation_data(load, iteration, perturbed=False) solver.alpha.copy(deepcopy=True) ColorPrint.print_pass('Perturbing current state, looking for stability, iteration {}'.format(iteration)) uval = u.vector()[:] + hstar * perturbation_v.vector()[:] aval = alpha.vector()[:] + hstar * perturbation_beta.vector()[:] alpha.vector().vec().ghostUpdate() u.vector().vec().ghostUpdate() self.time_data_i, am_iter = solver.solve() if self.file_con is not None: with self.file_con as f: f.write(alpha, iteration) f.write(u, iteration) self.compile_continuation_data(load, iteration, perturbed=True) ColorPrint.print_pass('Energy diff {}, rel thhreshold {}' .format(self.continuation_data_i["total_energy_diff"]/self.continuation_data_i["total_energy"], self.stability.parameters['cont_rtol'])) continuation_data.append(self.continuation_data_i) if np.mod(it, self.parameters['savelag']) == 0: continuation_data_pd=pd.DataFrame(continuation_data) continuation_data_pd.to_json(os.path.join(outdir + "/continuation_data.json")) if self.continuation_data_i["total_energy_diff"]/self.continuation_data_i["total_energy"] < - self.stability.parameters['cont_rtol']: ColorPrint.print_pass('Updating irreversibility') solver.update() else: ColorPrint.print_pass('Threshold not met, continuing load program') break (stable, negev) = stability.solve(alpha_old) if self.file_eig is not None: with self.file_eig as f: f.write(perturbation_beta, iteration) f.write(perturbation_v, iteration) iteration += 1 time_data_pd = self.compile_time_data(load) if np.mod(it, self.parameters['savelag']) == 0: time_data_pd.to_json(os.path.join(outdir + "/time_data.json")) ColorPrint.print_pass('written data to file {}'.format(str(os.path.join(outdir + "/time_data.json")))) if self.file_out is not None: with self.file_out as f: f.write(alpha, load) f.write(u, load) pp.plot_global_data(time_data_pd,load,outdir) self.user_postprocess_timestep(load) return time_data_pd
def step(self, dt, u1, p1, u, p0, u_bcs, p_bcs, f0=None, f1=None, verbose=True, tol=1.0e-10 ): # Some initial sanity checkups. assert dt > 0.0 # Define trial and test functions ui = Function(self.W) v = TestFunction(self.W) # Create functions # Define coefficients k = Constant(dt) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Compute tentative velocity step: # # F(u) = 0, # F(u) := rho (U0 + (u.\nabla)u) - mu \div(\nabla u) - f = 0. # with Message('Computing tentative velocity'): # TODO higher-order scheme for time integration # # For higher-order schemes, see # # A comparison of time-discretization/linearization approaches # for the incompressible Navier-Stokes equations; # Volker John, Gunar Matthies, Joachim Rang; # Comput. Methods Appl. Mech. Engrg. 195 (2006) 5995-6010; # <http://www.wias-berlin.de/people/john/ELECTRONIC_PAPERS/JMR06.CMAME.pdf>. # F1 = self.rho * inner((ui - u[0])/k, v) * dx if abs(self.theta) > DOLFIN_EPS: # Implicit terms. if f1 is None: raise RuntimeError('Implicit schemes need right-hand side ' 'at target step (f1).') F1 -= self.theta * _rhs_weak(ui, v, f1, self.rho, self.mu) if abs(1.0 - self.theta) > DOLFIN_EPS: # Explicit terms. if f0 is None: raise RuntimeError('Explicit schemes need right-hand side ' 'at current step (f0).') F1 -= (1.0 - self.theta) \ * _rhs_weak(u[0], v, f0, self.rho, self.mu) if p0: F1 += dot(grad(p0), v) * dx #if stabilization: # tau = stab.supg2(V.mesh(), # u_1, # mu/rho, # V.ufl_element().degree() # ) # R = rho*(ui - u_1)/k # if abs(theta) > DOLFIN_EPS: # R -= theta * _rhs_strong(ui, f1, rho, mu) # if abs(1.0-theta) > DOLFIN_EPS: # R -= (1.0-theta) * _rhs_strong(u_1, f0, rho, mu) # if p_1: # R += grad(p_1) # # TODO use u_1 or ui here? # F1 += tau * dot(R, grad(v)*u_1) * dx # Get linearization and solve nonlinear system. # If the scheme is fully explicit (theta=0.0), then the system is # actually linear and only one Newton iteration is performed. J = derivative(F1, ui) # What is a good initial guess for the Newton solve? # Three choices come to mind: # # (1) the previous solution u_1, # (2) the intermediate solution from the previous step ui_1, # (3) the solution of the semilinear system # (u.\nabla(u) -> u_1.\nabla(u)). # # Numerical experiments with the Karman vortex street show that the # order of accuracy is (1), (3), (2). Typical norms would look like # # ||u - u_1 || = 1.726432e-02 # ||u - ui_1|| = 2.720805e+00 # ||u - u_e || = 5.921522e-02 # # Hence, use u_1 as initial guess. ui.assign(u[0]) # Wrap the solution in a try-catch block to make sure we call end() # if necessary. #problem = NonlinearVariationalProblem(F1, ui, u_bcs, J) #solver = NonlinearVariationalSolver(problem) solve( F1 == 0, ui, bcs=u_bcs, J=J, solver_parameters={ #'nonlinear_solver': 'snes', 'nonlinear_solver': 'newton', 'newton_solver': { 'maximum_iterations': 5, 'report': True, 'absolute_tolerance': tol, 'relative_tolerance': 0.0, 'linear_solver': 'iterative', ## The nonlinear term makes the problem generally ## nonsymmetric. #'symmetric': False, # If the nonsymmetry is too strong, e.g., if u_1 is # large, then AMG preconditioning might not work very # well. 'preconditioner': 'ilu', #'preconditioner': 'hypre_amg', 'krylov_solver': {'relative_tolerance': tol, 'absolute_tolerance': 0.0, 'maximum_iterations': 1000, 'monitor_convergence': verbose} }}) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - with Message('Computing pressure correction'): # # The following is based on the update formula # # rho/dt (u_{n+1}-u*) + \nabla phi = 0 # # with # # phi = (p_{n+1} - p*) + chi*mu*div(u*) # # and div(u_{n+1})=0. One derives # # - \nabla^2 phi = rho/dt div(u_{n+1} - u*), # - n.\nabla phi = rho/dt n.(u_{n+1} - u*), # # In its weak form, this is # # \int \grad(phi).\grad(q) # = - rho/dt \int div(u*) q - rho/dt \int_Gamma n.(u_{n+1}-u*) q. # # If Dirichlet boundary conditions are applied to both u* and # u_{n+1} (the latter in the final step), the boundary integral # vanishes. # # Assume that on the boundary # L2 -= inner(n, rho/k (u_bcs - ui)) * q * ds # is zero. This requires the boundary conditions to be set for # ui as well as u_final. # This creates some problems if the boundary conditions are # supposed to remain 'free' for the velocity, i.e., no Dirichlet # conditions in normal direction. In that case, one needs to # specify Dirichlet pressure conditions. # rotational_form = False self._pressure_poisson(p1, p0, self.mu, ui, divu=Constant(self.rho/dt)*div(ui), p_bcs=p_bcs, rotational_form=rotational_form, tol=tol, verbose=verbose ) #plot(p - phi, title='p-phi') #plot(ui, title='u intermediate') #plot(f, title='f') ##plot(ui[1], title='u intermediate[1]') #plot(div(ui), title='div(u intermediate)') #plot(phi, title='phi') #interactive() # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Velocity correction. # U = U0 - dt/rho \nabla p. with Message('Computing velocity correction'): u2 = TrialFunction(self.W) a3 = inner(u2, v) * dx if p0: phi = Function(self.P) phi.assign(p1) phi -= p0 else: phi = p1 if rotational_form: phi += self.mu * div(ui) L3 = inner(ui, v) * dx \ - k/self.rho * inner(grad(phi), v) * dx solve(a3 == L3, u1, bcs=u_bcs, solver_parameters={ 'linear_solver': 'iterative', 'symmetric': True, 'preconditioner': 'jacobi', 'krylov_solver': {'relative_tolerance': tol, 'absolute_tolerance': 0.0, 'maximum_iterations': 100, 'monitor_convergence': verbose} }) #u = project(ui - k/rho * grad(phi), V) #print '||u||_div = %e' % norm(u, 'Hdiv0') #uu = TrialFunction(Q) #vv = TestFunction(Q) #div_u = Function(Q) #solve(uu*vv*dx == div(u)*vv*dx, div_u, # #bcs=DirichletBC(Q, 0.0, 'on_boundary') # ) #plot(div_u, title='div(u)') #interactive() return
# Set slotted disk psi0_expr = SlottedDisk(radius=rdisk, center=[xc, yc], width=rwidth, depth=0., degree=3, lb=lb, ub=ub) # Function space and velocity field W = FunctionSpace(mesh, 'DG', k) psi_h = Function(W) V = VectorFunctionSpace(mesh, 'DG', 3) uh = Function(V) uh.assign(Expression(('-Uh*x[1]', 'Uh*x[0]'), Uh=Uh, degree=3)) # Generate particles x = RandomCircle(Point(x0, y0), r).generate([pres, pres]) s = assign_particle_values(x, psi0_expr) p = particles(x, [s], mesh) # Initialize advection class, use RK3 scheme ap = advect_rk3(p, V, uh, 'closed') # Init projection lstsq_psi = l2projection(p, W, 1) # Do projection to get initial field lstsq_psi.project(psi_h.cpp_object(), lb, ub) AD = AddDelete(p, 10, 20, [psi_h], [1], [lb, ub])
class GeneralProblem(object): def __init__(self, args, tc, metadata): self.MPI_rank = MPI.rank(mpi_comm_world()) self.metadata = metadata # need to be specified in subclass init before calling this init self.problem_code = self.problem_code self.metadata['pcode'] = self.problem_code self.has_analytic_solution = self.has_analytic_solution self.metadata['hasAnalyticSolution'] = self.has_analytic_solution self.args = args # Time control self.tc = tc self.tc.init_watch('mesh', 'Imported mesh', True) self.tc.init_watch('saveP', 'Saved pressure', True) self.tc.init_watch('saveVel', 'Saved velocity', True) self.tc.init_watch('averageP', 'Averaged pressure', True) self.tc.init_watch('updateBC', 'Updated velocity BC', True, count_to_percent=True) self.tc.init_watch('div', 'Computed and saved divergence', True) self.tc.init_watch('divNorm', 'Computed norm of divergence', True) self.tc.init_watch('WSSinit', 'Initialized mesh for WSS', False) self.tc.init_watch('WSS', 'Computed and saved WSS', True) self.tc.init_watch('errorV', 'Computed velocity error', True) self.tc.init_watch('errorVtest', 'Computed velocity error test', True) # If it is sensible (and implemented) to force pressure gradient on outflow boundary # 1. set self.outflow_area in initialize # 2. implement self.compute_outflow and get_outflow_measures self.can_force_outflow = False self.outflow_area = None self.outflow_measures = [] # stopping criteria (for relative H1 velocity error norm) (if known) self.divergence_treshold = 10 # used for writing status files .run to monitor progress during computation: self.last_status_functional = 0.0 self.status_functional_str = 'to be defined in Problem class' # mesh and function objects self.normal = None self.mesh = None self.facet_function = None self.mesh_volume = 0 # must be specified in subclass (needed to compute pressure average) self.stepsInCycle = None self.vSpace = None self.vFunction = None self.divSpace = None self.divFunction = None self.pSpace = None # self.pgSpace = None # NT computing pressure gradient function not used (commented throughout code) self.pFunction = None # self.pgFunction = None self.solutionSpace = None self.solution = None # time related variables self.actual_time = 0.0 self.cycle_length = 1.0 self.step_number = 0 self.chosen_steps = [0.1, 0.125, 0.15, 0.16, 0.165, 0.166, 0.167, 0.17, 0.188, 0.189, 0.2] # must be specified in subclass # 0.166... is peak for real problem, 0.188 is peak for womersley profile # TODO chosen_steps should depend on problem (and inflow profile) self.distance_from_chosen_steps = 0.0 self.save_this_step = False self.isWholeSecond = None self.N1 = None self.N0 = None # lists used for storing normalization and scaling coefficients # (time independent, mainly used in womersley_cylinder) self.vel_normalization_factor = [] self.pg_normalization_factor = [] self.p_normalization_factor = [] self.scale_factor = [] self.analytic_v_norm_L2 = None self.analytic_v_norm_H1 = None self.analytic_v_norm_H1w = None self.last_error = None # for WSS generation metadata['hasWSS'] = (args.wss != 'none') metadata['WSSmethod'] = self.args.wss_method self.T = None self.Tb = None self.wall_mesh = None self.wall_mesh_oriented = None self.Vb = None self.Sb = None self.VDG = None self.R = None self.SDG = None self.nb = None # dictionaries for output XDMF files self.fileDict = {'u': {'name': 'velocity'}, 'p': {'name': 'pressure'}, # 'pg': {'name': 'pressure_grad'}, 'd': {'name': 'divergence'}} self.fileDictTent = {'u2': {'name': 'velocity_tent'}, 'd2': {'name': 'divergence_tent'}} self.fileDictDiff = {'uD': {'name': 'velocity_diff'}, 'pD': {'name': 'pressure_diff'}, 'pgD': {'name': 'pressure_grad_diff'}} self.fileDictTentDiff = {'u2D': {'name': 'velocity_tent_diff'}} self.fileDictTentP = {'p2': {'name': 'pressure_tent'}} # 'pg2': {'name': 'pressure_grad_tent'}} self.fileDictTentPDiff = {'p2D': {'name': 'pressure_tent_diff'}} # 'pg2D': {'name': 'pressure_grad_tent_diff'}} self.fileDictWSS = {'wss': {'name': 'wss'}, } self.fileDictWSSnorm = {'wss_norm': {'name': 'wss_norm'}, } self.fileDictDebugRot = {'grad_cor': {'name': 'grad_cor'}, } # lists of functionals and other scalar output data self.time_list = [] # list of times, when error is measured (used in report) self.second_list = [] self.listDict = {} # list of fuctionals # dictionary of data lists {list, name, abbreviation, add scaled row to report} # normalisation coefficients (time-independent) are added to some lists to be used in normalized data series # coefficients are lists (updated during initialisation, so we cannot use float type) # coefs are equal to average of respective value of analytic solution # norm lists (time-dependent normalisation coefficients) are added to some lists to be used in relative data # series (to remove natural pulsation of error due to change in volume flow rate) # slist - lists for cycle-averaged values # L2(0) means L2 difference of pressures taken with zero average self.listDict = { 'd': {'list': [], 'name': 'corrected velocity L2 divergence', 'abrev': 'DC', 'scale': self.scale_factor, 'slist': []}, 'd2': {'list': [], 'name': 'tentative velocity L2 divergence', 'abrev': 'DT', 'scale': self.scale_factor, 'slist': []}, 'pg': {'list': [], 'name': 'computed pressure gradient', 'abrev': 'PG', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor}, 'pg2': {'list': [], 'name': 'computed pressure tent gradient', 'abrev': 'PTG', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor}, } if self.has_analytic_solution: self.listDict.update({ 'u_L2': {'list': [], 'name': 'corrected velocity L2 error', 'abrev': 'CE_L2', 'scale': self.scale_factor, 'relative': 'av_norm_L2', 'slist': [], 'norm': self.vel_normalization_factor}, 'u2L2': {'list': [], 'name': 'tentative velocity L2 error', 'abrev': 'TE_L2', 'scale': self.scale_factor, 'relative': 'av_norm_L2', 'slist': [], 'norm': self.vel_normalization_factor}, 'u_L2test': {'list': [], 'name': 'test corrected L2 velocity error', 'abrev': 'TestCE_L2', 'scale': self.scale_factor}, 'u2L2test': {'list': [], 'name': 'test tentative L2 velocity error', 'abrev': 'TestTE_L2', 'scale': self.scale_factor}, 'u_H1': {'list': [], 'name': 'corrected velocity H1 error', 'abrev': 'CE_H1', 'scale': self.scale_factor, 'relative': 'av_norm_H1', 'slist': []}, 'u2H1': {'list': [], 'name': 'tentative velocity H1 error', 'abrev': 'TE_H1', 'scale': self.scale_factor, 'relative': 'av_norm_H1', 'slist': []}, 'u_H1test': {'list': [], 'name': 'test corrected H1 velocity error', 'abrev': 'TestCE_H1', 'scale': self.scale_factor}, 'u2H1test': {'list': [], 'name': 'test tentative H1 velocity error', 'abrev': 'TestTE_H1', 'scale': self.scale_factor}, 'apg': {'list': [], 'name': 'analytic pressure gradient', 'abrev': 'APG', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor}, 'av_norm_L2': {'list': [], 'name': 'analytic velocity L2 norm', 'abrev': 'AVN_L2'}, 'av_norm_H1': {'list': [], 'name': 'analytic velocity H1 norm', 'abrev': 'AVN_H1'}, 'ap_norm': {'list': [], 'name': 'analytic pressure norm', 'abrev': 'APN'}, 'p': {'list': [], 'name': 'pressure L2(0) error', 'abrev': 'PE', 'scale': self.scale_factor, 'slist': [], 'norm': self.p_normalization_factor}, 'pgE': {'list': [], 'name': 'computed pressure gradient error', 'abrev': 'PGE', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor, 'slist': []}, 'pgEA': {'list': [], 'name': 'computed absolute pressure gradient error', 'abrev': 'PGEA', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor}, 'p2': {'list': [], 'name': 'pressure tent L2(0) error', 'abrev': 'PTE', 'scale': self.scale_factor, 'slist': [], 'norm': self.p_normalization_factor}, 'pgE2': {'list': [], 'name': 'computed tent pressure tent gradient error', 'abrev': 'PTGE', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor, 'slist': []}, 'pgEA2': {'list': [], 'name': 'computed absolute pressure tent gradient error', 'abrev': 'PTGEA', 'scale': self.scale_factor, 'norm': self.pg_normalization_factor} }) # parse arguments self.nu = 0.0 # nu should be specified in subclass (fixed or by argument) self.onset = args.onset self.onset_factor = 0 # saving options: self.doSave = False self.saveOnlyVel = False self.doSaveDiff = False self.save_nth = args.saventh option = args.save if option == 'doSave' or option == 'diff' or option == 'only_vel': self.doSave = True if option == 'diff': self.doSaveDiff = True info('Saving velocity differences.') if option == 'only_vel': self.saveOnlyVel = True info('Saving only velocity profiles.') info('Saving solution ON.') elif option == 'noSave': self.doSave = False info('Saving solution OFF.') # error control options: self.doErrControl = None self.testErrControl = False if not self.has_analytic_solution: self.doErrControl = False elif args.error == "noEC": self.doErrControl = False info("Error control OFF") else: self.doErrControl = True if args.error == "test": self.testErrControl = True info("Error control in testing mode") else: info("Error control ON") # set directory for results and reports self.str_dir_name = "%s_%s_results" % (self.problem_code, metadata['name']) self.metadata['dir'] = self.str_dir_name # create directory, needed because of using "with open(..." construction later if not os.path.exists(self.str_dir_name) and self.MPI_rank == 0: os.mkdir(self.str_dir_name) @staticmethod def setup_parser_options(parser): parser.add_argument('-S', '--save', help='Save solution mode', choices=['doSave', 'noSave', 'diff', 'only_vel'], default='noSave') # doSave: create .xdmf files with velocity, pressure, divergence # diff: save also difference vel-sol # noSave: do not create .xdmf files with velocity, pressure, divergence parser.add_argument('--saventh', help='save only n-th step in first cycle', type=int, default=1) parser.add_argument('--ST', help='save modes', choices=['min', 'peak', 'no_restriction'], default='no_restriction') # stronger than --saventh, to be used instead of it parser.add_argument('--onset', help='boundary condition onset length', type=float, default=0.5) parser.add_argument('--wss', help='compute wall shear stress', choices=['none', 'all', 'peak'], default='none') # --wss is independent of -S options parser.add_argument('--wss_method', help='compute wall shear stress', choices=['expression', 'integral'], default='integral') # 'expression' works with BoundaryMesh object. It restricts stress to boundary mesh and then computes CG,1 # vector WSS and its magnitude from values on boundary # expression does not work for too many processors (24 procs for 'HYK' is OK, 48 is too much), because the case # when some parallel process have no boundary mesh is not implemented in FEniCS # 'integral' works always. It computes facet average wss norm using assemble() to integrate over boundary # facets. Results for each cell are stored in DG,0 scalar function (interior cells will have value 0, because # only exterior facet integrals are computed) # Note: it does not make sense to project the result into any other space, because zeros inside domain would # interfere with values on boundary parser.add_argument('--debug_rot', help='save more information about rotation correction', action='store_true') # idea was to save rotational correction contribution to velocity RHS # NT results are not reliable. One would need to project nu*div(v) into Q space before computing gradient. # NT not documented in readme parser.add_argument('-e', '--error', help='Error control mode', choices=['doEC', 'noEC', 'test'], default='doEC') # compute error using analytic solution (if available) # 'test' mode computes errors using both assemble() and errornorm() to check if the results are equal @staticmethod def loadMesh(mesh): """ :param mesh: name of mesh file (without extension) :return: tuple mesh, facet function read from .hdf5 file """ f = HDF5File(mpi_comm_world(), 'meshes/'+mesh+'.hdf5', 'r') mesh = Mesh() f.read(mesh, 'mesh', False) facet_function = MeshFunction("size_t", mesh) f.read(facet_function, 'facet_function') return mesh, facet_function def initialize(self, V, Q, PS, D): """ :param V: velocity space :param Q: pressure space :param PS: scalar space of same order as V, used for analytic solution generation :param D: divergence of velocity space """ self.vSpace = V self.divSpace = D self.pSpace = Q self.solutionSpace = V self.vFunction = Function(V) self.divFunction = Function(D) self.pFunction = Function(Q) # self.pgSpace = VectorFunctionSpace(mesh, "DG", 0) # used to save pressure gradient as vectors # self.pgFunction = Function(self.pgSpace) self.initialize_xdmf_files() self.stepsInCycle = self.cycle_length / self.metadata['dt'] info('stepsInCycle = %f' % self.stepsInCycle) if self.args.wss != 'none': self.tc.start('WSSinit') if self.args.wss_method == 'expression': self.T = TensorFunctionSpace(self.mesh, 'Lagrange', 1) info('Generating boundary mesh') self.wall_mesh = BoundaryMesh(self.mesh, 'exterior') self.wall_mesh_oriented = BoundaryMesh(self.mesh, 'exterior', order=False) info(' Boundary mesh geometric dim: %d' % self.wall_mesh.geometry().dim()) info(' Boundary mesh topologic dim: %d' % self.wall_mesh.topology().dim()) self.Tb = TensorFunctionSpace(self.wall_mesh, 'Lagrange', 1) self.Vb = VectorFunctionSpace(self.wall_mesh, 'Lagrange', 1) info('Generating normal to boundary') normal_expr = self.NormalExpression(self.wall_mesh_oriented) Vn = VectorFunctionSpace(self.wall_mesh, 'DG', 0) self.nb = project(normal_expr, Vn) self.Sb = FunctionSpace(self.wall_mesh, 'DG', 0) if self.args.wss_method == 'integral': self.SDG = FunctionSpace(self.mesh, 'DG', 0) self.tc.end('WSSinit') def initialize_xdmf_files(self): info(' Initializing output files.') # for creating paraview scripts self.metadata['filename_base'] = self.problem_code + '_' + self.metadata['name'] # assemble file dictionary if self.doSave: if self.doSaveDiff: self.fileDict.update(self.fileDictDiff) if self.metadata['hasTentativeV']: self.fileDict.update(self.fileDictTent) if self.doSaveDiff: self.fileDict.update(self.fileDictTentDiff) if self.metadata['hasTentativeP']: self.fileDict.update(self.fileDictTentP) if self.doSaveDiff: self.fileDict.update(self.fileDictTentPDiff) else: self.fileDict = {} if self.args.wss != 'none': self.fileDict.update(self.fileDictWSSnorm) if self.args.wss_method == 'expression': self.fileDict.update(self.fileDictWSS) if self.args.debug_rot: self.fileDict.update(self.fileDictDebugRot) # create and setup files for key, value in self.fileDict.iteritems(): value['file'] = XDMFFile(mpi_comm_world(), self.str_dir_name + "/" + self.problem_code + '_' + self.metadata['name'] + value['name'] + ".xdmf") value['file'].parameters['rewrite_function_mesh'] = False # save mesh only once per file # method for saving divergence (ensuring, that it will be one time line in ParaView) def save_div(self, is_tent, field): self.tc.start('div') self.divFunction.assign(project(div(field), self.divSpace)) self.fileDict['d2' if is_tent else 'd']['file'].write(self.divFunction, self.actual_time) self.tc.end('div') def compute_div(self, is_tent, velocity): self.tc.start('divNorm') div_list = self.listDict['d2' if is_tent else 'd']['list'] div_list.append(norm(velocity, 'Hdiv0')) self.tc.end('divNorm') # method for saving velocity (ensuring, that it will be one time line in ParaView) def save_vel(self, is_tent, field): self.vFunction.assign(field) self.fileDict['u2' if is_tent else 'u']['file'].write(self.vFunction, self.actual_time) if self.doSaveDiff: self.vFunction.assign((1.0 / self.vel_normalization_factor[0]) * (field - self.solution)) self.fileDict['u2D' if is_tent else 'uD']['file'].write(self.vFunction, self.actual_time) def compute_err(self, is_tent, velocity, t): if self.doErrControl: er_list_L2 = self.listDict['u2L2' if is_tent else 'u_L2']['list'] er_list_H1 = self.listDict['u2H1' if is_tent else 'u_H1']['list'] self.tc.start('errorV') # assemble is faster than errornorm errorL2_sq = assemble(inner(velocity - self.solution, velocity - self.solution) * dx) errorH1seminorm_sq = assemble(inner(grad(velocity - self.solution), grad(velocity - self.solution)) * dx) info(' H1 seminorm error: %f' % sqrt(errorH1seminorm_sq)) errorL2 = sqrt(errorL2_sq) errorH1 = sqrt(errorL2_sq + errorH1seminorm_sq) info(" Relative L2 error in velocity = %f" % (errorL2 / self.analytic_v_norm_L2)) self.last_error = errorH1 / self.analytic_v_norm_H1 self.last_status_functional = self.last_error info(" Relative H1 error in velocity = %f" % self.last_error) er_list_L2.append(errorL2) er_list_H1.append(errorH1) self.tc.end('errorV') if self.testErrControl: er_list_test_H1 = self.listDict['u2H1test' if is_tent else 'u_H1test']['list'] er_list_test_L2 = self.listDict['u2L2test' if is_tent else 'u_L2test']['list'] self.tc.start('errorVtest') er_list_test_L2.append(errornorm(velocity, self.solution, norm_type='L2', degree_rise=0)) er_list_test_H1.append(errornorm(velocity, self.solution, norm_type='H1', degree_rise=0)) self.tc.end('errorVtest') # stopping criteria for detecting diverging solution if self.last_error > self.divergence_treshold: raise RuntimeError('STOPPED: Failed divergence test!') def averaging_pressure(self, pressure): """ :param pressure: average is subtracted from it """ self.tc.start('averageP') # averaging pressure (subtract average) p_average = assemble((1.0 / self.mesh_volume) * pressure * dx) info('Average pressure: %f' % p_average) p_average_function = interpolate(Expression("p", p=p_average, degree=1), self.pSpace) pressure.assign(pressure - p_average_function) self.tc.end('averageP') def save_pressure(self, is_tent, pressure): self.tc.start('saveP') self.fileDict['p2' if is_tent else 'p']['file'].write(pressure, self.actual_time) # NT normalisation factor defined only in Womersley # pg = project((1.0 / self.pg_normalization_factor[0]) * grad(pressure), self.pgSpace) # self.pgFunction.assign(pg) # self.fileDict['pg2' if is_tent else 'pg'][0].write(self.pgFunction, self.actual_time self.tc.end('saveP') def get_boundary_conditions(self, use_pressure_BC, v_space, p_space): info('get_boundary_conditions() for this problem was not properly implemented.') def get_initial_conditions(self, function_list): """ :param function_list: [{'type': 'v'/'p', 'time':-0.1},...] :return: velocities and pressures in selected times """ info('get_initial_conditions for this problem was not properly implemented.') def get_v_solution(self, t): pass def get_p_solution(self, t): pass def update_time(self, actual_time, step_number): info('GS_UPDATE_TIME t = %f, step %d' % (actual_time, step_number)) self.actual_time = actual_time self.step_number = step_number self.time_list.append(self.actual_time) if self.onset < 0.001 or self.actual_time > self.onset: # TODO onset time relative to cycle_length self.onset_factor = 1. else: self.onset_factor = (1. - cos(pi * actual_time / self.onset))*0.5 info('GS_UPDATE_TIME Onset factor: %f' % self.onset_factor) # Manage saving choices for this step # save only n-th step in first second dec, i = modf(actual_time / self.cycle_length) i = int(i) info('GS_UPDATE_TIME Cycles done: %d' % i) info('GS_UPDATE_TIME Relative part of cycle: %f' % dec) dec *= self.cycle_length info('GS_UPDATE_TIME Absolute time in cycle: %f' % dec) self.distance_from_chosen_steps = min([abs(dec - d) for d in self.chosen_steps]) # TODO use self.close_to_chosen_steps info('GS_UPDATE_TIME Distance from chosen steps: %f' % self.distance_from_chosen_steps) info('GS_UPDATE_TIME Distance threshold: %f' % (0.5 * self.metadata['dt'] + DOLFIN_EPS)) if self.doSave: if self.args.ST == 'min': self.save_this_step = (i >= 1 and (self.distance_from_chosen_steps < 0.5 * self.metadata['dt'] + DOLFIN_EPS)) # QQ might skip step? change 0.5 to 0.51? elif self.args.ST == 'peak': self.save_this_step = (i >= 1 and 0.1 < dec < 0.20001) # TODO relative to cycle_length elif self.save_nth == 1 or i >= 1 or self.step_number % self.save_nth == 0: self.save_this_step = True else: self.save_this_step = False if self.save_this_step: info('GS_UPDATE_TIME Chose to save this step: (%f, %d)' % (actual_time, step_number)) # this generates vector expression of normal on boundary mesh # for right orientation use BoundaryMesh(..., order=False) # IMP this does not work if some parallel process have no boundary mesh, because that is not implemented in FEniCS class NormalExpression(Expression): """ This generates vector Expression of normal on boundary mesh. For right outward orientation use BoundaryMesh(mesh, 'exterior', order=False) """ def __init__(self, mesh): self.mesh = mesh self.gdim = mesh.geometry().dim() def eval_cell(self, values, x, ufc_cell): cell = Cell(self.mesh, ufc_cell.index) v=cell.get_vertex_coordinates().reshape((-1, self.gdim)) vec1 = v[1] - v[0] # create vectors by subtracting coordinates of vertices vec2 = v[2] - v[0] n = np.cross(vec1, vec2) n /= np.linalg.norm(n) values[0] = n[0] values[1] = n[1] values[2] = n[2] def value_shape(self): return 3, def compute_functionals(self, velocity, pressure, t, step): if self.args.wss == 'all' or \ (step >= self.stepsInCycle and self.args.wss == 'peak' and (self.distance_from_chosen_steps < 0.5 * self.metadata['dt'])): # TODO check if choosing time steps works properly # QQ might skip step? change 0.5 to 0.51? self.tc.start('WSS') begin('WSS (%dth step)' % step) if self.args.wss_method == 'expression': stress = project(self.nu*2*sym(grad(velocity)), self.T) # pressure is not used as it contributes only to the normal component stress.set_allow_extrapolation(True) # need because of some inaccuracies in BoundaryMesh coordinates stress_b = interpolate(stress, self.Tb) # restrict stress to boundary mesh # self.fileDict['stress']['file'].write(stress_b, self.actual_time) # info('Saved stress tensor') info('Computing WSS') wss = dot(stress_b, self.nb) - inner(dot(stress_b, self.nb), self.nb)*self.nb wss_func = project(wss, self.Vb) wss_norm = project(sqrt_ufl(inner(wss, wss)), self.Sb) info('Saving WSS') self.fileDict['wss']['file'].write(wss_func, self.actual_time) self.fileDict['wss_norm']['file'].write(wss_norm, self.actual_time) if self.args.wss_method == 'integral': wss_norm = Function(self.SDG) mS = TestFunction(self.SDG) scaling = 1/FacetArea(self.mesh) stress = self.nu*2*sym(grad(velocity)) wss = dot(stress, self.normal) - inner(dot(stress, self.normal), self.normal)*self.normal wss_norm_form = scaling*mS*sqrt_ufl(inner(wss, wss))*ds # ds is integral over exterior facets only assemble(wss_norm_form, tensor=wss_norm.vector()) self.fileDict['wss_norm']['file'].write(wss_norm, self.actual_time) # to get vector WSS values: # NT this works, but in ParaView for (DG,1)-vector space glyphs are displayed in cell centers # wss_vector = [] # for i in range(3): # wss_component = Function(self.SDG) # wss_vector_form = scaling*wss[i]*mS*ds # assemble(wss_vector_form, tensor=wss_component.vector()) # wss_vector.append(wss_component) # wss_func = project(as_vector(wss_vector), self.VDG) # self.fileDict['wss']['file'].write(wss_func, self.actual_time) self.tc.end('WSS') end() def compute_outflow(self, velocity): out = assemble(inner(velocity, self.normal)*self.get_outflow_measure_form()) return out def get_outflow_measures(self): """ return list of Measure objects for outflow boundary parts must be overridden in Problem before use (if needed) """ info('Integration over outflow for this problem was not properly implemented.') def get_outflow_measure_form(self): """ returns term (dSout1 + ... + dSoutN) that can be used in form to integrate over all outflow boundary parts works if get_outflow_measures is implemented """ if not self.outflow_measures: self.outflow_measures = self.get_outflow_measures() if len(self.outflow_measures) == 1: return self.outflow_measures[0] else: out = self.outflow_measures[0] for m in self.outflow_measures[1:]: out += m return out def get_metadata_to_save(self): return str(cPickle.dumps(self.metadata))#.replace('\n', '$') # noinspection PyTypeChecker def report(self): total = toc() md = self.metadata # compare errors measured by assemble and errornorm # TODO implement generally (if listDict[fcional]['testable']) # if self.testErrControl: # for e in [[self.listDict['u_L2']['list'], self.listDict['u_L2test']['list'], 'L2'], # [self.listDict['u_H1']['list'], self.listDict['u_H1test']['list'], 'H1']]: # print('test ', e[2], sum([abs(e[0][i]-e[1][i]) for i in range(len(self.time_list))])) # report error norms, norms of divergence etc. for individual time steps with open(self.str_dir_name + "/report_time_lines.csv", 'w') as reportFile: report_writer = csv.writer(reportFile, delimiter=';', escapechar='\\', quoting=csv.QUOTE_NONE) # report_writer.writerow(self.problem.get_metadata_to_save()) report_writer.writerow(["name", "what", "time"] + self.time_list) keys = sorted(self.listDict.keys()) for key in keys: l = self.listDict[key] if l['list']: abrev = l['abrev'] report_writer.writerow([md['name'], l['name'], abrev] + l['list']) if 'scale' in l: temp_list = [i/l['scale'][0] for i in l['list']] report_writer.writerow([md['name'], "scaled " + l['name'], abrev+"s"] + temp_list + ['scale factor:' + str(l['scale'])]) if 'norm' in l: if l['norm']: temp_list = [i/l['norm'][0] for i in l['list']] report_writer.writerow([md['name'], "normalized " + l['name'], abrev+"n"] + temp_list) else: info('Norm missing:' + str(l)) l['normalized_list_sec'] = [] if 'relative' in l: norm_list = self.listDict[l['relative']]['list'] temp_list = [l['list'][i]/norm_list[i] for i in range(0, len(l['list']))] self.listDict[key]['relative_list'] = temp_list report_writer.writerow([md['name'], "relative " + l['name'], abrev+"r"] + temp_list) # report error norms, norms of divergence etc. averaged over cycles # IFNEED rewrite to averaging over cycles (must track number of steps in cycle, it can be different for each cycle) # NT for cases when cycle_length % dt != 0 will be inacurate (less with smaller time steps) # note: self.stepsInCycle is float # if self.second_list: # with open(self.str_dir_name + "/report_seconds.csv", 'w') as reportFile: # report_writer = csv.writer(reportFile, delimiter=';', escapechar='|', quoting=csv.QUOTE_NONE) # report_writer.writerow(["name", "what", "time"] + self.second_list) # keys = sorted(self.listDict.keys()) # for key in keys: # l = self.listDict[key] # if 'slist' in l and l['list']: # abrev = l['abrev'] # values = l['slist'] # # generate averages over seconds from list # for sec in self.second_list: # N0 = (sec-1)*self.stepsInCycle # N1 = sec*self.stepsInCycle # values.append(sqrt(sum([i*i for i in l['list'][N0:N1]]) / float(self.stepsInCycle))) # # report_writer.writerow([md['name'], l['name'], abrev] + values) # if 'scale' in l: # temp_list = [i/l['scale'][0] for i in values] # report_writer.writerow([md['name'], "scaled " + l['name'], abrev+"s"] + temp_list) # if 'norm' in l: # if l['norm']: # temp_list = [i/l['norm'][0] for i in values] # l['normalized_list_sec'] = temp_list # report_writer.writerow([md['name'], "normalized " + l['name'], abrev+"n"] + temp_list) # else: # info('Norm missing:' + str(l)) # l['normalized_list_sec'] = [] # if 'relative_list' in l: # temp_list = [] # for sec in self.second_list: # N0 = (sec-1)*self.stepsInCycle # N1 = sec*self.stepsInCycle # temp_list.append(sqrt(sum([i*i for i in l['relative_list'][N0:N1]]) / float(self.stepsInCycle))) # l['relative_list_sec'] = temp_list # report_writer.writerow([md['name'], "relative " + l['name'], abrev+"r"] + temp_list) header_row = ["name", "totalTimeHours"] data_row = [md['name'], total / 3600.0] for key in ['u_L2', 'u_H1', 'u_H1w', 'p', 'u2L2', 'u2H1', 'u2H1w', 'p2', 'pgE', 'pgE2', 'd', 'd2', 'force_wall']: if key in self.listDict: l = self.listDict[key] header_row += ['last_cycle_'+l['abrev']] data_row += [l['slist'][-1]] if l['slist'] else [0] if 'relative_list_sec' in l and l['relative_list_sec']: header_row += ['last_cycle_'+l['abrev']+'r'] data_row += [l['relative_list_sec'][-1]] elif key in ['p', 'p2']: header_row += ['last_cycle_'+l['abrev']+'n'] data_row += [l['normalized_list_sec'][-1]] if 'normalized_list_sec' in l \ and l['normalized_list_sec'] else [0] # save metadata. Can be loaded and used in postprocessing with open(self.str_dir_name + "/metadata", 'w') as reportFile: reportFile.write(self.get_metadata_to_save()) # report without header with open(self.str_dir_name + "/report.csv", 'w') as reportFile: report_writer = csv.writer(reportFile, delimiter=';', escapechar='|', quoting=csv.QUOTE_NONE) report_writer.writerow(data_row) # report with header with open(self.str_dir_name + "/report_h.csv", 'w') as reportFile: report_writer = csv.writer(reportFile, delimiter=';', escapechar='|', quoting=csv.QUOTE_NONE) report_writer.writerow(header_row) report_writer.writerow(data_row) # report time cotrol with open(self.str_dir_name + "/report_timecontrol.csv", 'w') as reportFile: self.tc.report(reportFile, self.metadata['name']) self.remove_status_file() # create file showing all was done well f = open(md['name'] + "_OK.report", "w") f.close() def report_fail(self, t): print("Runtime error:", sys.exc_info()[1]) print("Traceback:") traceback.print_tb(sys.exc_info()[2]) f = open(self.metadata['name'] + "_failed_at_%5.3f.report" % t, "w") f.write(traceback.format_exc()) f.close() self.remove_status_file() def write_status_file(self, t): self.tc.start('status') f = open(self.metadata['name'] + ".run", "w") progress = t/self.metadata['time'] f.write('t = %5.3f (dt=%f)\nprogress = %3.0f %%\n%s = %5.3f\n' % (t, self.metadata['dt'], 100*progress, self.status_functional_str, self.last_status_functional)) f.close() self.tc.end('status') def remove_status_file(self): if self.MPI_rank == 0: try: os.remove(self.metadata['name'] + ".run") except OSError: info('.run file probably not created')
class Problem(gp.GeneralProblem): def __init__(self, args, tc, metadata): self.has_analytic_solution = True self.problem_code = 'WCYL' super(Problem, self).__init__(args, tc, metadata) self.tc.init_watch('assembleSol', 'Assembled analytic solution', True) self.tc.init_watch('analyticP', 'Analytic pressure', True) self.tc.init_watch('analyticVnorms', 'Computed analytic velocity norms', True) self.tc.init_watch('errorP', 'Computed pressure error', True) self.tc.init_watch('errorForce', 'Computed force error', True) self.tc.init_watch('computePG', 'Computed pressure gradient', True) self.name = 'womersley_cylinder' self.status_functional_str = 'last H1 velocity error' # input parameters self.ic = args.ic self.factor = args.factor self.metadata['factor'] = self.factor self.scale_factor.append(self.factor) # fixed parameters (used in analytic solution and in BC) self.nu = 3.71 * self.args.nufactor # kinematic viscosity self.R = 5.0 # cylinder radius self.mesh_volume = pi * 25. * 20. # Import gmsh mesh self.tc.start('mesh') self.mesh, self.facet_function = super(Problem, self).loadMesh(args.mesh) self.dsIn = Measure("ds", subdomain_id=2, subdomain_data=self.facet_function) self.dsOut = Measure("ds", subdomain_id=3, subdomain_data=self.facet_function) self.dsWall = Measure("ds", subdomain_id=1, subdomain_data=self.facet_function) self.normal = FacetNormal(self.mesh) print("Mesh name: ", args.mesh, " ", self.mesh) print("Mesh norm max: ", self.mesh.hmax()) print("Mesh norm min: ", self.mesh.hmin()) self.tc.end('mesh') self.sol_p = None self.last_analytic_pressure_norm = None self.v_in = None self.area = None choose_note = {1.0: '', 0.1: 'nuL10', 0.01: 'nuL100', 10.0: 'nuH10'} self.precomputed_filename = args.mesh + choose_note[self.args.nufactor] print('chosen filename for precomputed solution', self.precomputed_filename) # partial Bessel functions and coefficients self.bessel_parabolic = None self.bessel_real = [] self.bessel_complex = [] self.coefs_exp = [-8, -6, -4, -2, 2, 4, 6, 8] self.listDict.update({ 'u_H1w': { 'list': [], 'name': 'corrected velocity H1 error on wall', 'abrev': 'CE_H1w', 'scale': self.scale_factor, 'relative': 'av_norm_H1w', 'slist': [] }, 'u2H1w': { 'list': [], 'name': 'tentative velocity H1 error on wall', 'abrev': 'TE_H1w', 'scale': self.scale_factor, 'relative': 'av_norm_H1w', 'slist': [] }, 'av_norm_H1w': { 'list': [], 'name': 'analytic velocity H1 norm on wall', 'abrev': 'AVN_H1w' }, 'a_force_wall': { 'list': [], 'name': 'analytic force on wall', 'abrev': 'AF' }, 'a_force_wall_normal': { 'list': [], 'name': 'analytic force on wall', 'abrev': 'AFN' }, 'a_force_wall_shear': { 'list': [], 'name': 'analytic force on wall', 'abrev': 'AFS' }, 'force_wall': { 'list': [], 'name': 'force error on wall', 'abrev': 'FE', 'relative': 'a_force_wall', 'slist': [] }, 'force_wall_normal': { 'list': [], 'name': 'normal force error on wall', 'abrev': 'FNE', 'relative': 'a_force_wall', 'slist': [] }, 'force_wall_shear': { 'list': [], 'name': 'shear force error on wall', 'abrev': 'FSE', 'relative': 'a_force_wall', 'slist': [] }, }) def __str__(self): return 'womersley flow in cylinder' @staticmethod def setup_parser_options(parser): super(Problem, Problem).setup_parser_options(parser) parser.add_argument('--ic', help='Initial condition', choices=['zero', 'correct'], default='zero') parser.add_argument('-F', '--factor', help='Velocity scale factor', type=float, default=1.0) parser.add_argument('--nufactor', help='kinematic viscosity factor', type=float, default=1.0) def initialize(self, V, Q, PS, D): super(Problem, self).initialize(V, Q, PS, D) print("IC type: " + self.ic) print("Velocity scale factor = %4.2f" % self.factor) reynolds = 728.761 * self.factor / self.args.nufactor print("Computing with Re = %f" % reynolds) self.v_in = Function(V) print('Initializing error control') self.load_precomputed_bessel_functions(PS) self.solution = self.assemble_solution(0.0) # set constants for self.area = assemble( interpolate(Expression("1.0", degree=1), Q) * self.dsIn) # inflow area # womersley = steady + e^iCt, e^iCt has average 0 self.pg_normalization_factor.append( womersleyBC.average_analytic_pressure_grad(self.factor)) self.p_normalization_factor.append( norm(interpolate( womersleyBC.average_analytic_pressure_expr(self.factor), self.pSpace), norm_type='L2')) self.vel_normalization_factor.append( norm(interpolate( womersleyBC.average_analytic_velocity_expr(self.factor), self.vSpace), norm_type='L2')) one = (interpolate(Expression('1.0', degree=1), Q)) self.outflow_area = assemble(one * self.dsOut) print('Outflow area:', self.outflow_area) def get_boundary_conditions(self, use_pressure_BC, v_space, p_space): # boundary parts: 1 walls, 2 inflow, 3 outflow bc0 = DirichletBC(v_space, (0.0, 0.0, 0.0), self.facet_function, 1) # no-slip inflow = DirichletBC(v_space, self.v_in, self.facet_function, 2) bcu = [inflow, bc0] bcp = [] if use_pressure_BC: outflow = DirichletBC(p_space, 0.0, self.facet_function, 3) bcp = [outflow] return bcu, bcp def get_initial_conditions(self, function_list): out = [] for d in function_list: if d['type'] == 'v': f = Function(self.vSpace) if self.ic == 'correct': f = self.assemble_solution(d['time']) if d['type'] == 'p': f = Function(self.pSpace) if self.ic == 'correct': f = interpolate( womersleyBC.analytic_pressure(self.factor, d['time']), self.pSpace) out.append(f) return out def get_outflow_measures(self): return [self.dsOut] def get_v_solution(self, t): v = self.assemble_solution(t) return v def get_p_solution(self, t): p = interpolate(womersleyBC.analytic_pressure(self.factor, t), self.pSpace) return p def update_time(self, actual_time, step_number): super(Problem, self).update_time(actual_time, step_number) if self.actual_time > 0.5 and abs( math.modf(actual_time)[0]) < 0.5 * self.metadata['dt']: self.second_list.append(int(round(self.actual_time))) self.solution = self.assemble_solution(self.actual_time) # Update boundary condition self.tc.start('updateBC') self.v_in.assign(self.onset_factor * self.solution) self.tc.end('updateBC') # construct analytic pressure (used for computing pressure and force errors) self.tc.start('analyticP') analytic_pressure = womersleyBC.analytic_pressure( self.factor, self.actual_time) self.sol_p = interpolate(analytic_pressure, self.pSpace) self.tc.end('analyticP') self.tc.start('analyticVnorms') self.analytic_v_norm_L2 = norm(self.solution, norm_type='L2') self.analytic_v_norm_H1 = norm(self.solution, norm_type='H1') self.analytic_v_norm_H1w = sqrt( assemble((inner(grad(self.solution), grad(self.solution)) + inner(self.solution, self.solution)) * self.dsWall)) self.listDict['av_norm_L2']['list'].append(self.analytic_v_norm_L2) self.listDict['av_norm_H1']['list'].append(self.analytic_v_norm_H1) self.listDict['av_norm_H1w']['list'].append(self.analytic_v_norm_H1w) self.tc.end('analyticVnorms') def assemble_solution(self, t): # returns """ :param t: time :return: Womersley flow (analytic solution) at time t analytic solution at any time is a steady parabolic flow + linear combination of 8 modes modes were precomputed as 8 functions on given mesh and stored in hdf5 file """ if self.tc is not None: self.tc.start('assembleSol') sol = Function(self.solutionSpace) # analytic solution has zero x and y components dofs2 = self.solutionSpace.sub(2).dofmap().dofs( ) # gives field of indices corresponding to z axis sol.assign(Constant(("0.0", "0.0", "0.0"))) # QQ not needed sol.vector()[dofs2] += self.factor * self.bessel_parabolic.vector( ).array() # parabolic part of sol for idx in range(8): # add modes of Womersley sol sol.vector()[dofs2] += self.factor * cos( self.coefs_exp[idx] * pi * t) * self.bessel_real[idx].vector().array() sol.vector()[dofs2] += self.factor * -sin( self.coefs_exp[idx] * pi * t) * self.bessel_complex[idx].vector().array() if self.tc is not None: self.tc.end('assembleSol') return sol def load_precomputed_bessel_functions(self, PS): """ loads precomputed Bessel functions (modes of analytic solution) """ f = HDF5File( mpi_comm_world(), 'precomputed/precomputed_' + self.precomputed_filename + '.hdf5', 'r') temp = toc() fce = Function(PS) f.read(fce, "parab") self.bessel_parabolic = fce.copy(deepcopy=True) for i in range(8): f.read(fce, "real%d" % i) self.bessel_real.append(fce.copy(deepcopy=True)) f.read(fce, "imag%d" % i) self.bessel_complex.append(fce.copy(deepcopy=True)) print("Loaded partial solution functions. Time: %f" % (toc() - temp)) def compute_err(self, is_tent, velocity, t): super(Problem, self).compute_err(is_tent, velocity, t) er_list_H1w = self.listDict['u2H1w' if is_tent else 'u_H1w']['list'] errorH1wall = sqrt( assemble( (inner(grad(velocity - self.solution), grad(velocity - self.solution)) + inner(velocity - self.solution, velocity - self.solution)) * self.dsWall)) er_list_H1w.append(errorH1wall) print(' Relative H1wall error:', errorH1wall / self.analytic_v_norm_H1w) def compute_functionals(self, velocity, pressure, t, step): super(Problem, self).compute_functionals(velocity, pressure, t, step) self.compute_force(velocity, pressure, t) def compute_force(self, velocity, pressure, t): self.tc.start('errorForce') I = Identity(3) # Identity tensor def T(p, v): return -p * I + 2.0 * self.nu * sym(grad(v)) error_force = sqrt( assemble( inner((T(pressure, velocity) - T(self.sol_p, self.solution)) * self.normal, (T(pressure, velocity) - T(self.sol_p, self.solution)) * self.normal) * self.dsWall)) an_force = sqrt( assemble( inner( T(self.sol_p, self.solution) * self.normal, T(self.sol_p, self.solution) * self.normal) * self.dsWall)) an_f_normal = sqrt( assemble( inner( inner( T(self.sol_p, self.solution) * self.normal, self.normal), inner( T(self.sol_p, self.solution) * self.normal, self.normal)) * self.dsWall)) error_f_normal = sqrt( assemble( inner( inner( (T(self.sol_p, self.solution) - T(pressure, velocity)) * self.normal, self.normal), inner( (T(self.sol_p, self.solution) - T(pressure, velocity)) * self.normal, self.normal)) * self.dsWall)) an_f_shear = sqrt( assemble( inner( (I - outer(self.normal, self.normal)) * T(self.sol_p, self.solution) * self.normal, (I - outer(self.normal, self.normal)) * T(self.sol_p, self.solution) * self.normal) * self.dsWall)) error_f_shear = sqrt( assemble( inner((I - outer(self.normal, self.normal)) * (T(self.sol_p, self.solution) - T(pressure, velocity)) * self.normal, (I - outer(self.normal, self.normal)) * (T(self.sol_p, self.solution) - T(pressure, velocity)) * self.normal) * self.dsWall)) self.listDict['a_force_wall']['list'].append(an_force) self.listDict['a_force_wall_normal']['list'].append(an_f_normal) self.listDict['a_force_wall_shear']['list'].append(an_f_shear) self.listDict['force_wall']['list'].append(error_force) self.listDict['force_wall_normal']['list'].append(error_f_normal) self.listDict['force_wall_shear']['list'].append(error_f_shear) print(' Relative force error:', error_force / an_force) self.tc.end('errorForce') def save_pressure(self, is_tent, pressure): super(Problem, self).save_pressure(is_tent, pressure) self.tc.start('computePG') # Report pressure gradient p_in = assemble((1.0 / self.area) * pressure * self.dsIn) p_out = assemble((1.0 / self.area) * pressure * self.dsOut) computed_gradient = (p_out - p_in) / 20.0 # 20.0 is a length of a pipe NT should depend on mesh length (implement through metadata or function of mesh) self.tc.end('computePG') self.tc.start('analyticP') analytic_gradient = womersleyBC.analytic_pressure_grad( self.factor, self.actual_time) if not is_tent: self.last_analytic_pressure_norm = norm(self.sol_p, norm_type='L2') self.listDict['ap_norm']['list'].append( self.last_analytic_pressure_norm) self.tc.end('analyticP') self.tc.start('errorP') error = errornorm(self.sol_p, pressure, norm_type="l2", degree_rise=0) self.listDict['p2' if is_tent else 'p']['list'].append(error) print("Normalized pressure error norm:", error / self.p_normalization_factor[0]) self.listDict['pg2' if is_tent else 'pg']['list'].append( computed_gradient) if not is_tent: self.listDict['apg']['list'].append(analytic_gradient) self.listDict['pgE2' if is_tent else 'pgE']['list'].append( computed_gradient - analytic_gradient) self.listDict['pgEA2' if is_tent else 'pgEA']['list'].append( abs(computed_gradient - analytic_gradient)) self.tc.end('errorP') if self.doSaveDiff: # sol_pg_expr = Expression(("0", "0", "pg"), pg=analytic_gradient / self.pg_normalization_factor[0]) # sol_pg = interpolate(sol_pg_expr, self.pgSpace) # plot(sol_p, title="sol") # plot(pressure, title="p") # plot(pressure - sol_p, interactive=True, title="diff") # exit() self.pFunction.assign(pressure - self.sol_p) self.fileDict['p2D' if is_tent else 'pD']['file'] << self.pFunction
class ObjectiveFunctional(LinearOperator): """ Provides data misfit, gradient and Hessian information for the data misfit part of a time-independent symmetric inverse problem. """ __metaclass__ = abc.ABCMeta # Instantiation def __init__(self, V, Vm, bc, bcadj, \ RHSinput=[], ObsOp=[], UD=[], Regul=[], Data=[], plot=False, \ mycomm=None): # Define test, trial and all other functions self.trial = TrialFunction(V) self.test = TestFunction(V) self.mtrial = TrialFunction(Vm) self.mtest = TestFunction(Vm) self.rhs = Function(V) self.m = Function(Vm) self.mcopy = Function(Vm) self.srchdir = Function(Vm) self.delta_m = Function(Vm) self.MG = Function(Vm) self.Grad = Function(Vm) self.Gradnorm = 0.0 self.lenm = len(self.m.vector().array()) self.u = Function(V) self.ud = Function(V) self.diff = Function(V) self.p = Function(V) # Define weak forms to assemble A, C and E self._wkforma() self._wkformc() self._wkforme() # Store other info: self.ObsOp = ObsOp self.UD = UD self.reset() # Initialize U, C and E to [] self.Data = Data self.GN = 1.0 # GN = 0.0 => GN Hessian; = 1.0 => full Hessian # Operators and bc LinearOperator.__init__(self, self.delta_m.vector(), \ self.delta_m.vector()) self.bc = bc self.bcadj = bcadj self._assemble_solverM(Vm) self.assemble_A() self.assemble_RHS(RHSinput) self.Regul = Regul # Counters, tolerances and others self.nbPDEsolves = 0 # Updated when solve_A called self.nbfwdsolves = 0 # Counter for plots self.nbadjsolves = 0 # Counter for plots self._set_plots(plot) # MPI: self.mycomm = mycomm try: self.myrank = MPI.rank(self.mycomm) except: self.myrank = 0 def copy(self): """Define a copy method""" V = self.trial.function_space() Vm = self.mtrial.function_space() newobj = self.__class__(V, Vm, self.bc, self.bcadj, [], self.ObsOp, \ self.UD, self.Regul, self.Data, False) newobj.RHS = self.RHS newobj.update_m(self.m) return newobj def mult(self, mhat, y): """mult(self, mhat, y): do y = Hessian * mhat member self.GN sets full Hessian (=1.0) or GN Hessian (=0.0)""" N = self.Nbsrc # Number of sources y[:] = np.zeros(self.lenm) for C, E in zip(self.C, self.E): # Solve for uhat C.transpmult(mhat, self.rhs.vector()) self.bcadj.apply(self.rhs.vector()) self.solve_A(self.u.vector(), -self.rhs.vector()) # Solve for phat E.transpmult(mhat, self.rhs.vector()) Etmhat = self.rhs.vector().array() self.rhs.vector().axpy(1.0, self.ObsOp.incradj(self.u)) self.bcadj.apply(self.rhs.vector()) self.solve_A(self.p.vector(), -self.rhs.vector()) # Compute Hessian*x: y.axpy(1.0/N, C * self.p.vector()) y.axpy(self.GN/N, E * self.u.vector()) y.axpy(1.0, self.Regul.hessian(mhat)) # Getters def getm(self): return self.m def getmarray(self): return self.m.vector().array() def getmcopyarray(self): return self.mcopy.vector().array() def getVm(self): return self.mtrial.function_space() def getMGarray(self): return self.MG.vector().array() def getMGvec(self): return self.MG.vector() def getGradarray(self): return self.Grad.vector().array() def getGradnorm(self): return self.Gradnorm def getsrchdirarray(self): return self.srchdir.vector().array() def getsrchdirvec(self): return self.srchdir.vector() def getsrchdirnorm(self): return np.sqrt((self.MM*self.getsrchdirvec()).inner(self.getsrchdirvec())) def getgradxdir(self): return self.gradxdir def getcost(self): return self.cost, self.misfit, self.regul def getprecond(self): Prec = PETScKrylovSolver("richardson", "amg") Prec.parameters["maximum_iterations"] = 1 Prec.parameters["error_on_nonconvergence"] = False Prec.parameters["nonzero_initial_guess"] = False Prec.set_operator(self.Regul.get_precond()) return Prec def getMass(self): return self.MM # Setters def setsrchdir(self, arr): self.srchdir.vector()[:] = arr def setgradxdir(self, valueloc): """Sum all local results for Grad . Srch_dir""" try: valueglob = MPI.sum(self.mycomm, valueloc) except: valueglob = valueloc self.gradxdir = valueglob # Solve def solvefwd(self, cost=False): """Solve fwd operators for given RHS""" self.nbfwdsolves += 1 if self.ObsOp.noise: self.noise = 0.0 if self.plot: self.plotu = PlotFenics(self.plotoutdir) self.plotu.set_varname('u{0}'.format(self.nbfwdsolves)) if cost: self.misfit = 0.0 for ii, rhs in enumerate(self.RHS): self.solve_A(self.u.vector(), rhs) if self.plot: self.plotu.plot_vtk(self.u, ii) u_obs, noiselevel = self.ObsOp.obs(self.u) self.U.append(u_obs) if self.ObsOp.noise: self.noise += noiselevel if cost: self.misfit += self.ObsOp.costfct(u_obs, self.UD[ii]) self.C.append(assemble(self.c)) if cost: self.misfit /= len(self.U) self.regul = self.Regul.cost(self.m) self.cost = self.misfit + self.regul if self.ObsOp.noise and self.myrank == 0: print 'Total noise in data misfit={:.5e}\n'.\ format(self.noise*.5/len(self.U)) self.ObsOp.noise = False # Safety if self.plot: self.plotu.gather_vtkplots() def solvefwd_cost(self): """Solve fwd operators for given RHS and compute cost fct""" self.solvefwd(True) def solveadj(self, grad=False): """Solve adj operators""" self.nbadjsolves += 1 if self.plot: self.plotp = PlotFenics(self.plotoutdir) self.plotp.set_varname('p{0}'.format(self.nbadjsolves)) self.Nbsrc = len(self.UD) if grad: self.MG.vector()[:] = np.zeros(self.lenm) for ii, C in enumerate(self.C): self.ObsOp.assemble_rhsadj(self.U[ii], self.UD[ii], \ self.rhs, self.bcadj) self.solve_A(self.p.vector(), self.rhs.vector()) if self.plot: self.plotp.plot_vtk(self.p, ii) self.E.append(assemble(self.e)) if grad: self.MG.vector().axpy(1.0/self.Nbsrc, \ C * self.p.vector()) if grad: self.MG.vector().axpy(1.0, self.Regul.grad(self.m)) self.solverM.solve(self.Grad.vector(), self.MG.vector()) self.Gradnorm = np.sqrt(self.Grad.vector().inner(self.MG.vector())) if self.plot: self.plotp.gather_vtkplots() def solveadj_constructgrad(self): """Solve adj operators and assemble gradient""" self.solveadj(True) # Assembler def assemble_A(self): """Assemble operator A(m)""" self.A = assemble(self.a) self.bc.apply(self.A) self.set_solver() def solve_A(self, b, f): """Solve system of the form A.b = f, with b and f in form to be used in solver.""" self.solver.solve(b, f) self.nbPDEsolves += 1 def assemble_RHS(self, RHSin): """Assemble RHS for fwd solve""" if RHSin == []: self.RHS = None else: self.RHS = [] for rhs in RHSin: if isinstance(rhs, Expression): L = rhs*self.test*dx b = assemble(L) self.bc.apply(b) self.RHS.append(b) else: raise WrongInstanceError("rhs should be Expression") def _assemble_solverM(self, Vm): self.MM = assemble(inner(self.mtrial, self.mtest)*dx) self.solverM = LUSolver() self.solverM.parameters['reuse_factorization'] = True self.solverM.parameters['symmetric'] = True self.solverM.set_operator(self.MM) def _set_plots(self, plot): self.plot = plot if self.plot: filename, ext = splitext(sys.argv[0]) self.plotoutdir = filename + '/Plots/' self.plotvarm = PlotFenics(self.plotoutdir) self.plotvarm.set_varname('m') def plotm(self, index): if self.plot: self.plotvarm.plot_vtk(self.m, index) def gatherm(self): if self.plot: self.plotvarm.gather_vtkplots() # Update param def update_Data(self, Data): """Update Data member""" self.Data = Data self.assemble_A() self.reset() def update_m(self, m): """Update values of parameter m""" if isinstance(m, np.ndarray): self.m.vector()[:] = m elif isinstance(m, Function): self.m.assign(m) elif isinstance(m, float): self.m.vector()[:] = m elif isinstance(m, int): self.m.vector()[:] = float(m) else: raise WrongInstanceError('Format for m not accepted') self.assemble_A() self.reset() def backup_m(self): self.mcopy.assign(self.m) def restore_m(self): self.update_m(self.mcopy) def reset(self): """Reset U, C and E""" self.U = [] self.C = [] self.E = [] def set_solver(self): """Reset solver for fwd operator""" self.solver = LUSolver() self.solver.parameters['reuse_factorization'] = True self.solver.set_operator(self.A) def addPDEcount(self, increment=1): """Increase 'nbPDEsolves' by 'increment'""" self.nbPDEsolves += increment def resetPDEsolves(self): self.nbPDEsolves = 0 # Additional methods for compatibility with CG solver: def init_vector(self, x, dim): """Initialize vector x to be compatible with parameter Does not work in dolfin 1.3.0""" self.MM.init_vector(x, 0) def init_vector130(self): """Initialize vector x to be compatible with parameter""" return Vector(Function(self.mcopy.function_space()).vector()) # Abstract methods @abc.abstractmethod def _wkforma(self): self.a = [] @abc.abstractmethod def _wkformc(self): self.c = [] @abc.abstractmethod def _wkforme(self): self.e = []
def test_assign(V, W): for V0, V1, vector_space in [(V, W, False), (W, V, True)]: u = Function(V0) u0 = Function(V0) u1 = Function(V0) u2 = Function(V0) u3 = Function(V1) u.vector()[:] = 1.0 u0.vector()[:] = 2.0 u1.vector()[:] = 3.0 u2.vector()[:] = 4.0 u3.vector()[:] = 5.0 uu = Function(V0) uu.assign(2 * u) assert uu.vector().get_local().sum() == u0.vector().get_local().sum() uu = Function(V1) uu.assign(3 * u) assert uu.vector().get_local().sum() == u1.vector().get_local().sum() # Test complex assignment expr = 3 * u - 4 * u1 - 0.1 * 4 * u * 4 + u2 + 3 * u0 / 3. / 0.5 expr_scalar = 3 - 4 * 3 - 0.1 * 4 * 4 + 4. + 3 * 2. / 3. / 0.5 uu.assign(expr) assert (round( uu.vector().get_local().sum() - float(expr_scalar * uu.vector().size()), 7) == 0) # Test expression scaling expr = 3 * expr expr_scalar *= 3 uu.assign(expr) assert (round( uu.vector().get_local().sum() - float(expr_scalar * uu.vector().size()), 7) == 0) # Test expression scaling expr = expr / 4.5 expr_scalar /= 4.5 uu.assign(expr) assert (round( uu.vector().get_local().sum() - float(expr_scalar * uu.vector().size()), 7) == 0) # Test self assignment expr = 3 * u - 5.0 * u2 + u1 - 5 * u expr_scalar = 3 - 5 * 4. + 3. - 5 u.assign(expr) assert (round( u.vector().get_local().sum() - float(expr_scalar * u.vector().size()), 7) == 0) # Test zero assignment u.assign(-u2 / 2 + 2 * u1 - u1 / 0.5 + u2 * 0.5) assert round(u.vector().get_local().sum() - 0.0, 7) == 0 # Test erroneous assignments uu = Function(V1) @function.expression.numba_eval def expr_eval(values, x, t): values[:, 0] = 1.0 f = Expression(expr_eval) with pytest.raises(RuntimeError): uu.assign(1.0) with pytest.raises(RuntimeError): uu.assign(4 * f) if not vector_space: with pytest.raises(RuntimeError): uu.assign(u * u0) with pytest.raises(RuntimeError): uu.assign(4 / u0) with pytest.raises(RuntimeError): uu.assign(4 * u * u1)
def test(problem, max_num_steps=2, show=False): # # Density depends on temperature. # material = 'water' # rho = params[material]['density'](293.0) # mu = params[material]['dynamic viscosity'](293.0) rho = 1.0 mu = 1.0 # Start time, end time, time step. t = 0.0 T = 8.0 dt = 1.0e-5 dt_max = 1.0e-1 num_subspaces = problem.W.num_sub_spaces() if num_subspaces == 2: # g = Constant((0.0, 0.0)) g = Constant((0.0, -9.81)) elif num_subspaces == 3: # g = Constant((0.0, 0.0, 0.0)) g = Constant((0.0, -9.81, 0.0)) else: raise RuntimeError("Illegal number of subspaces ({}).".format(num_subspaces)) initial_stokes = False if initial_stokes: u0, p0 = cyl_stokes.solve( problem.W, problem.P, mu, rho, problem.u_bcs, problem.p_bcs, f=rho * g, tol=1.0e-10, maxiter=2000, ) else: # Initial states. u0 = Function(problem.W, name="velocity") u0.vector().zero() p0 = Function(problem.P, name="pressure") p0.vector().zero() filename = "navier_stokes.xdmf" with XDMFFile(mpi_comm_world(), filename) as xdmf_file: xdmf_file.parameters["flush_output"] = True xdmf_file.parameters["rewrite_function_mesh"] = False if show: xdmf_file.write(u0, t) xdmf_file.write(p0, t) stepper = cyl_ns.IPCS(time_step_method="backward euler") steps = 0 while t < T + DOLFIN_EPS and steps < max_num_steps: steps += 1 begin("Time step {:e} -> {:e}...".format(t, t + dt)) try: u1, p1 = stepper.step( Constant(dt), {0: u0}, p0, problem.W, problem.P, problem.u_bcs, problem.p_bcs, Constant(rho), Constant(mu), f={0: rho * g, 1: rho * g}, tol=1.0e-10, ) except RuntimeError: print( "Navier--Stokes solver failed to converge. " "Decrease time step from {} to {} and try again.".format( dt, 0.5 * dt ) ) dt *= 0.5 end() end() end() continue u0.assign(u1) p0.assign(p1) if show: # Save to files. xdmf_file.write(u0, t + dt) xdmf_file.write(p0, t + dt) # Plotting for some reason takes up a lot of memory. plot(u0, title="velocity", rescale=True) plot(p0, title="pressure", rescale=True) # interactive() begin("Step size adaptation...") # unorm = project(abs(u[0]) + abs(u[1]) + abs(u[2]), # P, # form_compiler_parameters={'quadrature_degree': 4} # ) unorm = project( norm(u1), problem.P, form_compiler_parameters={"quadrature_degree": 4} ) unorm = norm(unorm.vector(), "linf") # print('||u||_inf = {:e}'.format(unorm)) # Some smooth step-size adaption. target_dt = 0.2 * problem.mesh.hmax() / unorm print("current dt: {:e}".format(dt)) print("target dt: {:e}".format(target_dt)) # alpha is the aggressiveness factor. The distance between the # current step size and the target step size is reduced by # |1-alpha|. Hence, if alpha==1 then dt_next==target_dt. Otherwise # target_dt is approached slowlier. alpha = 0.5 dt = min( dt_max, # At most double the step size from step to step. dt * min(2.0, 1.0 + alpha * (target_dt - dt) / dt), ) print("next dt: {:e}".format(dt)) t += dt end() end() return
class Problem(gp.GeneralProblem): def __init__(self, args, tc, metadata): self.has_analytic_solution = True self.problem_code = 'WCYL' super(Problem, self).__init__(args, tc, metadata) # TODO check if really used here self.tc.init_watch('assembleSol', 'Assembled analytic solution', True) self.tc.init_watch('analyticP', 'Analytic pressure', True) self.tc.init_watch('analyticVnorms', 'Computed analytic velocity norms', True) self.tc.init_watch('errorP', 'Computed pressure error', True) self.tc.init_watch('errorV', 'Computed velocity error', True) self.tc.init_watch('errorForce', 'Computed force error', True) self.tc.init_watch('errorVtest', 'Computed velocity error test', True) self.tc.init_watch('computePG', 'Computed pressure gradient', True) self.name = 'womersley_cylinder' self.status_functional_str = 'last H1 velocity error' # input parameters self.ic = args.ic self.factor = args.factor self.metadata['factor'] = self.factor self.scale_factor.append(self.factor) # fixed parameters (used in analytic solution and in BC) self.nu = 3.71 * args.nu # kinematic viscosity self.R = 5.0 # cylinder radius self.mesh_volume = pi*25.*20. # Import gmsh mesh self.mesh, self.facet_function = super(Problem, self).loadMesh(args.mesh) self.dsIn = Measure("ds", subdomain_id=2, subdomain_data=self.facet_function) self.dsOut = Measure("ds", subdomain_id=3, subdomain_data=self.facet_function) self.dsWall = Measure("ds", subdomain_id=1, subdomain_data=self.facet_function) self.normal = FacetNormal(self.mesh) print("Mesh name: ", args.mesh, " ", self.mesh) print("Mesh norm max: ", self.mesh.hmax()) print("Mesh norm min: ", self.mesh.hmin()) self.actual_time = None self.sol_p = None self.last_analytic_pressure_norm = None self.v_in = None self.area = None choose_note = {1.0: '', 0.1: 'nuL10', 0.01: 'nuL100', 10.0: 'nuH10'} self.precomputed_filename = args.mesh + choose_note[self.nu_factor] print('chosen filename for precomputed solution', self.precomputed_filename) # partial Bessel functions and coefficients self.bessel_parabolic = None self.bessel_real = [] self.bessel_complex = [] self.coefs_exp = [-8, -6, -4, -2, 2, 4, 6, 8] self.listDict.update({ 'u_H1w': {'list': [], 'name': 'corrected velocity H1 error on wall', 'abrev': 'CE_H1w', 'scale': self.scale_factor, 'relative': 'av_norm_H1w', 'slist': []}, 'u2H1w': {'list': [], 'name': 'tentative velocity H1 error on wall', 'abrev': 'TE_H1w', 'scale': self.scale_factor, 'relative': 'av_norm_H1w', 'slist': []}, 'av_norm_H1w': {'list': [], 'name': 'analytic velocity H1 norm on wall', 'abrev': 'AVN_H1w'}, 'a_force_wall': {'list': [], 'name': 'analytic force on wall', 'abrev': 'AF'}, 'a_force_wall_normal': {'list': [], 'name': 'analytic force on wall', 'abrev': 'AFN'}, 'a_force_wall_shear': {'list': [], 'name': 'analytic force on wall', 'abrev': 'AFS'}, 'force_wall': {'list': [], 'name': 'force error on wall', 'abrev': 'FE', 'relative': 'a_force_wall', 'slist': []}, 'force_wall_normal': {'list': [], 'name': 'normal force error on wall', 'abrev': 'FNE', 'relative': 'a_force_wall', 'slist': []}, 'force_wall_shear': {'list': [], 'name': 'shear force error on wall', 'abrev': 'FSE', 'relative': 'a_force_wall', 'slist': []}, }) def __str__(self): return 'womersley flow in cylinder' @staticmethod def setup_parser_options(parser): super(Problem, Problem).setup_parser_options(parser) # QQ precomputed initial condition? # IFNEED smooth initial u0 v_in incompatibility via modification of v_in (options normal, smoothed) parser.add_argument('--ic', help='Initial condition', choices=['zero', 'correct'], default='zero') parser.add_argument('-F', '--factor', help='Velocity scale factor', type=float, default=1.0) def initialize(self, V, Q, PS, D): super(Problem, self).initialize(V, Q, PS, D) print("IC type: " + self.ic) print("Velocity scale factor = %4.2f" % self.factor) reynolds = 728.761 * self.factor # TODO modify by nu_factor print("Computing with Re = %f" % reynolds) self.v_in = Function(V) print('Initializing error control') self.load_precomputed_bessel_functions(PS) self.solution = self.assemble_solution(0.0) # set constants for self.area = assemble(interpolate(Expression("1.0"), Q) * self.dsIn) # inflow area # womersley = steady + e^iCt, e^iCt has average 0 self.pg_normalization_factor.append(womersleyBC.average_analytic_pressure_grad(self.factor)) self.p_normalization_factor.append(norm( interpolate(womersleyBC.average_analytic_pressure_expr(self.factor), self.pSpace), norm_type='L2')) self.vel_normalization_factor.append(norm( interpolate(womersleyBC.average_analytic_velocity_expr(self.factor), self.vSpace), norm_type='L2')) # print('Normalisation factors (vel, p, pg):', self.vel_normalization_factor[0], self.p_normalization_factor[0], # self.pg_normalization_factor[0]) one = (interpolate(Expression('1.0'), Q)) self.outflow_area = assemble(one*self.dsOut) print('Outflow area:', self.outflow_area) def get_boundary_conditions(self, use_pressure_BC, v_space, p_space): # boundary parts: 1 walls, 2 inflow, 3 outflow # Boundary conditions bc0 = DirichletBC(v_space, (0.0, 0.0, 0.0), self.facet_function, 1) inflow = DirichletBC(v_space, self.v_in, self.facet_function, 2) bcu = [inflow, bc0] bcp = [] if use_pressure_BC: outflow = DirichletBC(p_space, 0.0, self.facet_function, 3) bcp = [outflow] return bcu, bcp def get_initial_conditions(self, function_list): out = [] for d in function_list: if d['type'] == 'v': f = Function(self.vSpace) if self.ic == 'correct': f = self.assemble_solution(d['time']) if d['type'] == 'p': f = Function(self.pSpace) if self.ic == 'correct': f = interpolate(womersleyBC.analytic_pressure(self.factor, d['time']), self.pSpace) out.append(f) return out def get_outflow_measures(self): return [self.dsOut] def get_outflow_measure_form(self): return self.dsOut def get_v_solution(self, t): v = self.assemble_solution(t) return v def get_p_solution(self, t): p = interpolate(womersleyBC.analytic_pressure(self.factor, t), self.pSpace) return p def update_time(self, actual_time, step_number): super(Problem, self).update_time(actual_time, step_number) if self.actual_time > 0.5 and int(round(self.actual_time * 1000)) % 1000 == 0: self.isWholeSecond = True seconds = int(round(self.actual_time)) self.second_list.append(seconds) self.N1 = seconds*self.stepsInSecond self.N0 = (seconds-1)*self.stepsInSecond else: self.isWholeSecond = False self.solution = self.assemble_solution(self.actual_time) # Update boundary condition self.tc.start('updateBC') self.v_in.assign(self.solution) self.tc.end('updateBC') # construct analytic pressure (used for computing pressure and force errors) self.tc.start('analyticP') analytic_pressure = womersleyBC.analytic_pressure(self.factor, self.actual_time) self.sol_p = interpolate(analytic_pressure, self.pSpace) self.tc.end('analyticP') self.tc.start('analyticVnorms') self.analytic_v_norm_L2 = norm(self.solution, norm_type='L2') self.analytic_v_norm_H1 = norm(self.solution, norm_type='H1') self.analytic_v_norm_H1w = sqrt(assemble((inner(grad(self.solution), grad(self.solution)) + inner(self.solution, self.solution)) * self.dsWall)) self.listDict['av_norm_L2']['list'].append(self.analytic_v_norm_L2) self.listDict['av_norm_H1']['list'].append(self.analytic_v_norm_H1) self.listDict['av_norm_H1w']['list'].append(self.analytic_v_norm_H1w) self.tc.end('analyticVnorms') def assemble_solution(self, t): # returns Womersley sol for time t if self.tc is not None: self.tc.start('assembleSol') sol = Function(self.solutionSpace) dofs2 = self.solutionSpace.sub(2).dofmap().dofs() # gives field of indices corresponding to z axis sol.assign(Constant(("0.0", "0.0", "0.0"))) # QQ not needed sol.vector()[dofs2] += self.factor * self.bessel_parabolic.vector().array() # parabolic part of sol for idx in range(8): # add modes of Womersley sol sol.vector()[dofs2] += self.factor * cos(self.coefs_exp[idx] * pi * t) * self.bessel_real[idx].vector().array() sol.vector()[dofs2] += self.factor * -sin(self.coefs_exp[idx] * pi * t) * self.bessel_complex[idx].vector().array() if self.tc is not None: self.tc.end('assembleSol') return sol # load precomputed Bessel functions def load_precomputed_bessel_functions(self, PS): f = HDF5File(mpi_comm_world(), 'precomputed/precomputed_' + self.precomputed_filename + '.hdf5', 'r') temp = toc() fce = Function(PS) f.read(fce, "parab") self.bessel_parabolic = Function(fce) for i in range(8): f.read(fce, "real%d" % i) self.bessel_real.append(Function(fce)) f.read(fce, "imag%d" % i) self.bessel_complex.append(Function(fce)) # plot(coefs_r_prec[i], title="coefs_r_prec", interactive=True) # reasonable values # plot(coefs_i_prec[i], title="coefs_i_prec", interactive=True) # reasonable values # plot(c0_prec,title="c0_prec",interactive=True) # reasonable values print("Loaded partial solution functions. Time: %f" % (toc() - temp)) def compute_err(self, is_tent, velocity, t): super(Problem, self).compute_err(is_tent, velocity, t) er_list_H1w = self.listDict['u2H1w' if is_tent else 'u_H1w']['list'] errorH1wall = sqrt(assemble((inner(grad(velocity - self.solution), grad(velocity - self.solution)) + inner(velocity - self.solution, velocity - self.solution)) * self.dsWall)) er_list_H1w.append(errorH1wall) print(' Relative H1wall error:', errorH1wall / self.analytic_v_norm_H1w) if self.isWholeSecond: self.listDict['u2H1w' if is_tent else 'u_H1w']['slist'].append( sqrt(sum([i*i for i in er_list_H1w[self.N0:self.N1]])/self.stepsInSecond)) def compute_functionals(self, velocity, pressure, t): super(Problem, self).compute_functionals(velocity, pressure, t) self.compute_force(velocity, pressure, t) def compute_force(self, velocity, pressure, t): self.tc.start('errorForce') I = Identity(3) # Identity tensor def T(p, v): return -p * I + 2.0 * self.nu * sym(grad(v)) error_force = sqrt( assemble(inner((T(pressure, velocity) - T(self.sol_p, self.solution)) * self.normal, (T(pressure, velocity) - T(self.sol_p, self.solution)) * self.normal) * self.dsWall)) an_force = sqrt(assemble(inner(T(self.sol_p, self.solution) * self.normal, T(self.sol_p, self.solution) * self.normal) * self.dsWall)) an_f_normal = sqrt(assemble(inner(inner(T(self.sol_p, self.solution) * self.normal, self.normal), inner(T(self.sol_p, self.solution) * self.normal, self.normal)) * self.dsWall)) error_f_normal = sqrt( assemble(inner(inner((T(self.sol_p, self.solution) - T(pressure, velocity)) * self.normal, self.normal), inner((T(self.sol_p, self.solution) - T(pressure, velocity)) * self.normal, self.normal)) * self.dsWall)) an_f_shear = sqrt( assemble(inner((I - outer(self.normal, self.normal)) * T(self.sol_p, self.solution) * self.normal, (I - outer(self.normal, self.normal)) * T(self.sol_p, self.solution) * self.normal) * self.dsWall)) error_f_shear = sqrt( assemble(inner((I - outer(self.normal, self.normal)) * (T(self.sol_p, self.solution) - T(pressure, velocity)) * self.normal, (I - outer(self.normal, self.normal)) * (T(self.sol_p, self.solution) - T(pressure, velocity)) * self.normal) * self.dsWall)) self.listDict['a_force_wall']['list'].append(an_force) self.listDict['a_force_wall_normal']['list'].append(an_f_normal) self.listDict['a_force_wall_shear']['list'].append(an_f_shear) self.listDict['force_wall']['list'].append(error_force) self.listDict['force_wall_normal']['list'].append(error_f_normal) self.listDict['force_wall_shear']['list'].append(error_f_shear) if self.isWholeSecond: self.listDict['force_wall']['slist'].append( sqrt(sum([i*i for i in self.listDict['force_wall']['list'][self.N0:self.N1]])/self.stepsInSecond)) print(' Relative force error:', error_force/an_force) self.tc.end('errorForce') def save_pressure(self, is_tent, pressure): super(Problem, self).save_pressure(is_tent, pressure) self.tc.start('computePG') # Report pressure gradient p_in = assemble((1.0/self.area) * pressure * self.dsIn) p_out = assemble((1.0/self.area) * pressure * self.dsOut) computed_gradient = (p_out - p_in)/20.0 # 20.0 is a length of a pipe NT should depend on mesh length (implement throuhg metadata or function of mesh) self.tc.end('computePG') self.tc.start('analyticP') analytic_gradient = womersleyBC.analytic_pressure_grad(self.factor, self.actual_time) if not is_tent: self.last_analytic_pressure_norm = norm(self.sol_p, norm_type='L2') self.listDict['ap_norm']['list'].append(self.last_analytic_pressure_norm) self.tc.end('analyticP') self.tc.start('errorP') error = errornorm(self.sol_p, pressure, norm_type="l2", degree_rise=0) self.listDict['p2' if is_tent else 'p']['list'].append(error) print("Normalized pressure error norm:", error/self.p_normalization_factor[0]) self.listDict['pg2' if is_tent else 'pg']['list'].append(computed_gradient) if not is_tent: self.listDict['apg']['list'].append(analytic_gradient) self.listDict['pgE2' if is_tent else 'pgE']['list'].append(computed_gradient-analytic_gradient) self.listDict['pgEA2' if is_tent else 'pgEA']['list'].append(abs(computed_gradient-analytic_gradient)) if self.isWholeSecond: for key in (['pgE2', 'p2'] if is_tent else ['pgE', 'p']): self.listDict[key]['slist'].append( sqrt(sum([i*i for i in self.listDict[key]['list'][self.N0:self.N1]])/self.stepsInSecond)) self.tc.end('errorP') if self.doSaveDiff: sol_pg_expr = Expression(("0", "0", "pg"), pg=analytic_gradient / self.pg_normalization_factor[0]) # sol_pg = interpolate(sol_pg_expr, self.pgSpace) # plot(sol_p, title="sol") # plot(pressure, title="p") # plot(pressure - sol_p, interactive=True, title="diff") # exit() self.pFunction.assign(pressure-self.sol_p) self.fileDict['p2D' if is_tent else 'pD']['file'] << self.pFunction
def test_time_step(): problem = problems.Crucible() boundaries = problem.wp_boundaries # The melting point of GaAs is 1511 K. average_temp = 1520.0 f = Constant(0.0) material = problem.subdomain_materials[problem.wpi] rho = material.density(average_temp) cp = material.specific_heat_capacity kappa = material.thermal_conductivity my_ds = Measure("ds")(subdomain_data=boundaries) # from dolfin import DirichletBC convection = None heat = maelstrom.heat.Heat( problem.Q, kappa, rho, cp, convection, source=Constant(0.0), dirichlet_bcs=problem.theta_bcs_d, neumann_bcs=problem.theta_bcs_n, robin_bcs=problem.theta_bcs_r, my_dx=dx, my_ds=my_ds, ) # create time stepper # stepper = parabolic.ExplicitEuler(heat) stepper = parabolic.ImplicitEuler(heat) # stepper = parabolic.Trapezoidal(heat) theta0 = project(Constant(average_temp), problem.Q) # theta0 = heat.solve_stationary() theta0.rename("theta0", "temperature") theta1 = Function(problem.Q) theta1 = Function(problem.Q) t = 0.0 dt = 1.0e-3 end_time = 10 * dt with XDMFFile("temperature.xdmf") as f: f.parameters["flush_output"] = True f.parameters["rewrite_function_mesh"] = False f.write(theta0, t) while t < end_time: theta1.assign(stepper.step(theta0, t, dt)) theta0.assign(theta1) t += dt # f.write(theta0, t) assert abs(maelstrom.helpers.average(theta0) - 1519.81) < 1.0e-2 return
def step(self, dt, u1, p1, u, p0, u_bcs, p_bcs, f0=None, f1=None, verbose=True, tol=1.0e-10 ): '''General pressure projection scheme as described in section 3.4 of :cite:`GMS06`. ''' # Some initial sanity checkups. assert dt > 0.0 # Define coefficients k = Constant(dt) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Compute tentative velocity step. # rho (u[-1] + (u.\nabla)u) = mu 1/r \div(r \nabla u) + rho g. with Message('Computing tentative velocity'): solver = NewtonSolver() solver.parameters['maximum_iterations'] = 5 solver.parameters['absolute_tolerance'] = tol solver.parameters['relative_tolerance'] = 0.0 solver.parameters['report'] = True # The nonlinear term makes the problem generally nonsymmetric. solver.parameters['linear_solver'] = 'gmres' # If the nonsymmetry is too strong, e.g., if u[-1] is large, then # AMG preconditioning might not work very well. # Use HYPRE-Euclid instead of ILU for parallel computation. solver.parameters['preconditioner'] = 'hypre_euclid' solver.parameters['krylov_solver']['relative_tolerance'] = tol solver.parameters['krylov_solver']['absolute_tolerance'] = 0.0 solver.parameters['krylov_solver']['maximum_iterations'] = 1000 solver.parameters['krylov_solver']['monitor_convergence'] = verbose ui = Function(self.W) step_problem = \ TentativeVelocityProblem(ui, self.theta, self.rho, self.mu, u, p0, k, u_bcs, f0, f1, stabilization=self.stabilization, dx=self.dx ) # Take u[-1] as initial guess. ui.assign(u[-1]) solver.solve(step_problem, ui.vector()) #div_u = 1/r * div(r*ui) #plot(div_u) #interactive() # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - with Message('Computing pressure correction'): # The pressure correction is based on the update formula # # ( dphi/dr ) # rho/dt (u_{n+1}-u*) + ( dphi/dz ) = 0 # (1/r dphi/dtheta) # # with # # phi = p_{n+1} - p* # # and # # 1/r d/dr (r ur_{n+1}) # + d/dz ( uz_{n+1}) # + 1/r d/dtheta( utheta_{n+1}) = 0 # # With the assumption that u does not change in the direction # theta, one derives # # - 1/r * div(r * \nabla phi) = 1/r * rho/dt div(r*(u_{n+1} - u*)) # - 1/r * n. (r * \nabla phi) = 1/r * rho/dt n.(r*(u_{n+1} - u*)) # # In its weak form, this is # # \int r * \grad(phi).\grad(q) *2*pi = # - rho/dt \int div(r*u*) q *2*p # - rho/dt \int_Gamma n.(r*(u_{n+1}-u*)) q *2*pi. # # (The terms 1/r cancel with the volume elements 2*pi*r.) # If Dirichlet boundary conditions are applied to both u* and u_n # (the latter in the final step), the boundary integral vanishes. # alpha = 1.0 #alpha = 3.0 / 2.0 rotational_form = False self._pressure_poisson(p1, p0, self.mu, ui, self.rho * alpha * ui / k, p_bcs=p_bcs, rotational_form=rotational_form, tol=tol, verbose=verbose ) #plot(ui, title='u intermediate') ##plot(f, title='f') ##plot(ui[1], title='u intermediate[1]') #plot(div(ui), title='div(u intermediate)') #plot(p1, title='p1') #interactive() # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Velocity correction. # U = u[-1] - dt/rho \nabla (p1-p0). with Message('Computing velocity correction'): u = TrialFunction(self.W) v = TestFunction(self.W) a3 = dot(u, v) * dx phi = Function(self.P) phi.assign(p1) if p0: phi -= p0 if rotational_form: r = Expression('x[0]', degree=1, domain=self.W.mesh()) div_ui = 1/r * (r * ui[0]).dx(0) + ui[1].dx(1) phi += self.mu * div_ui L3 = dot(ui, v) * dx \ - k / self.rho * (phi.dx(0) * v[0] + phi.dx(1) * v[1]) * dx # Jacobi preconditioning for mass matrix is order-optimal. solve( a3 == L3, u1, bcs=u_bcs, solver_parameters={ 'linear_solver': 'iterative', 'symmetric': True, 'preconditioner': 'jacobi', 'krylov_solver': {'relative_tolerance': tol, 'absolute_tolerance': 0.0, 'maximum_iterations': 100, 'monitor_convergence': verbose} } ) #u = project(ui - k/rho * grad(phi), V) # div_u = 1/r * div(r*u) r = Expression('x[0]', degree=1, domain=self.W.mesh()) div_u1 = 1.0 / r * (r * u1[0]).dx(0) + u1[1].dx(1) info('||u||_div = %e' % sqrt(assemble(div_u1 * div_u1 * dx))) #plot(div_u) #interactive() ## Ha! TODO remove #u1.vector()[:] = ui.vector() return
def get_lorentz_joule(problem, input_voltages, show=False): submesh_workpiece = problem.W.mesh() subdomain_indices = problem.subdomain_materials.keys() info("Input voltages:") info(repr(input_voltages)) if input_voltages is None: return None, Constant(0.0) # Merge coil rings with voltages. coils = [ {"rings": coil_domain, "c_type": "voltage", "c_value": voltage} for coil_domain, voltage in zip(problem.coil_domains, input_voltages) ] # Build subdomain parameter dictionaries for Maxwell mu_const = { i: problem.subdomain_materials[i].magnetic_permeability for i in subdomain_indices } sigma_const = { i: problem.subdomain_materials[i].electrical_conductivity for i in subdomain_indices } # Function space for magnetic scalar potential, Lorentz force etc. V = FunctionSpace(problem.mesh, "CG", 1) # Compute the magnetic field. # The Maxwell equations depend on two parameters that change during the # computation: (a) the temperature, and (b) the velocity field u0. We # assume though that changes in either of the two will only marginally # influence the magnetic field. Consequently, we precompute all associated # values. dx_subdomains = Measure("dx", subdomain_data=problem.subdomains) with Message("Computing magnetic field..."): Phi, voltages = cmx.compute_potential( coils, V, dx_subdomains, mu_const, sigma_const, problem.omega, convections={} # io_submesh=submesh_workpiece ) # Get resulting Lorentz force. lorentz = cmx.compute_lorentz(Phi, problem.omega, sigma_const[problem.wpi]) # Show the Lorentz force in the workpiece. # W_element = VectorElement('CG', submesh_workpiece.ufl_cell(), 1) # First project onto the entire mesh, then onto the submesh; see bug # <https://bitbucket.org/fenics-project/dolfin/issues/869/projecting-grad-onto-submesh-error>. W = VectorFunctionSpace(problem.mesh, "CG", 1) pl = project(lorentz, W) W2 = VectorFunctionSpace(submesh_workpiece, "CG", 1) pl = project(pl, W2) pl.rename("Lorentz force", "Lorentz force") with XDMFFile(submesh_workpiece.mpi_comm(), "lorentz.xdmf") as f: f.parameters["flush_output"] = True f.write(pl) if show: tri = plot(pl, title="Lorentz force") plt.colorbar(tri) plt.show() # Get Joule heat source. joule = cmx.compute_joule( Phi, voltages, problem.omega, sigma_const, mu_const, subdomain_indices ) if show: # Show Joule heat source. submesh = SubMesh(problem.mesh, problem.subdomains, problem.wpi) W_submesh = FunctionSpace(submesh, "CG", 1) jp = Function(W_submesh, name="Joule heat source") jp.assign(project(joule[problem.wpi], W_submesh)) tri = plot(jp) plt.title("Joule heat source") plt.colorbar(tri) plt.show() joule_wpi = joule[problem.wpi] # To work around bug # <https://bitbucket.org/fenics-project/dolfin/issues/869/projecting-grad-onto-submesh-error>. # return the projection `pl` and not `lorentz` itself. # TODO remove this workaround return pl, joule_wpi, Phi
def compute_tentative_velocity( time_step_method, rho, mu, u, p0, dt, u_bcs, f, W, my_dx, tol ): """Compute the tentative velocity via .. math:: \\rho (u_0 + (u\\cdot\\nabla)u) = \\mu \\frac{1}{r} \\div(r \\nabla u) + \\rho g. """ class TentativeVelocityProblem(NonlinearProblem): def __init__(self, ui, time_step_method, rho, mu, u, p0, dt, bcs, f, my_dx): super(TentativeVelocityProblem, self).__init__() W = ui.function_space() v = TestFunction(W) self.bcs = bcs r = SpatialCoordinate(ui.function_space().mesh())[0] def me(uu, ff): return _momentum_equation(uu, v, p0, ff, rho, mu, my_dx) self.F0 = rho * dot(ui - u[0], v) / dt * 2 * pi * r * my_dx if time_step_method == "forward euler": self.F0 += me(u[0], f[0]) elif time_step_method == "backward euler": self.F0 += me(ui, f[1]) else: assert ( time_step_method == "crank-nicolson" ), "Unknown time stepper '{}'".format( time_step_method ) self.F0 += 0.5 * (me(u[0], f[0]) + me(ui, f[1])) self.jacobian = derivative(self.F0, ui) self.reset_sparsity = True return # pylint: disable=unused-argument def F(self, b, x): # We need to evaluate F at x, so we have to make sure that self.F0 # is assembled for ui=x. We could use a self.ui and set # # self.ui.vector()[:] = x # # here. One way around this copy is to instantiate this class with # the same Function ui that is then used for the solver.solve(). assemble(self.F0, tensor=b, form_compiler_parameters={"optimize": True}) for bc in self.bcs: bc.apply(b, x) return def J(self, A, x): # We can ignore x; see comment at F(). assemble( self.jacobian, tensor=A, form_compiler_parameters={"optimize": True} ) for bc in self.bcs: bc.apply(A) self.reset_sparsity = False return solver = NewtonSolver() solver.parameters["maximum_iterations"] = 10 solver.parameters["absolute_tolerance"] = tol solver.parameters["relative_tolerance"] = 0.0 solver.parameters["report"] = True # While GMRES+ILU converges if the time step is small enough, increasing # the time step slows down convergence dramatically in some cases. This # makes the step fail, and the adaptive time stepper will decrease the step # size. This size can be _very_ small such that simulation take forever. # For now, just use a direct solver. Choose UMFPACK over SuperLU since the # docker image doesn't contain SuperLU yet, cf. # <https://bitbucket.org/fenics-project/docker/issues/64/add-superlu>. # TODO come up with an appropriate GMRES preconditioner here solver.parameters["linear_solver"] = "umfpack" ui = Function(W) step_problem = TentativeVelocityProblem( ui, time_step_method, rho, mu, u, p0, dt, u_bcs, f, my_dx ) # Take u[0] as initial guess. ui.assign(u[0]) solver.solve(step_problem, ui.vector()) # Make sure ui is from W. This should happen anyways, but somehow doesn't. # TODO find out why not ui = project(ui, W) # div_u = 1/r * div(r*ui) return ui
def test(show=False): problem = problems.Crucible() # The voltage is defined as # # v(t) = Im(exp(i omega t) v) # = Im(exp(i (omega t + arg(v)))) |v| # = sin(omega t + arg(v)) |v|. # # Hence, for a lagging voltage, arg(v) needs to be negative. voltages = [ 38.0 * numpy.exp(-1j * 2 * pi * 2 * 70.0 / 360.0), 38.0 * numpy.exp(-1j * 2 * pi * 1 * 70.0 / 360.0), 38.0 * numpy.exp(-1j * 2 * pi * 0 * 70.0 / 360.0), 25.0 * numpy.exp(-1j * 2 * pi * 0 * 70.0 / 360.0), 25.0 * numpy.exp(-1j * 2 * pi * 1 * 70.0 / 360.0), ] lorentz, joule, Phi = get_lorentz_joule(problem, voltages, show=show) # Some assertions ref = 1.4627674791126285e-05 assert abs(norm(Phi[0], "L2") - ref) < 1.0e-3 * ref ref = 3.161363929287592e-05 assert abs(norm(Phi[1], "L2") - ref) < 1.0e-3 * ref # ref = 12.115309575057681 assert abs(norm(lorentz, "L2") - ref) < 1.0e-3 * ref # ref = 1406.336109054347 V = FunctionSpace(problem.submesh_workpiece, "CG", 1) jp = project(joule, V) jp.rename("s", "Joule heat source") assert abs(norm(jp, "L2") - ref) < 1.0e-3 * ref # check_currents = False # if check_currents: # r = SpatialCoordinate(problem.mesh)[0] # begin('Currents computed after the fact:') # k = 0 # with XDMFFile('currents.xdmf') as xdmf_file: # for coil in coils: # for ii in coil['rings']: # J_r = sigma[ii] * ( # voltages[k].real/(2*pi*r) + problem.omega * Phi[1] # ) # J_i = sigma[ii] * ( # voltages[k].imag/(2*pi*r) - problem.omega * Phi[0] # ) # alpha = assemble(J_r * dx(ii)) # beta = assemble(J_i * dx(ii)) # info('J = {:e} + i {:e}'.format(alpha, beta)) # info( # '|J|/sqrt(2) = {:e}'.format( # numpy.sqrt(0.5 * (alpha**2 + beta**2)) # )) # submesh = SubMesh(problem.mesh, problem.subdomains, ii) # V1 = FunctionSpace(submesh, 'CG', 1) # # Those projections may take *very* long. # # TODO find out why # j_v1 = [ # project(J_r, V1), # project(J_i, V1) # ] # # show=Trueplot(j_v1[0], title='j_r') # # plot(j_v1[1], title='j_i') # current = project(as_vector(j_v1), V1*V1) # current.rename('j{}'.format(ii), 'current {}'.format(ii)) # xdmf_file.write(current) # k += 1 # end() filename = "./maxwell.xdmf" with XDMFFile(filename) as xdmf_file: xdmf_file.parameters["flush_output"] = True xdmf_file.parameters["rewrite_function_mesh"] = False # Store phi info("Writing out Phi to {}...".format(filename)) V = FunctionSpace(problem.mesh, "CG", 1) phi = Function(V, name="phi") Phi0 = project(Phi[0], V) Phi1 = project(Phi[1], V) omega = problem.omega for t in numpy.linspace(0.0, 2 * pi / omega, num=100, endpoint=False): # Im(Phi * exp(i*omega*t)) phi.vector().zero() phi.vector().axpy(sin(problem.omega * t), Phi0.vector()) phi.vector().axpy(cos(problem.omega * t), Phi1.vector()) xdmf_file.write(phi, t) # Show the resulting magnetic field # # B_r = -dphi/dz, # B_z = 1/r d(rphi)/dr. # r = SpatialCoordinate(problem.mesh)[0] g = 1.0 / r * grad(r * Phi[0]) V_element = FiniteElement("CG", V.mesh().ufl_cell(), 1) VV = FunctionSpace(V.mesh(), V_element * V_element) B_r = project(as_vector((-g[1], g[0])), VV) g = 1 / r * grad(r * Phi[1]) B_i = project(as_vector((-g[1], g[0])), VV) info("Writing out B to {}...".format(filename)) B = Function(VV) B.rename("B", "magnetic field") if abs(problem.omega) < DOLFIN_EPS: B.assign(B_r) xdmf_file.write(B) # plot(B_r, title='Re(B)') # plot(B_i, title='Im(B)') else: # Write those out to a file. lspace = numpy.linspace( 0.0, 2 * pi / problem.omega, num=100, endpoint=False ) for t in lspace: # Im(B * exp(i*omega*t)) B.vector().zero() B.vector().axpy(sin(problem.omega * t), B_r.vector()) B.vector().axpy(cos(problem.omega * t), B_i.vector()) xdmf_file.write(B, t) filename = "./lorentz-joule.xdmf" info("Writing out Lorentz force and Joule heat source to {}...".format(filename)) with XDMFFile(filename) as xdmf_file: xdmf_file.write(lorentz, 0.0) # xdmf_file.write(jp, 0.0) return
class FluxModule(object): """ Module responsible for calculating scalar fluxes and keff eigenvalue """ def __init__(self, PD, DD, verbosity): """ Constructor :param ProblemData PD: Problem information and various mesh-region <-> xs-material mappings :param Discretization DD: Discretization data :param int verbosity: Verbosity level. """ super(FluxModule, self).__init__() self.verb = verbosity self.print_prefix = "" self.mat_file_name = dict() self.PD = PD self.DD = DD self.BC = PD.bc try: self.fixed_source_problem = PD.fixed_source_problem self.eigenproblem = PD.eigenproblem except AttributeError: PD.distribute_material_data(DD.cell_regions, DD.M) self.A = PETScMatrix() # unused in case of an eigenvalue problem self.Q = PETScVector() # used only for saving the algebraic system self.rows_A = None self.cols_A = None self.vals_A = None self.rows_B = None self.cols_B = None self.vals_B = None if self.fixed_source_problem: self.fixed_source = Function(self.DD.V0) self.vals_Q = None # multigroup scalar fluxes self.phi_mg = [] for g in range(self.DD.G): phig = Function(self.DD.Vphi1) phig.rename("phi","phi_g{}".format(g)) self.phi_mg.append(phig) self.sln = Function(DD.V) self.sln_vec = as_backend_type(self.sln.vector()) self.local_sln_size = self.sln_vec.local_size() # auxiliary function for storing various DG(0) quantities (cross sections, group-integrated reaction rates, etc.) self.R = Function(self.DD.V0) # fission spectrum if 'chi' in self.PD.used_xs: self.chi = Function(self.DD.V0) else: self.chi = None if 'eSf' in self.PD.used_xs: self.E = numpy.zeros(self.DD.local_ndof0) else: self.E = None self.up_to_date = {"flux" : False, "cell_powers" : False} self.bnd_matrix_form = None self.parameters = parameters["flux_module"] if self.eigenproblem: assert self.parameters.has_parameter_set("eigensolver") self.eigen_params = self.parameters["eigensolver"] self.adaptive_eig_tol_end = 0 self.B = PETScMatrix() self.keff = 1 self.prev_keff = self.keff self.set_initial_approximation(numpy.random.random(self.local_sln_size)) self.update_phi() self.u = TrialFunction(self.DD.V) self.v = TestFunction(self.DD.V) self.v0 = TestFunction(self.DD.V0) self.phig = Function(self.DD.Vphi1) # single group scalar flux self.cell_RR_form = self.R * self.phig * self.v0 * dx self._cell_RRg_vector = PETScVector() self.vis_folder = os.path.join(self.PD.out_folder, "FLUX") self.vis_files = dict() var = "cell_powers" self.vis_files[var] = File(os.path.join(self.vis_folder, var+".pvd"), "compressed") var = "flux" self.vis_files[var] = [ File(os.path.join(self.vis_folder, "{}_g{}.pvd".format(var, g)), "compressed") for g in range(self.DD.G) ] variables = self.parameters["saving"].iterkeys() self.save_folder = { k : os.path.join(self.PD.out_folder, k.upper()) for k in variables } # noinspection PyTypeChecker def save_algebraic_system(self, mat_file_name=None, it=0): if not mat_file_name: mat_file_name = {} try: should_save = divmod(it, self.parameters["saving"]["algebraic_system"])[1] == 0 except ZeroDivisionError: should_save = False if not should_save: return if self.verb > 1: print0("Saving the created matrices.") timer = Timer("COO representation + matrix saving") self.rows_A, self.cols_A, self.vals_A = coo_rep_on_zero(self.A, self.rows_A, self.cols_A, self.vals_A) if self.eigenproblem: self.rows_B, self.cols_B, self.vals_B = coo_rep_on_zero(self.B, self.rows_B, self.cols_B, self.vals_B) elif self.fixed_source_problem: self.vals_Q = self.Q.gather_on_zero() if MPI.rank(comm) == 0: if not mat_file_name: mat_file_name['A'] = 'A' if self.eigenproblem: mat_file_name['B'] = 'B' if self.fixed_source_problem: mat_file_name['Q'] = 'Q' for k,v in mat_file_name.iteritems(): mat_file_name[k] = os.path.join(self.save_folder["algebraic_system"], v) if self.verb > 2: print0( self.print_prefix + " Saving A to " + mat_file_name['A']+'.mat' ) savemat(mat_file_name['A']+'.mat', { 'rows':numpy.asarray(self.rows_A, dtype='d'), 'cols':numpy.asarray(self.cols_A, dtype='d'), 'vals':self.vals_A }, do_compression=True) if self.eigenproblem: if self.verb > 2: print0( self.print_prefix + " Saving B to " + mat_file_name['B']+'.mat' ) savemat(mat_file_name['B']+'.mat', { 'rows':numpy.asarray(self.rows_B, dtype='d'), 'cols':numpy.asarray(self.cols_B, dtype='d'), 'vals':self.vals_B }, do_compression=True) elif self.fixed_source_problem: if self.verb > 2: print0( self.print_prefix + " Saving Q to " + mat_file_name['Q']+'.mat' ) savemat(mat_file_name['Q']+'.mat', { 'vals':self.vals_Q }, do_compression=True) def update(self, it): # Handle adaptive eigensolver convergence tolerance strengthening if self.eigen_params["adaptive_eig_tol_start"] > 0: if self.adaptive_eig_tol_end == 0: # First iteration - set the adaptive tolerance strengthening if self.verb > 0: s = "Eigenvalue tolerance will be decreased adaptively from {} to {} during adaptivity iterations" print0(s.format(self.eigen_params["adaptive_eig_tol_start"], self.eigen_params["tol"])) self.adaptive_eig_tol_end = self.eigen_params["tol"] self.eigen_params["tol"] = self.eigen_params["adaptive_eig_tol_start"] else: # Subsequent iterations - strengthen the eigensolver convergence tolerance each adaptivity step until # adaptive_eig_tol_end is reached if self.eigen_params["tol"] > self.adaptive_eig_tol_end: self.eigen_params["tol"] /= 10 def update_phi(self): raise NotImplementedError("Abstract scalar flux update method -- must be overriden in specific flux modules.") def get_dg0_fluxes(self): """ Get flux interpolated at DG(0) dofs. :return: List of `G` arrays of group-fluxes at local DG(0) dofs. :rtype: list[ndarray] """ dg0_fluxes = list() for g in xrange(self.DD.G): dg0_fluxes.append(interpolate(self.phig, self.DD.V0).vector().get_local()) return dg0_fluxes def visualize(self, it=0): var = "cell_powers" try: should_vis = divmod(it, self.parameters["visualization"][var])[1] == 0 except ZeroDivisionError: should_vis = False if should_vis: if not self.up_to_date[var]: self.calculate_cell_powers() else: qfun = Function(self.DD.V0) qfun.vector()[:] = self.E qfun.rename("q", "Power") self.vis_files["cell_powers"] << (qfun, float(it)) var = "flux" try: should_vis = divmod(it, self.parameters["visualization"][var])[1] == 0 except ZeroDivisionError: should_vis = False if should_vis: if not self.up_to_date[var]: self.update_phi() for g in xrange(self.DD.G): self.vis_files["flux"][g] << (self.phi_mg[g], float(it)) def print_results(self): if self.verb > 2: if self.eigenproblem: print0(self.print_prefix + "keff = {}".format(self.keff)) print0(self.print_prefix + "Residual norm: {}".format(self.residual_norm())) def eigenvalue_residual_norm(self, norm_type='l2'): r = PETScVector() y = PETScVector() self.B.mult(self.sln_vec, r) self.A.mult(self.sln_vec, y) r.apply("insert") y.apply("insert") r -= self.keff * y return norm(r, norm_type) def fixed_source_residual_norm(self, norm_type='l2'): y = PETScVector() self.A.mult(self.sln_vec, y) y.apply("insert") return norm(self.Q - y, norm_type) def residual_norm(self, norm_type='l2'): if self.eigenproblem: return self.eigenvalue_residual_norm(norm_type) else: return self.fixed_source_residual_norm(norm_type) # FIXME: It doesn't work to define eigensolver only once in the c'tor (we use pointers to matrices, so it should...). def solve_keff(self, it=0): assert self.A assert self.B assert self.sln_vec self.prev_keff = self.keff eigensolver = backend_ext_module.GeneralizedEigenSolver(self.A, self.B) if eigensolver.parameters["adaptive_shifting"]: eigensolver.set_shift_AB(1. / self.keff) if eigensolver.parameters["inner_solver_adaptive_tol_multiplier"] > 0: PETScOptions.set("st_ksp_atol", eigensolver.parameters["inner_solver_adaptive_tol_multiplier"] * self.eigenvalue_residual_norm()) if self.verb > 1: print0(self.print_prefix + "Solving ({})...".format(eigensolver.get_actual_problem_description())) solution_timer = Timer("Solver") eigensolver.set_initial_space(self.sln_vec) eigensolver.solve() self.keff = 1. / eigensolver.get_first_eigenpair_AB(self.sln_vec) # This is needed in parallel (why not in serial?) self.sln.vector()[:] = self.sln_vec.array() solution_timer.stop() if MPI.rank(comm) == 0: if self.verb > 1: print "\n" + self.print_prefix + "keff = {}\n".format(self.keff) try: should_save = divmod(it, self.parameters["saving"]["results"])[1] == 0 except ZeroDivisionError: should_save = False if should_save: savemat(os.path.join(self.save_folder["results"], "eigensolver_out.mat"), {'x': self.sln_vec.array(), 'keff': self.keff}) def solve_fixed_source(self, it=0): assert self.A assert self.Q dolfin_solve(self.A, self.sln_vec, self.Q, "gmres", "petsc_amg") def assemble_algebraic_system(self): raise NotImplemented def solve(self, it=0): """ Pick the appropriate solver for current problem (eigen/fixed-source) and solve the problem (i.e., update solution vector and possibly the eigenvalue ). """ self.assemble_algebraic_system() self.save_algebraic_system(it) if self.eigenproblem: self.solve_keff(it) else: self.solve_fixed_source(it) self.up_to_date = {k : False for k in self.up_to_date.iterkeys()} def set_initial_approximation(self, x0): self.sln_vec[:] = x0 def calculate_cell_powers(self): """ Calculates cell-integrated powers (sets :attr:`E`). Also performs normalization to the specified core(-fraction) power (:attr:`core.power`). Note that the array is ordered by the associated DG(0) dof, not by the cell index in the mesh. """ q_calc_timer = Timer("FM: Calculation of cell powers") ass_timer = Timer("FM: Assemble cell powers") if self.verb > 2: print0(self.print_prefix + " calculating cell-wise powers.") self.E.fill(0) if not self.up_to_date["flux"]: self.update_phi() for g in xrange(self.DD.G): self.PD.get_xs('eSf', self.R, g) ass_timer.start() self.phig.assign(self.phi_mg[g]) assemble(self.cell_RR_form, tensor=self._cell_RRg_vector) ass_timer.stop() self.E += self._cell_RRg_vector.get_local() self.up_to_date["cell_powers"] = True self.normalize_cell_powers() def normalize_cell_powers(self): assert self.up_to_date["cell_powers"] P = MPI.sum(numpy.sum(self.E)) if self.verb > 2: print0(self.print_prefix + " desired power: " + str(self.PD.core.power)) print0(self.print_prefix + " actual power: " + str(P)) print0("") assert (P > 0) self.E *= self.PD.core.power / P # noinspection PyAttributeOutsideInit def calculate_cell_reaction_rate(self, reaction_xs, rr_vect=None, return_xs_arrays=False): """ Calculates cell-integrated reaction rate and optionally returns the xs's needed for the calculation. Note that the array is ordered by the associated DG(0) dof, not by the cell index in the mesh. :param str reaction_xs: Reaction cross-section id. :param ndarray rr_vect: (optional) Output vector. If not given, reaction rate vector of the FluxModule class (:attr:`cell_RR`) will be updated. Must be pre-allocated to store :attr:`Discretization.local_ndof0` elements, but doesn't need to be pre-set to any value. :param bool return_xs_arrays: (optional) If True, a list that contains for each group the cell (dof) values array of the DG(0) representation of the specified reaction xs will be created and returned. :return: List with xs value arrays for each group if `return_xs_arrays == True`, None otherwise (see above) :rtype: list[ndarray] | None """ if reaction_xs not in self.PD.used_xs: warning("Attempted to calculate cell-wise reaction rate for reaction without loaded cross-section (skipping).") return if self.verb > 1: print0(self.print_prefix + "Calculating cell-wise '{}' reaction rate.".format(reaction_xs)) if not rr_vect: try: self.cell_RR except AttributeError: self.cell_RR = numpy.zeros(self.DD.local_ndof0) finally: rr_vect = self.cell_RR else: assert rr_vect.size == self.DD.local_ndof0 if return_xs_arrays: xs_arrays = [None] * self.DD.G else: xs_arrays = None rr_vect.fill(0) if not self.up_to_date["cell_powers"]: self.update_phi() calc_timer = Timer("FM: Calculation of '{}' reaction rate".format(reaction_xs)) ass_timer = Timer("FM: Assemble '{}' reaction rate".format(reaction_xs)) for g in xrange(self.DD.G): self.PD.get_xs(reaction_xs, self.R, g) if xs_arrays: xs_arrays[g] = self.R.vector().get_local().copy() ass_timer.start() self.phig.assign(self.phi_mg[g]) assemble(self.cell_RR_form, tensor=self._cell_RRg_vector) ass_timer.stop() rr_vect += self._cell_RRg_vector.get_local() return xs_arrays
class ObjectiveFunctional(LinearOperator): """ Provides data misfit, gradient and Hessian information for the data misfit part of a time-independent symmetric inverse problem. """ __metaclass__ = abc.ABCMeta # Instantiation def __init__(self, V, Vm, bc, bcadj, \ RHSinput=[], ObsOp=[], UD=[], Regul=[], Data=[], plot=False, \ mycomm=None): # Define test, trial and all other functions self.trial = TrialFunction(V) self.test = TestFunction(V) self.mtrial = TrialFunction(Vm) self.mtest = TestFunction(Vm) self.rhs = Function(V) self.m = Function(Vm) self.mcopy = Function(Vm) self.srchdir = Function(Vm) self.delta_m = Function(Vm) self.MG = Function(Vm) self.MGv = self.MG.vector() self.Grad = Function(Vm) self.Gradnorm = 0.0 self.lenm = len(self.m.vector().array()) self.u = Function(V) self.ud = Function(V) self.diff = Function(V) self.p = Function(V) # Store other info: self.ObsOp = ObsOp self.UD = UD self.reset() # Initialize U, C and E to [] self.Data = Data self.GN = 1.0 # GN = 0.0 => GN Hessian; = 1.0 => full Hessian # Define weak forms to assemble A, C and E self._wkforma() self._wkformc() self._wkforme() # Operators and bc LinearOperator.__init__(self, self.delta_m.vector(), \ self.delta_m.vector()) self.bc = bc self.bcadj = bcadj self._assemble_solverM(Vm) self.assemble_A() self.assemble_RHS(RHSinput) self.Regul = Regul self.regparam = 1.0 if Regul != []: self.PD = self.Regul.isPD() # Counters, tolerances and others self.nbPDEsolves = 0 # Updated when solve_A called self.nbfwdsolves = 0 # Counter for plots self.nbadjsolves = 0 # Counter for plots # MPI: self.mycomm = mycomm def copy(self): """Define a copy method""" V = self.trial.function_space() Vm = self.mtrial.function_space() newobj = self.__class__(V, Vm, self.bc, self.bcadj, [], self.ObsOp, \ self.UD, self.Regul, self.Data, False) newobj.RHS = self.RHS newobj.update_m(self.m) return newobj def mult(self, mhat, y): """mult(self, mhat, y): do y = Hessian * mhat member self.GN sets full Hessian (=1.0) or GN Hessian (=0.0)""" N = self.Nbsrc # Number of sources y[:] = np.zeros(self.lenm) for C, E in zip(self.C, self.E): C.transpmult(mhat, self.rhs.vector()) if self.bcadj is not None: self.bcadj.apply(self.rhs.vector()) self.solve_A(self.u.vector(), -self.rhs.vector()) E.transpmult(mhat, self.rhs.vector()) Etmhat = self.rhs.vector().array() self.rhs.vector().axpy(1.0, self.ObsOp.incradj(self.u)) if self.bcadj is not None: self.bcadj.apply(self.rhs.vector()) self.solve_A(self.p.vector(), -self.rhs.vector()) y.axpy(1.0 / N, C * self.p.vector()) y.axpy(self.GN / N, E * self.u.vector()) y.axpy(self.regparam, self.Regul.hessian(mhat)) # Getters def getm(self): return self.m def getmarray(self): return self.m.vector().array() def getmcopyarray(self): return self.mcopy.vector().array() def getVm(self): return self.mtrial.function_space() def getMGarray(self): return self.MG.vector().array() def getMGvec(self): return self.MGv def getGradarray(self): return self.Grad.vector().array() def getGradnorm(self): return self.Gradnorm def getsrchdirarray(self): return self.srchdir.vector().array() def getsrchdirvec(self): return self.srchdir.vector() def getsrchdirnorm(self): return np.sqrt( (self.MM * self.getsrchdirvec()).inner(self.getsrchdirvec())) def getgradxdir(self): return self.gradxdir def getcost(self): return self.cost, self.misfit, self.regul def getprecond(self): return self.Regul.getprecond() # Prec = PETScKrylovSolver("richardson", "amg") # Prec.parameters["maximum_iterations"] = 1 # Prec.parameters["error_on_nonconvergence"] = False # Prec.parameters["nonzero_initial_guess"] = False # Prec.set_operator(self.Regul.get_precond()) # return Prec def getMass(self): return self.MM # Setters def setsrchdir(self, arr): self.srchdir.vector()[:] = arr def setgradxdir(self, valueloc): """Sum all local results for Grad . Srch_dir""" try: valueglob = MPI.sum(self.mycomm, valueloc) except: valueglob = valueloc self.gradxdir = valueglob # Solve def solvefwd(self, cost=False): """Solve fwd operators for given RHS""" self.nbfwdsolves += 1 if cost: self.misfit = 0.0 self.U = [] self.C = [] for ii, rhs in enumerate(self.RHS): self.solve_A(self.u.vector(), rhs) u_obs, noiselevel = self.ObsOp.obs(self.u) self.U.append(u_obs) if cost: self.misfit += self.ObsOp.costfct(u_obs, self.UD[ii]) self.C.append(assemble(self.c)) if cost: self.misfit /= len(self.U) self.regul = self.Regul.cost(self.m) self.cost = self.misfit + self.regparam * self.regul def solvefwd_cost(self): """Solve fwd operators for given RHS and compute cost fct""" self.solvefwd(True) def solveadj(self, grad=False): """Solve adj operators""" self.nbadjsolves += 1 self.Nbsrc = len(self.UD) if grad: self.MG.vector().zero() self.E = [] for ii, C in enumerate(self.C): self.ObsOp.assemble_rhsadj(self.U[ii], self.UD[ii], \ self.rhs, self.bcadj) self.solve_A(self.p.vector(), self.rhs.vector()) self.E.append(assemble(self.e)) if grad: self.MG.vector().axpy(1.0 / self.Nbsrc, C * self.p.vector()) if grad: self.MG.vector().axpy(self.regparam, self.Regul.grad(self.m)) self.solverM.solve(self.Grad.vector(), self.MG.vector()) self.Gradnorm = np.sqrt(self.Grad.vector().inner(self.MG.vector())) def solveadj_constructgrad(self): """Solve adj operators and assemble gradient""" self.solveadj(True) # Assembler def assemble_A(self): """Assemble operator A(m)""" self.A = assemble(self.a) if self.bc is not None: self.bc.apply(self.A) compute_eigfenics(self.A, 'eigA.txt') self.set_solver() def solve_A(self, b, f): """Solve system of the form A.b = f, with b and f in form to be used in solver.""" self.solver.solve(b, f) self.nbPDEsolves += 1 def assemble_RHS(self, RHSin): """Assemble RHS for fwd solve""" if RHSin == []: self.RHS = None else: self.RHS = [] for rhs in RHSin: if isinstance(rhs, Expression): L = rhs * self.test * dx b = assemble(L) if self.bc is not None: self.bc.apply(b) self.RHS.append(b) elif isinstance(rhs, GenericVector): self.RHS.append(rhs) else: raise WrongInstanceError( "rhs should be an Expression or a GenericVector") def _assemble_solverM(self, Vm): self.MM = assemble(inner(self.mtrial, self.mtest) * dx) self.solverM = PETScKrylovSolver('cg', 'jacobi') self.solverM.parameters["maximum_iterations"] = 1000 self.solverM.parameters["relative_tolerance"] = 1e-12 self.solverM.parameters["error_on_nonconvergence"] = True self.solverM.parameters["nonzero_initial_guess"] = False # self.solverM = LUSolver() # self.solverM.parameters['reuse_factorization'] = True # self.solverM.parameters['symmetric'] = True self.solverM.set_operator(self.MM) # Update param def update_Data(self, Data): """Update Data member""" self.Data = Data self.assemble_A() self.reset() def update_m(self, m): """Update values of parameter m""" if isinstance(m, np.ndarray): self.m.vector()[:] = m elif isinstance(m, Function): self.m.assign(m) elif isinstance(m, float): self.m.vector()[:] = m elif isinstance(m, int): self.m.vector()[:] = float(m) else: raise WrongInstanceError('Format for m not accepted') self.assemble_A() self.reset() def backup_m(self): self.mcopy.assign(self.m) def restore_m(self): self.update_m(self.mcopy) def reset(self): """Reset U, C and E""" self.U = [] self.C = [] self.E = [] def set_solver(self): """Reset solver for fwd operator""" #self.solver = LUSolver() #self.solver.parameters['reuse_factorization'] = True self.solver = PETScKrylovSolver("cg", "amg") self.solver.parameters["maximum_iterations"] = 1000 self.solver.parameters["relative_tolerance"] = 1e-12 self.solver.parameters["error_on_nonconvergence"] = True self.solver.parameters["nonzero_initial_guess"] = False self.solver.set_operator(self.A) def addPDEcount(self, increment=1): """Increase 'nbPDEsolves' by 'increment'""" self.nbPDEsolves += increment def resetPDEsolves(self): self.nbPDEsolves = 0 # Additional methods for compatibility with CG solver: def init_vector(self, x, dim): """Initialize vector x to be compatible with parameter Does not work in dolfin 1.3.0""" self.MM.init_vector(x, 0) def init_vector130(self): """Initialize vector x to be compatible with parameter""" return Vector(Function(self.mcopy.function_space()).vector()) # Abstract methods @abc.abstractmethod def _wkforma(self): self.a = [] @abc.abstractmethod def _wkformc(self): self.c = [] @abc.abstractmethod def _wkforme(self): self.e = [] def inversion(self, initial_medium, target_medium, mpicomm, \ parameters_in=[], myplot=None): """ solve inverse problem with that objective function """ parameters = {'tolgrad':1e-10, 'tolcost':1e-14, 'maxnbNewtiter':50, \ 'maxtolcg':0.5} parameters.update(parameters_in) maxnbNewtiter = parameters['maxnbNewtiter'] tolgrad = parameters['tolgrad'] tolcost = parameters['tolcost'] tolcg = parameters['maxtolcg'] mpirank = MPI.rank(mpicomm) self.update_m(initial_medium) self._plotm(myplot, 'init') if mpirank == 0: print '\t{:12s} {:10s} {:12s} {:12s} {:12s} {:10s} \t{:10s} {:12s} {:12s}'.format(\ 'iter', 'cost', 'misfit', 'reg', '|G|', 'medmisf', 'a_ls', 'tol_cg', 'n_cg') dtruenorm = np.sqrt(target_medium.vector().\ inner(self.MM*target_medium.vector())) self.solvefwd_cost() for it in xrange(maxnbNewtiter): self.solveadj_constructgrad() # compute gradient if it == 0: gradnorm0 = self.Gradnorm diff = self.m.vector() - target_medium.vector() medmisfit = np.sqrt(diff.inner(self.MM * diff)) if mpirank == 0: print '{:12d} {:12.4e} {:12.2e} {:12.2e} {:11.4e} {:10.2e} ({:4.2f})'.\ format(it, self.cost, self.misfit, self.regul, \ self.Gradnorm, medmisfit, medmisfit/dtruenorm), self._plotm(myplot, str(it)) self._plotgrad(myplot, str(it)) if self.Gradnorm < gradnorm0 * tolgrad or self.Gradnorm < 1e-12: if mpirank == 0: print '\nGradient sufficiently reduced -- optimization stopped' break # Compute search direction: tolcg = min(tolcg, np.sqrt(self.Gradnorm / gradnorm0)) self.assemble_hessian() # for regularization cgiter, cgres, cgid, tolcg = compute_searchdirection( self, 'Newt', tolcg) self._plotsrchdir(myplot, str(it)) # Line search: cost_old = self.cost statusLS, LScount, alpha = bcktrcklinesearch(self, 12) if mpirank == 0: print '{:11.3f} {:12.2e} {:10d}'.format(alpha, tolcg, cgiter) if self.PD: self.Regul.update_w(self.srchdir.vector(), alpha) if np.abs(self.cost - cost_old) / np.abs(cost_old) < tolcost: if mpirank == 0: if tolcg < 1e-14: print 'Cost function stagnates -- optimization aborted' break tolcg = 0.001 * tolcg def assemble_hessian(self): self.Regul.assemble_hessian(self.m) def _plotm(self, myplot, index): """ plot media during inversion """ if not myplot == None: myplot.set_varname('m' + index) myplot.plot_vtk(self.m) def _plotgrad(self, myplot, index): """ plot grad during inversion """ if not myplot == None: myplot.set_varname('Grad_m' + index) myplot.plot_vtk(self.Grad) def _plotsrchdir(self, myplot, index): """ plot srchdir during inversion """ if not myplot == None: myplot.set_varname('srchdir_m' + index) myplot.plot_vtk(self.srchdir)
class RungeKuttaDGTimestepping(object): def __init__( self, simulation, a, L, u, up, func_name, order=None, explicit_funcs=None, bcs=None, ): """ RKDG timestepping. A is a block diagonal mass matrix form (u*v*dx), L is the form of the right hand side of du/dt = L. The functions u and up are the result and the previous value respectively. If order is not given it is taken as P+1 where P is the element degree of u. It is assumed that up is used in the form L so that changing it causes L to change. L should not depend on u or t, only up. The up function *will be modified* when running .step()! It will not contain a sensible value afterwords, while the u function will contain the time integrated value after the time step. The up function should contain the value of u at the beginning of the time step before running .step() Explicit functions are other functions that are a part of L. They will be extrapolated. The API is: give [ux, up, upp, ... uppp] where ux is the function that is explicit in L and will be extrapolated to the fractional RK time step based on the values at previous time steps. The order of extrapolation depends on the number of previous values upp...pps given. """ self.simulation = simulation V = u.function_space() if order is None: order = V.ufl_element().degree() + 1 self.order = order # Number of stages if order <= 3: S = order else: self.A, self.B, _C = get_ssp_rk_coefficients(order) S = len(self.A) simulation.log.info( ' Preparing SSP RK method of order %d with %d stages' % (order, S)) self.funcs_to_extrapolate = explicit_funcs self.bcs = bcs self.u = u self.up = up self.us = [Function(V) for _ in range(S - 1)] self.dus = [Function(V) for _ in range(S)] self.solver = LocalSolver(a, L) self.solver.factorize() self.slope_limiters = { du: SlopeLimiter(simulation, func_name, du) for du in self.dus } if self.order > 3: # Need one extra function storage when running the generic code self.up_store = Function(V) def _solve(self, du, fdt, uexpl): """ Assemble L and use the block diagonality of the mass matrix to run the pre-factorized local solver instead of a global solve We first update any explicit functions (uexpl which we get from the previous RK step and self.funcs_to_extrapolate which we extrapolate). We also update the boundary conditions in case they are time dependent """ if uexpl is not self.up: self.up.assign(uexpl) # The RK sub time step orig_t = self.simulation.time t = orig_t - (1 - fdt) * self.simulation.dt self.simulation.time = t # Update time dependent explicit functions in L to the RK sub time step for funcs in self.funcs_to_extrapolate: ux = funcs[0] ups = funcs[1:] ux.vector().zero() if len(ups) == 2: old1, old2 = ups ux.vector().axpy(fdt + 1, old1.vector()) ux.vector().axpy(-fdt, old2.vector()) elif len(ups) == 3: old1, old2, old3 = ups ux.vector().axpy(fdt**2 / 2 + 3 * fdt / 2 + 1, old1.vector()) ux.vector().axpy(-fdt**2 - 2 * fdt, old2.vector()) ux.vector().axpy(fdt**2 / 2 + fdt / 2, old3.vector()) else: raise NotImplementedError( 'Extrapolation of degree %d not implemented' % (len(ups) - 1)) # Update time dependent BCs in L to the RK sub time step for bc in self.bcs: bc.update() self.solver.solve_local_rhs(du) self.simulation.time = orig_t self.slope_limiters[du].run() def step(self, dt): """ Use Runge-Kutta to step dt forward in time """ u, up = self.u, self.up us, dus = self.us, self.dus K, ls = self.order, self.solver if K == 1: u.assign(up) ls.solve_global_rhs(dus[0]) u.vector().axpy(dt, dus[0].vector()) elif K == 2: u.assign(up) self._solve(dus[0], 0.0, up) us[0].assign(u) us[0].vector().axpy(dt, dus[0].vector()) self._solve(dus[1], 1.0, us[0]) u.vector().axpy(0.5 * dt, dus[0].vector()) u.vector().axpy(0.5 * dt, dus[1].vector()) elif K == 3: u.assign(up) self._solve(dus[0], 0.0, up) us[0].assign(u) us[0].vector().axpy(dt, dus[0].vector()) self._solve(dus[1], 0.5, us[0]) us[1].assign(u) us[1].vector().axpy(0.25 * dt, dus[0].vector()) us[1].vector().axpy(0.25 * dt, dus[1].vector()) self._solve(dus[2], 1.0, us[1]) u.vector().axpy(1 / 6 * dt, dus[0].vector()) u.vector().axpy(1 / 6 * dt, dus[1].vector()) u.vector().axpy(2 / 3 * dt, dus[2].vector()) else: # Generic implementation self.up_store.assign(up) A, B = self.A, self.B S = len(A) # number of stages for i in range(S): # FIXME: use the _solve() method ... we must first calculate the effective time step if i > 0: up.assign(us[i - 1]) ls.solve_local_rhs(dus[i]) un = us[i] if i < S - 1 else u un.vector().zero() for j in range(i + 1): a, b = A[i][j], B[i][j] if a != 0: uo = us[j - 1] if j > 0 else self.up_store un.vector().axpy(a, uo.vector()) if b != 0: un.vector().axpy(b * dt, dus[j].vector())
def solve(self, problem): self.problem = problem doSave = problem.doSave save_this_step = False onlyVel = problem.saveOnlyVel dt = self.metadata['dt'] nu = Constant(self.problem.nu) self.tc.init_watch('init', 'Initialization', True, count_to_percent=False) self.tc.init_watch('rhs', 'Assembled right hand side', True, count_to_percent=True) self.tc.init_watch('applybc1', 'Applied velocity BC 1st step', True, count_to_percent=True) self.tc.init_watch('applybc3', 'Applied velocity BC 3rd step', True, count_to_percent=True) self.tc.init_watch('applybcP', 'Applied pressure BC or othogonalized rhs', True, count_to_percent=True) self.tc.init_watch('assembleMatrices', 'Initial matrix assembly', False, count_to_percent=True) self.tc.init_watch('solve 1', 'Running solver on 1st step', True, count_to_percent=True) self.tc.init_watch('solve 2', 'Running solver on 2nd step', True, count_to_percent=True) self.tc.init_watch('solve 3', 'Running solver on 3rd step', True, count_to_percent=True) self.tc.init_watch('solve 4', 'Running solver on 4th step', True, count_to_percent=True) self.tc.init_watch('assembleA1', 'Assembled A1 matrix (without stabiliz.)', True, count_to_percent=True) self.tc.init_watch('assembleA1stab', 'Assembled A1 stabilization', True, count_to_percent=True) self.tc.init_watch('next', 'Next step assignments', True, count_to_percent=True) self.tc.init_watch('saveVel', 'Saved velocity', True) self.tc.start('init') # Define function spaces (P2-P1) mesh = self.problem.mesh self.V = VectorFunctionSpace(mesh, "Lagrange", 2) # velocity self.Q = FunctionSpace(mesh, "Lagrange", 1) # pressure self.PS = FunctionSpace(mesh, "Lagrange", 2) # partial solution (must be same order as V) self.D = FunctionSpace(mesh, "Lagrange", 1) # velocity divergence space problem.initialize(self.V, self.Q, self.PS, self.D) # Define trial and test functions u = TrialFunction(self.V) v = TestFunction(self.V) p = TrialFunction(self.Q) q = TestFunction(self.Q) n = FacetNormal(mesh) I = Identity(find_geometric_dimension(u)) # Initial conditions: u0 velocity at previous time step u1 velocity two time steps back p0 previous pressure [u1, u0, p0] = self.problem.get_initial_conditions([{'type': 'v', 'time': -dt}, {'type': 'v', 'time': 0.0}, {'type': 'p', 'time': 0.0}]) u_ = Function(self.V) # current tentative velocity u_cor = Function(self.V) # current corrected velocity p_ = Function(self.Q) # current pressure or pressure help function from rotation scheme p_mod = Function(self.Q) # current modified pressure from rotation scheme # Define coefficients k = Constant(self.metadata['dt']) f = Constant((0, 0, 0)) # Define forms # step 1: Tentative velocity, solve to u_ u_ext = 1.5 * u0 - 0.5 * u1 # extrapolation for convection term # Stabilisation h = CellSize(mesh) if self.args.cbc_tau: # used in Simula cbcflow project tau = Constant(self.stabCoef) * h / (sqrt(inner(u_ext, u_ext)) + h) else: # proposed in R. Codina: On stabilized finite element methods for linear systems of # convection-diffusion-reaction equations. tau = Constant(self.stabCoef) * k * h ** 2 / ( 2 * nu * k + k * h * sqrt(DOLFIN_EPS + inner(u_ext, u_ext)) + h ** 2) # DOLFIN_EPS is added because of FEniCS bug that inner(u_ext, u_ext) can be negative when u_ext = 0 if self.use_full_SUPG: v1 = v + tau * 0.5 * dot(grad(v), u_ext) parameters['form_compiler']['quadrature_degree'] = 6 else: v1 = v def nonlinearity(function): if self.args.ema: return 2 * inner(dot(sym(grad(function)), u_ext), v1) * dx + inner(div(function) * u_ext, v1) * dx else: return inner(dot(grad(function), u_ext), v1) * dx def diffusion(fce): if self.useLaplace: return nu * inner(grad(fce), grad(v1)) * dx else: form = inner(nu * 2 * sym(grad(fce)), sym(grad(v1))) * dx if self.bcv == 'CDN': return form if self.bcv == 'LAP': return form - inner(nu * dot(grad(fce).T, n), v1) * problem.get_outflow_measure_form() if self.bcv == 'DDN': return form # additional term must be added to non-constant part def pressure_rhs(): if self.args.bc == 'outflow': return inner(p0, div(v1)) * dx else: return inner(p0, div(v1)) * dx - inner(p0 * n, v1) * problem.get_outflow_measure_form() a1_const = (1. / k) * inner(u, v1) * dx + diffusion(0.5 * u) a1_change = nonlinearity(0.5 * u) if self.bcv == 'DDN': # does not penalize influx for current step, only for the next one # this can lead to oscilation: # DDN correct next step, but then u_ext is OK so in next step DDN is not used, leading to new influx... # u and u_ext cannot be switched, min_value is nonlinear function a1_change += -0.5 * min_value(Constant(0.), inner(u_ext, n)) * inner(u, v1) * problem.get_outflow_measure_form() # NT works only with uflacs compiler L1 = (1. / k) * inner(u0, v1) * dx - nonlinearity(0.5 * u0) - diffusion(0.5 * u0) + pressure_rhs() if self.bcv == 'DDN': L1 += 0.5 * min_value(0., inner(u_ext, n)) * inner(u0, v1) * problem.get_outflow_measure_form() # Non-consistent SUPG stabilisation if self.stabilize and not self.use_full_SUPG: # a1_stab = tau*inner(dot(grad(u), u_ext), dot(grad(v), u_ext))*dx a1_stab = 0.5 * tau * inner(dot(grad(u), u_ext), dot(grad(v), u_ext)) * dx(None, {'quadrature_degree': 6}) # optional: to use Crank Nicolson in stabilisation term following change of RHS is needed: # L1 += -0.5*tau*inner(dot(grad(u0), u_ext), dot(grad(v), u_ext))*dx(None, {'quadrature_degree': 6}) outflow_area = Constant(problem.outflow_area) need_outflow = Constant(0.0) if self.useRotationScheme: # Rotation scheme F2 = inner(grad(p), grad(q)) * dx + (1. / k) * q * div(u_) * dx else: # Projection, solve to p_ if self.forceOutflow and problem.can_force_outflow: info('Forcing outflow.') F2 = inner(grad(p - p0), grad(q)) * dx + (1. / k) * q * div(u_) * dx for m in problem.get_outflow_measures(): F2 += (1. / k) * (1. / outflow_area) * need_outflow * q * m else: F2 = inner(grad(p - p0), grad(q)) * dx + (1. / k) * q * div(u_) * dx a2, L2 = system(F2) # step 3: Finalize, solve to u_ if self.useRotationScheme: # Rotation scheme F3 = (1. / k) * inner(u - u_, v) * dx + inner(grad(p_), v) * dx else: F3 = (1. / k) * inner(u - u_, v) * dx + inner(grad(p_ - p0), v) * dx a3, L3 = system(F3) if self.useRotationScheme: # Rotation scheme: modify pressure F4 = (p - p0 - p_ + nu * div(u_)) * q * dx a4, L4 = system(F4) # Assemble matrices self.tc.start('assembleMatrices') A1_const = assemble(a1_const) # must be here, so A1 stays one Python object during repeated assembly A1_change = A1_const.copy() # copy to get matrix with same sparse structure (data will be overwritten) if self.stabilize and not self.use_full_SUPG: A1_stab = A1_const.copy() # copy to get matrix with same sparse structure (data will be overwritten) A2 = assemble(a2) A3 = assemble(a3) if self.useRotationScheme: A4 = assemble(a4) self.tc.end('assembleMatrices') if self.solvers == 'direct': self.solver_vel_tent = LUSolver('mumps') self.solver_vel_cor = LUSolver('mumps') self.solver_p = LUSolver('mumps') if self.useRotationScheme: self.solver_rot = LUSolver('mumps') else: # NT 2016-1 KrylovSolver >> PETScKrylovSolver # not needed, chosen not to use hypre_parasails: # if self.prec_v == 'hypre_parasails': # in FEniCS 1.6.0 inaccessible using KrylovSolver class # self.solver_vel_tent = PETScKrylovSolver('gmres') # PETSc4py object # self.solver_vel_tent.ksp().getPC().setType('hypre') # PETScOptions.set('pc_hypre_type', 'parasails') # # this is global setting, but preconditioners for pressure solvers are set by their constructors # else: self.solver_vel_tent = PETScKrylovSolver('gmres', self.args.precV) # nonsymetric > gmres # cannot use 'ilu' in parallel self.solver_vel_cor = PETScKrylovSolver('cg', self.args.precVC) self.solver_p = PETScKrylovSolver(self.args.solP, self.args.precP) # almost (up to BC) symmetric > CG if self.useRotationScheme: self.solver_rot = PETScKrylovSolver('cg', 'hypre_amg') # setup Krylov solvers if self.solvers == 'krylov': # Get the nullspace if there are no pressure boundary conditions foo = Function(self.Q) # auxiliary vector for setting pressure nullspace if self.args.bc == 'nullspace': null_vec = Vector(foo.vector()) self.Q.dofmap().set(null_vec, 1.0) null_vec *= 1.0 / null_vec.norm('l2') self.null_space = VectorSpaceBasis([null_vec]) as_backend_type(A2).set_nullspace(self.null_space) # apply global options for Krylov solvers solver_options = {'monitor_convergence': True, 'maximum_iterations': 10000, 'nonzero_initial_guess': True} # 'nonzero_initial_guess': True with solver.solve(A, u, b) means that # Solver will use anything stored in u as an initial guess for solver in [self.solver_vel_tent, self.solver_vel_cor, self.solver_rot, self.solver_p] if \ self.useRotationScheme else [self.solver_vel_tent, self.solver_vel_cor, self.solver_p]: for key, value in solver_options.items(): try: solver.parameters[key] = value except KeyError: info('Invalid option %s for KrylovSolver' % key) return 1 if self.args.solP == 'richardson': self.solver_p.parameters['monitor_convergence'] = False self.solver_vel_tent.parameters['relative_tolerance'] = 10 ** (-self.args.prv1) self.solver_vel_tent.parameters['absolute_tolerance'] = 10 ** (-self.args.pav1) self.solver_vel_cor.parameters['relative_tolerance'] = 10E-12 self.solver_vel_cor.parameters['absolute_tolerance'] = 10E-4 self.solver_p.parameters['relative_tolerance'] = 10 ** (-self.args.prp) self.solver_p.parameters['absolute_tolerance'] = 10 ** (-self.args.pap) if self.useRotationScheme: self.solver_rot.parameters['relative_tolerance'] = 10E-10 self.solver_rot.parameters['absolute_tolerance'] = 10E-10 if self.args.Vrestart > 0: self.solver_vel_tent.parameters['gmres']['restart'] = self.args.Vrestart if self.args.solP == 'gmres' and self.args.Prestart > 0: self.solver_p.parameters['gmres']['restart'] = self.args.Prestart # boundary conditions bcu, bcp = problem.get_boundary_conditions(self.args.bc == 'outflow', self.V, self.Q) self.tc.end('init') # Time-stepping info("Running of Incremental pressure correction scheme n. 1") ttime = self.metadata['time'] t = dt step = 1 # debug function if problem.args.debug_rot: plot_cor_v = Function(self.V) while t < (ttime + dt / 2.0): self.problem.update_time(t, step) if self.MPI_rank == 0: problem.write_status_file(t) if doSave: save_this_step = problem.save_this_step # assemble matrix (it depends on solution) self.tc.start('assembleA1') assemble(a1_change, tensor=A1_change) # assembling into existing matrix is faster than assembling new one A1 = A1_const.copy() # we dont want to change A1_const A1.axpy(1, A1_change, True) self.tc.end('assembleA1') self.tc.start('assembleA1stab') if self.stabilize and not self.use_full_SUPG: assemble(a1_stab, tensor=A1_stab) # assembling into existing matrix is faster than assembling new one A1.axpy(1, A1_stab, True) self.tc.end('assembleA1stab') # Compute tentative velocity step begin("Computing tentative velocity") self.tc.start('rhs') b = assemble(L1) self.tc.end('rhs') self.tc.start('applybc1') [bc.apply(A1, b) for bc in bcu] self.tc.end('applybc1') try: self.tc.start('solve 1') self.solver_vel_tent.solve(A1, u_.vector(), b) self.tc.end('solve 1') if save_this_step: self.tc.start('saveVel') problem.save_vel(True, u_) self.tc.end('saveVel') if save_this_step and not onlyVel: problem.save_div(True, u_) problem.compute_err(True, u_, t) problem.compute_div(True, u_) except RuntimeError as inst: problem.report_fail(t) return 1 end() if self.useRotationScheme: begin("Computing tentative pressure") else: begin("Computing pressure") if self.forceOutflow and problem.can_force_outflow: out = problem.compute_outflow(u_) info('Tentative outflow: %f' % out) n_o = -problem.last_inflow - out info('Needed outflow: %f' % n_o) need_outflow.assign(n_o) self.tc.start('rhs') b = assemble(L2) self.tc.end('rhs') self.tc.start('applybcP') [bc.apply(A2, b) for bc in bcp] if self.args.bc == 'nullspace': self.null_space.orthogonalize(b) self.tc.end('applybcP') try: self.tc.start('solve 2') self.solver_p.solve(A2, p_.vector(), b) self.tc.end('solve 2') except RuntimeError as inst: problem.report_fail(t) return 1 if self.useRotationScheme: foo = Function(self.Q) foo.assign(p_ + p0) if save_this_step and not onlyVel: problem.averaging_pressure(foo) problem.save_pressure(True, foo) else: foo = Function(self.Q) foo.assign(p_) # we do not want to change p_ by averaging if save_this_step and not onlyVel: problem.averaging_pressure(foo) problem.save_pressure(False, foo) end() begin("Computing corrected velocity") self.tc.start('rhs') b = assemble(L3) self.tc.end('rhs') if not self.args.B: self.tc.start('applybc3') [bc.apply(A3, b) for bc in bcu] self.tc.end('applybc3') try: self.tc.start('solve 3') self.solver_vel_cor.solve(A3, u_cor.vector(), b) self.tc.end('solve 3') problem.compute_err(False, u_cor, t) problem.compute_div(False, u_cor) except RuntimeError as inst: problem.report_fail(t) return 1 if save_this_step: self.tc.start('saveVel') problem.save_vel(False, u_cor) self.tc.end('saveVel') if save_this_step and not onlyVel: problem.save_div(False, u_cor) end() if self.useRotationScheme: begin("Rotation scheme pressure correction") self.tc.start('rhs') b = assemble(L4) self.tc.end('rhs') try: self.tc.start('solve 4') self.solver_rot.solve(A4, p_mod.vector(), b) self.tc.end('solve 4') except RuntimeError as inst: problem.report_fail(t) return 1 if save_this_step and not onlyVel: problem.averaging_pressure(p_mod) problem.save_pressure(False, p_mod) end() if problem.args.debug_rot: # save applied pressure correction (expressed as a term added to RHS of next tentative vel. step) # see comment next to argument definition plot_cor_v.assign(project(k * grad(nu * div(u_)), self.V)) problem.fileDict['grad_cor']['file'].write(plot_cor_v, t) # compute functionals (e. g. forces) problem.compute_functionals(u_cor, p_mod if self.useRotationScheme else p_, t, step) # Move to next time step self.tc.start('next') u1.assign(u0) u0.assign(u_cor) u_.assign(u_cor) # use corrected velocity as initial guess in first step if self.useRotationScheme: p0.assign(p_mod) else: p0.assign(p_) t = round(t + dt, 6) # round time step to 0.000001 step += 1 self.tc.end('next') info("Finished: Incremental pressure correction scheme n. 1") problem.report() return 0