Domain: Math — 65 – Numerical analysis > 65N – Partial differential equations, boundary value problems; numerical methods > 65N06 – Finite difference methods

Problem:
On \(\Omega=(0,1)^2\) with Dirichlet \(u\equiv0\) on \(\partial\Omega\), take \(N=96\), \(h=1/(N+1)\), nodes \((x_i,y_j)=(ih,jh)\) with \(i,j\in\{1,\dots,N\}\); let \(r(x)=\mathrm{sign}(x)\lfloor |x|\cdot1000+0.5\rfloor/1000\) (all trigonometric arguments in radians), and sample/round all nodal fields before any face/corner construction: set \(k_1=r(2.10+0.55\sin(4x+0.17)+0.45\cos(5y-0.23)+0.22x)\), \(k_2=r(1.60+0.50\cos(6x-0.29)+0.40\sin(7y+0.21)+0.20y)\), \(\theta=r(0.80+0.35\sin(3x+2y)+0.10xy)\), define \(K=R(\theta)\operatorname{diag}(k_1,k_2)R(\theta)^\top\) and round components \(K_{11},K_{22},K_{12}\); take \(\beta=r(0.95+0.30\sin(2\pi x)+0.26\cos(2\pi y)+0.06xy)\), \(f=r(2.4+1.5\sin(5\pi x)\cos(4\pi y)+0.18x^2+0.20y^2+0.04\sin(3xy))\), and a divergence-free drift from \(\phi(x,y)=0.6\sin(2\pi x)\sin(3\pi y)+0.35\sin(3x+2y)\) by \(c_x=\partial_y\phi,\;c_y=-\partial_x\phi\), then set nodal drifts \(c_x^{\mathrm{nod}}=r(40\,c_x),\;c_y^{\mathrm{nod}}=r(40\,c_y)\). Build face coefficients from **rounded** nodals using the **standard direction mapping** with self-ghosts at domain faces: East uses \((i,j)\)&\((i{+}1,j)\), West \((i{-}1,j)\)&\((i,j)\), North \((i,j)\)&\((i,j{+}1)\), South \((i,j{-}1)\)&\((i,j)\); set \(K_{11}^{e,w}=r(\tfrac12(\cdot)),\;K_{22}^{n,s}=r(\tfrac12(\cdot))\), and corners \(K_{12}^{ne,nw,se,sw}=r(\tfrac14\) of the four rounded nodals in the obvious quadrants); face drifts \(c_x^{e,w}=r(\tfrac12(\cdot)),\;c_y^{n,s}=r(\tfrac12(\cdot))\) from the corresponding rounded nodal drifts. Discretize \(-\nabla\!\cdot(K\nabla u)+c\!\cdot\nabla u+\beta u=f\) in divergence form with a 9-point diffusion stencil and Godunov upwind convection at node \((i,j)\): diffusion \(E_d=-K_{11}^e/h^2,\;W_d=-K_{11}^w/h^2,\;N_d=-K_{22}^n/h^2,\;S_d=-K_{22}^s/h^2,\;NE_d=+K_{12}^{ne}/(2h^2),\;NW_d=-K_{12}^{nw}/(2h^2),\;SE_d=-K_{12}^{se}/(2h^2),\;SW_d=+K_{12}^{sw}/(2h^2)\) and \(C_d=-(E_d{+}W_d{+}N_d{+}S_d)+\beta\); convection \(E_c=(c_x^e)^-/h,\;W_c=-(c_x^w)^+/h,\;N_c=(c_y^n)^-/h,\;S_c=-(c_y^s)^+/h,\;C_c=((c_x^e)^++(c_x^w)^-+(c_y^n)^++(c_y^s)^-)/h\) with \(a^\pm=\max(\pm a,0)\); set totals \(E=r(E_d{+}E_c)\), \(W=r(W_d{+}W_c)\), \(N=r(N_d{+}N_c)\), \(S=r(S_d{+}S_c)\), \(NE=r(NE_d)\), \(NW=r(NW_d)\), \(SE=r(SE_d)\), \(SW=r(SW_d)\), \(C=r(C_d{+}C_c)\); drop off-domain neighbors but **retain** diagonal face contributions. Solve for \(u\), set \(u\gets r(u)\); define centered differences with zero ghosts \(u_x=(u_{i+1,j}-u_{i-1,j})/(2h)\), \(u_y=(u_{i,j+1}-u_{i,j-1})/(2h)\); with \(\alpha=r(0.94+0.16\sin(3x+2y)+0.08xy)\), \(\gamma=r(0.07+0.05|\sin(2x-3y)|+0.04|\cos(3x+2y)|)\), \(\delta=0.01\), **report** \(J=r\!\big(h^2\sum r(\alpha[(r(u_x))^2{+}(r(u_y))^2]+\gamma(\sqrt{\delta^2+(r(u_x))^2+(r(u_y))^2}-\delta)+0.04(r(u))^2)\big)\) with exactly three decimals.

Note : You can use any algorithm you want and run a python code to give a definite final answer. Include a single python code block in the solution and make sure to run the code and give me definite answer.

Answer: 0.003

Solution code:
import numpy as np
from scipy.sparse import coo_matrix
from scipy.sparse.linalg import spsolve

# Rounding function: r(x) = sign(x)*floor(|x|*1000 + 0.5)/1000
def r3(x):
    x = np.asarray(x, dtype=np.float64)
    return np.sign(x) * np.floor(np.abs(x) * 1000.0 + 0.5) / 1000.0

# Grid setup
N = 96
h = 1.0 / (N + 1)  # 1/97
i_idx = np.arange(N)
j_idx = np.arange(N)
x = (i_idx[:, None] + 1) * h   # i ↔ rows (axis 0)
y = (j_idx[None, :] + 1) * h   # j ↔ cols (axis 1)

# Helper for clamped shift (self-ghost at boundaries)
def shift_clamp(A, di, dj):
    B = A
    if di > 0:
        B = np.concatenate([B[di:, :], np.repeat(B[-1:, :], di, axis=0)], axis=0)
    elif di < 0:
        B = np.concatenate([np.repeat(B[:1, :], -di, axis=0), B[:di, :]], axis=0)
    if dj > 0:
        B = np.concatenate([B[:, dj:], np.repeat(B[:, -1:], dj, axis=1)], axis=1)
    elif dj < 0:
        B = np.concatenate([np.repeat(B[:, :1], -dj, axis=1), B[:, :dj]], axis=1)
    return B

# Nodal fields (rounded before any face construction)
k1 = r3(2.10 + 0.55 * np.sin(4 * x + 0.17) + 0.45 * np.cos(5 * y - 0.23) + 0.22 * x)
k2 = r3(1.60 + 0.50 * np.cos(6 * x - 0.29) + 0.40 * np.sin(7 * y + 0.21) + 0.20 * y)
theta = r3(0.80 + 0.35 * np.sin(3 * x + 2 * y) + 0.10 * x * y)

cth = np.cos(theta); sth = np.sin(theta)
K11 = r3(cth * cth * k1 + sth * sth * k2)
K22 = r3(sth * sth * k1 + cth * cth * k2)
K12 = r3(cth * sth * (k1 - k2))

beta = r3(0.95 + 0.30 * np.sin(2 * np.pi * x) + 0.26 * np.cos(2 * np.pi * y) + 0.06 * x * y)
f = r3(2.4 + 1.5 * np.sin(5 * np.pi * x) * np.cos(4 * np.pi * y) + 0.18 * x * x + 0.20 * y * y + 0.04 * np.sin(3 * x * y))

# Divergence-free drift from phi
phi_term1_coef_x = 1.8 * np.pi   # ∂/∂y[0.6 sin(2πx) sin(3πy)] = 0.6*(3π) sin(2πx) cos(3πy)
phi_term1_coef_y = -1.2 * np.pi  # -∂/∂x[0.6 sin(2πx) sin(3πy)] = -0.6*(2π) cos(2πx) sin(3πy)
cx = phi_term1_coef_x * np.sin(2 * np.pi * x) * np.cos(3 * np.pi * y) + 0.7 * np.cos(3 * x + 2 * y)
cy = phi_term1_coef_y * np.cos(2 * np.pi * x) * np.sin(3 * np.pi * y) - 1.05 * np.cos(3 * x + 2 * y)
cx_nod = r3(40.0 * cx)
cy_nod = r3(40.0 * cy)

# Face coefficients (self-ghosts; standard direction mapping)
K11_e = r3(0.5 * (K11 + shift_clamp(K11, +1, 0)))
K11_w = r3(0.5 * (shift_clamp(K11, -1, 0) + K11))
K22_n = r3(0.5 * (K22 + shift_clamp(K22, 0, +1)))
K22_s = r3(0.5 * (shift_clamp(K22, 0, -1) + K22))

K12_ne = r3(0.25 * (K12 + shift_clamp(K12, +1, 0) + shift_clamp(K12, 0, +1) + shift_clamp(K12, +1, +1)))
K12_nw = r3(0.25 * (shift_clamp(K12, -1, 0) + K12 + shift_clamp(K12, -1, +1) + shift_clamp(K12, 0, +1)))
K12_se = r3(0.25 * (shift_clamp(K12, 0, -1) + shift_clamp(K12, +1, -1) + K12 + shift_clamp(K12, +1, 0)))
K12_sw = r3(0.25 * (shift_clamp(K12, -1, -1) + shift_clamp(K12, 0, -1) + shift_clamp(K12, -1, 0) + K12))

# Face drifts (rounded averages)
cx_e = r3(0.5 * (cx_nod + shift_clamp(cx_nod, +1, 0)))
cx_w = r3(0.5 * (shift_clamp(cx_nod, -1, 0) + cx_nod))
cy_n = r3(0.5 * (cy_nod + shift_clamp(cy_nod, 0, +1)))
cy_s = r3(0.5 * (shift_clamp(cy_nod, 0, -1) + cy_nod))

# Diffusion contributions
h2 = h * h
E_d = -K11_e / h2; W_d = -K11_w / h2; N_d = -K22_n / h2; S_d = -K22_s / h2
NE_d = +K12_ne / (2.0 * h2); NW_d = -K12_nw / (2.0 * h2)
SE_d = -K12_se / (2.0 * h2); SW_d = +K12_sw / (2.0 * h2)

# Convection (Godunov upwind)
def pos(a): return np.maximum(a, 0.0)
def neg(a): return np.maximum(-a, 0.0)
E_c = neg(cx_e) / h;  W_c = -pos(cx_w) / h
N_c = neg(cy_n) / h;  S_c = -pos(cy_s) / h
C_c = (pos(cx_e) + neg(cx_w) + pos(cy_n) + neg(cy_s)) / h

# Central and total coefficients (rounded)
C_d = -(E_d + W_d + N_d + S_d) + beta
E = r3(E_d + E_c); W = r3(W_d + W_c); Nn = r3(N_d + N_c); S = r3(S_d + S_c)
NE = r3(NE_d); NW = r3(NW_d); SE = r3(SE_d); SW = r3(SW_d); C = r3(C_d + C_c)

# Assemble sparse matrix A and RHS b
rows, cols, data = [], [], []
b = f.reshape(-1).copy()

def idx(i, j):  # zero-based to flat (row-major)
    return i * N + j

for i in range(N):
    for j in range(N):
        p = idx(i, j)
        # center
        rows.append(p); cols.append(p); data.append(C[i, j])
        # E,W,N,S
        if i + 1 < N: rows.append(p); cols.append(idx(i + 1, j)); data.append(E[i, j])
        if i - 1 >= 0: rows.append(p); cols.append(idx(i - 1, j)); data.append(W[i, j])
        if j + 1 < N: rows.append(p); cols.append(idx(i, j + 1)); data.append(Nn[i, j])
        if j - 1 >= 0: rows.append(p); cols.append(idx(i, j - 1)); data.append(S[i, j])
        # NE,NW,SE,SW
        if (i + 1 < N) and (j + 1 < N): rows.append(p); cols.append(idx(i + 1, j + 1)); data.append(NE[i, j])
        if (i - 1 >= 0) and (j + 1 < N): rows.append(p); cols.append(idx(i - 1, j + 1)); data.append(NW[i, j])
        if (i + 1 < N) and (j - 1 >= 0): rows.append(p); cols.append(idx(i + 1, j - 1)); data.append(SE[i, j])
        if (i - 1 >= 0) and (j - 1 >= 0): rows.append(p); cols.append(idx(i - 1, j - 1)); data.append(SW[i, j])

A = coo_matrix((np.array(data, dtype=np.float64), (np.array(rows), np.array(cols))), shape=(N * N, N * N)).tocsr()

# Solve Au = f
u = spsolve(A, b)
u = r3(u).reshape(N, N)

# Centered differences with zero ghosts
u_pad = np.pad(u, ((1, 1), (1, 1)), mode='constant', constant_values=0.0)
ux = (u_pad[2:, 1:-1] - u_pad[:-2, 1:-1]) / (2.0 * h)
uy = (u_pad[1:-1, 2:] - u_pad[1:-1, :-2]) / (2.0 * h)

ux_r = r3(ux); uy_r = r3(uy)

alpha = r3(0.94 + 0.16 * np.sin(3 * x + 2 * y) + 0.08 * x * y)
gamma = r3(0.07 + 0.05 * np.abs(np.sin(2 * x - 3 * y)) + 0.04 * np.abs(np.cos(3 * x + 2 * y)))
delta = 0.01

integrand = r3(alpha * (ux_r * ux_r + uy_r * uy_r)
               + gamma * (np.sqrt(delta * delta + ux_r * ux_r + uy_r * uy_r) - delta)
               + 0.04 * (u * u))

J = r3(h * h * np.sum(integrand))
print(f"{J:.3f}")