|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +import numpy as np |
| 4 | +import precice |
| 5 | +import os |
| 6 | +from scipy.interpolate import interp1d |
| 7 | + |
| 8 | +import ufl |
| 9 | +from dune.fem.space import lagrange as solutionSpace |
| 10 | +from dune.ufl import DirichletBC, Constant |
| 11 | +from dune.fem.scheme import galerkin as solutionScheme |
| 12 | +from dune.fem.operator import galerkin |
| 13 | +from dune.fem.utility import Sampler |
| 14 | +from dune.grid import cartesianDomain |
| 15 | +from dune.alugrid import aluSimplexGrid |
| 16 | +from dune.fem.function import uflFunction, gridFunction |
| 17 | +from dune.ufl import expression2GF |
| 18 | + |
| 19 | +if __name__ == '__main__': |
| 20 | + |
| 21 | + # standard heat equation: u_t + k \Delta u = 0 |
| 22 | + k = 100 # kg * m / s^3 / K, https://en.wikipedia.org/wiki/Thermal_conductivity |
| 23 | + |
| 24 | + # Create mesh and define function space |
| 25 | + nx = 100 |
| 26 | + ny = 25 |
| 27 | + |
| 28 | + dt_out = 0.2 # interval for writing VTK files |
| 29 | + y_top = 0 |
| 30 | + y_bottom = y_top - .25 |
| 31 | + x_left = 0 |
| 32 | + x_right = x_left + 1 |
| 33 | + |
| 34 | + # preCICE setup |
| 35 | + interface = precice.Interface("Solid", "../precice-config.xml", 0, 1) |
| 36 | + |
| 37 | + # define coupling mesh |
| 38 | + mesh_name = "Solid-Mesh" |
| 39 | + mesh_id = interface.get_mesh_id(mesh_name) |
| 40 | + interface_x_coordinates = np.linspace(0, 1, nx + 1) # meshpoints for interface values |
| 41 | + vertices = [[x0, 0] for x0 in interface_x_coordinates] |
| 42 | + vertex_ids = interface.set_mesh_vertices(mesh_id, vertices) |
| 43 | + temperature_id = interface.get_data_id("Temperature", mesh_id) |
| 44 | + flux_id = interface.get_data_id("Heat-Flux", mesh_id) |
| 45 | + |
| 46 | + domain = cartesianDomain([x_left, y_bottom], [x_right, y_top], [nx, ny]) |
| 47 | + mesh = aluSimplexGrid(domain, serial=True) |
| 48 | + space = solutionSpace(mesh, order=1) |
| 49 | + u = ufl.TrialFunction(space) |
| 50 | + v = ufl.TestFunction(space) |
| 51 | + x = ufl.SpatialCoordinate(ufl.triangle) |
| 52 | + n = ufl.FacetNormal(space) |
| 53 | + |
| 54 | + u0 = uflFunction(mesh, name="u0", order=space.order, |
| 55 | + ufl=Constant(310)) |
| 56 | + uold = space.interpolate(u0, name='uold') |
| 57 | + unew = space.interpolate(u0, name='unew') |
| 58 | + ucheckpoint = uold.copy(name='u_checkpoint') |
| 59 | + |
| 60 | + # creating boundary condition with data from the fluid |
| 61 | + |
| 62 | + ug_interp = interp1d(interface_x_coordinates, np.zeros(nx + 1)) |
| 63 | + def ug_f(x): return ug_interp(x[0]) |
| 64 | + |
| 65 | + @gridFunction(mesh, name="u_gamma", order=1) |
| 66 | + def u_gamma(xg): |
| 67 | + return ug_f(xg) |
| 68 | + |
| 69 | + eps = 1e-8 |
| 70 | + bc_top = DirichletBC(space, u_gamma, x[1] > - eps) |
| 71 | + bc_bottom = DirichletBC(space, Constant(310.), x[1] < -0.25 + eps) |
| 72 | + bcs = [bc_bottom, bc_top] |
| 73 | + |
| 74 | + k = Constant(k) |
| 75 | + dt = Constant(0.01, name='dt') |
| 76 | + |
| 77 | + A = u * v * ufl.dx + dt * k * ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx |
| 78 | + b = uold * v * ufl.dx |
| 79 | + |
| 80 | + scheme = solutionScheme([A == b, *bcs], solver='cg') |
| 81 | + |
| 82 | + # Weak form of the flux |
| 83 | + flux_expr = -(u - uold) * v / dt * ufl.dx - k * ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx |
| 84 | + flux_expr_operator = galerkin(flux_expr) |
| 85 | + flux_sol = space.interpolate(u0, name='flux_sol') |
| 86 | + |
| 87 | + # Sample the flux on the top edge |
| 88 | + flux_sol_expr = expression2GF(flux_sol.space.grid, flux_sol, flux_sol.space.order) |
| 89 | + sampler_weak_flux = Sampler(flux_sol_expr) |
| 90 | + def flux_f_weak(): return sampler_weak_flux.lineSample([0., 0.], [1., 0.], nx + 1)[1] * nx |
| 91 | + |
| 92 | + if not os.path.exists("output"): |
| 93 | + os.makedirs("output") |
| 94 | + vtk = mesh.sequencedVTK("output/heat", pointdata=[unew]) |
| 95 | + |
| 96 | + precice_dt = interface.initialize() |
| 97 | + dt.assign(min(float(dt), precice_dt)) |
| 98 | + |
| 99 | + t = float(dt) |
| 100 | + |
| 101 | + while interface.is_coupling_ongoing(): |
| 102 | + |
| 103 | + if interface.is_action_required(precice.action_write_iteration_checkpoint()): # write checkpoint |
| 104 | + t_check = t |
| 105 | + ucheckpoint.assign(uold) |
| 106 | + interface.mark_action_fulfilled(precice.action_write_iteration_checkpoint()) |
| 107 | + |
| 108 | + ug = interface.read_block_scalar_data(temperature_id, vertex_ids) |
| 109 | + ug_interp.y = ug |
| 110 | + |
| 111 | + scheme.model.dt = dt |
| 112 | + scheme.solve(target=unew) |
| 113 | + |
| 114 | + # compute flux, solution goes into flux_sol |
| 115 | + flux_expr_operator(unew, flux_sol) |
| 116 | + flux_values = flux_f_weak() # sample the flux function |
| 117 | + |
| 118 | + interface.write_block_scalar_data(flux_id, vertex_ids, flux_values) |
| 119 | + |
| 120 | + precice_dt = interface.advance(dt) |
| 121 | + dt.assign(min(float(dt), precice_dt)) |
| 122 | + |
| 123 | + if interface.is_action_required(precice.action_read_iteration_checkpoint()): # roll back to checkpoint |
| 124 | + t = t_check |
| 125 | + uold.assign(ucheckpoint) |
| 126 | + interface.mark_action_fulfilled(precice.action_read_iteration_checkpoint()) |
| 127 | + |
| 128 | + else: # update solution |
| 129 | + |
| 130 | + uold.assign(unew) |
| 131 | + t += float(dt) |
| 132 | + |
| 133 | + if interface.is_time_window_complete(): |
| 134 | + tol = 10e-5 # we need some tolerance, since otherwise output might be skipped. |
| 135 | + if abs((t + tol) % dt_out) < 2 * tol: # output if t is a multiple of dt_out |
| 136 | + print("output vtk for time = {}".format(float(t))) |
| 137 | + vtk() |
| 138 | + |
| 139 | + interface.finalize() |
0 commit comments