I want to solve the evolution of the heat equation in 1D, i.e.
using reduced order modelling. Using Dokken his tutorial on the heat equation I solved it by solving at each time step the linear equation
with A and F derived from the weak form of the heat equation. I stored solutions u^n of the full order system in a snapshot_matrix. From this snapshot matrix I retrieved a suitable reduced basis V_r by applying SVD. With this reduced basis V_r I want to create new reduced matrices A_r and F_r that are smaller than A and F and allow me to find reduced solution by solving
The solution to the heat equation follows by computing
The relationship between A_r and A is given by
and the relationship between F_r and F is given by
Is there a good/nice way to assemble A_r and F_r using dolfinx?
Solution of the heat equation in 1D:
import ufl
from dolfinx import fem, mesh, io, plot
from mpi4py import MPI
from petsc4py import PETSc
import numpy as np
# Mesh and function space
Nx = 4096
domain = mesh.create_interval(MPI.COMM_WORLD, Nx, [0.0, 1.0])
# mesh = dolfinx.UnitIntervalMesh(MPI.COMM_WORLD, Nx)
V = fem.FunctionSpace(domain, ("Lagrange", 1))
# Time variable
dt = 0.00002
T = 0.025
times = np.arange(0,T,dt)
# Parameters
diffusion = 10
mu = 0.5
sigma = 0.02
# Initial conditions
class Gaussian:
def __init__(self, mu, sigma):
self.mu = mu
self.sigma = sigma
def __call__(self, x):
return 1 / ( self.sigma * np.sqrt(2 * np.pi) ) * np.exp( - (x[0] - self.mu ) ** 2 / (2 * self.sigma ** 2))
gaussian = Gaussian(mu, sigma)
# Create boundary condition
fdim = domain.topology.dim - 1
boundary_facets = mesh.locate_entities_boundary(
domain, fdim, lambda x: np.full(x.shape[1], True, dtype=bool))
bc = fem.dirichletbc(PETSc.ScalarType(0), fem.locate_dofs_topological(V, fdim, boundary_facets), V)
# Time scheme
theta = 0.0
# Variational problem at each time
f = fem.Constant(domain, PETSc.ScalarType(0))
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
u_n = fem.Function(V)
backward = dt*diffusion*ufl.inner( ufl.grad(u_n), ufl.grad(v))*ufl.dx
forward = dt*diffusion*ufl.inner( ufl.grad(u), ufl.grad(v))*ufl.dx
F = (u-u_n)*v*ufl.dx + theta*backward + (1-theta)*forward
a = ufl.lhs(F)
L = ufl.rhs(F)
bilinear_form = fem.form(a)
linear_form = fem.form(L)
# Insert initial conditions in variational problem
u_n.interpolate(gaussian)
# Assemble stuff
A = fem.petsc.assemble_matrix(bilinear_form, bcs=[bc])
A.assemble()
b = fem.petsc.create_vector(linear_form)
# Define solver
solver = PETSc.KSP().create(domain.comm)
solver.setOperators(A)
solver.setType(PETSc.KSP.Type.PREONLY)
solver.getPC().setType(PETSc.PC.Type.LU)
# Solve the variational problem
uh = fem.Function(V)
snapshot_matrix = []
snapshot_times = []
max_iter = len(times)
for i, t in enumerate(times):
# Update the right hand side reusing the initial vector
with b.localForm() as loc_b:
loc_b.set(0)
fem.petsc.assemble_vector(b, linear_form)
# Apply Dirichlet boundary condition to the vector
fem.petsc.apply_lifting(b, [bilinear_form], [[bc]])
b.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
fem.petsc.set_bc(b, [bc])
# Solve linear problem
solver.solve(b, uh.vector)
uh.x.scatter_forward()
# Update solution at previous time step (u_n)
u_n.x.array[:] = uh.x.array
# Store values
if i % 5 == 0:
snapshot_matrix.append(np.array(uh.x.array))
snapshot_times.append(t)
print(f"Added solution to stack, iteration {i} of max {max_iter}")
u, s, vh = np.linalg.svd(snapshot_matrix, full_matrices=False)
V_r = u[:,:r]