Special thanks to @prj for helping with this problem. It is now resolved and I will leave the solution below in case anyone else has a similar question. The key here was to manipulate the Jacobian matrix using a low-rank update. This update uses a projection onto a “1D” fespace that shares the boundary governing the forcing and then projects the result back to the full fespace. This approach restricts the operator to the boundary degrees of freedom for the unknowns but not for the test functions. I hope someone finds this useful! Cheers.
// A simple example of my problem using Burgers' equation
func Pk = [P2, P2];
macro def(u) [u#x, u#y] // End macro
mesh th = square(25, 25), th2 = square(1, 25, [1 + 0.01*x, y]); // define meshes
fespace Xh(th, Pk), Xh2(th2, Pk, periodic = [[2, y], [4, y]]); // define spaces
Xh def(u), def(du), def(v); // definitions
matrix Ih = interpolate(Xh, Xh2); // from skinny to normal fespace
matrix Ih2 = interpolate(Xh2, Xh); // from normal to skinny fespace
matrix II = Ih*Ih2;
real f, tol = 1e-10, nu = 0.008; // forcing strength, tolerance, viscosity parameter
// Nonlinear operator (chosen here to be Burgers' equation)
macro N(u,v) (u#x*dx(u#x)*v#x + u#y*dy(u#x)*v#x + nu*(dx(u#x)*dx(v#x) + dy(u#x)*dy(v#x)) + u#x*dx(u#y)*v#y + u#y*dy(u#y)*v#y + nu*(dx(u#y)*dx(v#y) + dy(u#y)*dy(v#y))) // End macro
// Nonlinear forcing term dependent on the value at a boundary
macro F(u,v) ((u#y^2 - f*u#y(1, y)^2)*v#x) // End macro
// NOTE: the boundary term is proportional to -f
// Jacobian of nonlinear operator
macro dN(du,u,v) (du#x*dx(u#x)*v#x + du#y*dy(u#x)*v#x + u#x*dx(du#x)*v#x + u#y*dy(du#x)*v#x + nu*(dx(du#x)*dx(v#x) + dy(du#x)*dy(v#x))
+ du#x*dx(u#y)*v#y + du#y*dy(u#y)*v#y + u#x*dx(du#y)*v#y + u#y*dy(du#y)*v#y + nu*(dx(du#y)*dx(v#y) + dy(du#y)*dy(v#y))) // End macro
// Jacobian of nonlinear forcing term
macro dF(du,u,v) (2.*du#y*u#y*v#x) // End macro, not sure how to implement this
// NOTE: the linearization dF is incomplete as it does not include the boundary term
// Boundary conditions
macro BC(du,u) (on(1, 3, dux = -ux, duy = -uy) + on(4, dux = y*(1 - y) - ux, duy = y*(1 - y) - uy)) // End macro
// Declare variational forms
varf b(def(du), def(v)) = int2d(th)(-(N(u, v) + F(u, v))) + BC(du, u);
varf A(def(du), def(v)) = int2d(th)(dN(du, u, v) + dF(du, u, v)) + BC(du, u);
varf C(def(du), def(v)) = int2d(th)(-2.*f*uy(1, y)*duy*vx); // linearized forcing term
// Newton iteration on linearized system with f=0
ux[] = 0.; f = 0.; real error = 1.0; int i = 0;
cout << " ------ UNFORCED SYSTEM ------" << endl;
while (error > tol){
real[int] bb = b(0, Xh); // evaluate residual
matrix AA = A(Xh, Xh, solver = sparsesolver); // build matrix
dux[] = AA^-1*bb; // solve for correction
ux[] += dux[]; // update solution
error = dux[].l2/ux[].l2; // compute relative change
i++; cout << " Iteration = " << i << ", error = " << error << endl;
}cout << " Converged with " << i << " iterations." << endl;
// Newton iteration on linearized system with f=5
ux[] = 0.; f = 5.; error = 1.0; i = 0;
cout << "------ FORCED SYSTEM (boundary term not linearized) ------" << endl;
while (error > tol){
real[int] bb = b(0, Xh); // evaluate residual
matrix AA = A(Xh, Xh, solver = sparsesolver); // build matrix
dux[] = AA^-1*bb; // solve for correction
ux[] += dux[]; // update solution
error = dux[].l2/ux[].l2; // compute relative change
i++; cout << " Iteration = " << i << ", error = " << error << endl;
}cout << " Converged with " << i << " iterations." << endl;
// Newton iteration on linearized system with f=5
ux[] = 0.; f = 5.; error = 1.0; i = 0;
cout << "------ FORCED SYSTEM (fully linearized) ------" << endl;
while (error > tol){
real[int] bb = b(0, Xh); // evaluate residual
matrix AA = A(Xh, Xh); // build matrix
matrix CC = C(Xh, Xh); // build matrix
CC = CC*II;
AA += CC;
set(AA, solver = sparsesolver);
dux[] = AA^-1*bb; // solve for correction
ux[] += dux[]; // update solution
error = dux[].l2/ux[].l2; // compute relative change
i++; cout << " Iteration = " << i << ", error = " << error << endl;
}cout << " Converged with " << i << " iterations." << endl;