I am trying to simulate grain boundary properties in a ferroelectric bicrystal using FiPy. I have basically three variables to solve namely, degree of crystalinity (eta), polarization magnitude in the x and y direction(polx and poly) and the electric potential (voltage). The problem is based on the phase-field modelling and uses Allen-Cahn and Cahn-Hilliard equations.
While trying to solve the pde's through sweep method, using LinearLUSolver I am facing problem because the code seems to work fine untill 200 time steps, but after this the code doesnot work as expected and the plots are very weird with stripes and patterns I wouldn't imagine. please, help me what I should be doing to make the code work for more number of timesteps because I am trying to simulate the result from a reserach paper which would be my benchmarking step.
The code is given below.
from fipy.solvers import LinearPCGSolver, LinearGMRESSolver, LinearLUSolver
from fipy import MatplotlibViewer
import os
from fipy import dump
# Ensure the directory exists
output_dir = "PickleJar"
os.makedirs(output_dir, exist_ok=True)
# Initialize the solver with a specified tolerance
solver1 = LinearGMRESSolver(tolerance=1e-15)
solver2 = LinearLUSolver(tolerance=1e-10)
print("Solver initialized with tolerance:", solver1.tolerance)
# Other variables
div_u = CellVariable(name='div_u', mesh=mesh)
psi = CellVariable(name="psi", mesh=mesh)
mag_u = CellVariable(name='dimensionless pol', mesh=mesh)
# Save initial state
dump.write(data={
'crystalline order': eta,
'dimensionless voltage': volt_hat,
'theta': theta,
'globalux': ux,
'globaluy': uy,
}, filename='PickleJar/IN_phasefullbc14march_Cellvariables_00000.var')
dt = 2 * 5e-5
timesteps = 2500
# timestep_switch = 500
j = 0 # Counter for file naming
save_interval = 500 # Save data every 1000 timesteps
# Initialize viewers for eta, volt_hat, ux, and uy
eta_viewer = MatplotlibViewer(vars=eta, title="eta Field")
volt_hat_viewer = MatplotlibViewer(vars=volt_hat, title="Dimensionless Voltage (volt_hat)")
mag_u_viewer = MatplotlibViewer(vars=mag_u, title="polarization magnitude (mag_u)")
div_u_viewer = MatplotlibViewer(vars=div_u, title="divergence of polarization (div_u)")
psi_viewer = MatplotlibViewer(vars=psi, title="arctan polarization (psi)")
for i in range(timesteps):
# if i == timestep_switch:
# dt = 2 * 5e-4 # increase timestep after 1000 iterations
# print(f"\nTimestep increased to {dt} at iteration {i}")
div_u.setValue(ux.grad[0] + uy.grad[1])
psi.setValue(np.arctan2(uy, ux))
mag_u.setValue(np.sqrt(ux**2 + uy**2))
# Update old values only once per timestep
eta.updateOld()
ux.updateOld()
uy.updateOld()
volt_hat.updateOld()
# Plot the current values for eta, volt_hat, ux, and uy
eta_viewer.plot()
volt_hat_viewer.plot()
mag_u_viewer.plot()
div_u_viewer.plot()
psi_viewer.plot()
# Print which timestep we are on
print(f"\nStarting timestep {i + 1}/{timesteps}")
# Initialize residual and sweep count
res1 = res2 = 1e+100
max_res = 1e+100
sweep = 0
# Perform sweeps to achieve convergence within this timestep
while (max_res := max(res1, res2)) > 1e-3 and sweep < 30:
# Solve the equations for this timestep
res1 = etaEq.sweep(dt=dt, solver=solver1)
res2 = eqn1.sweep(dt=dt, solver=solver1)
sweep += 1
# Print sweep number and residual
print(f" Sweep {sweep}: Residual = {max_res:.5e}")
# Check if convergence was achieved within the sweep limit
if max_res <= 1e-3:
print(f" Converged after {sweep} sweeps with final residual {max_res:.5e}")
else:
print(f" Max sweeps reached without full convergence. Final residual: {max_res:.5e}")