From 4940fc9a714b65c1fdad25a4c77b1adfe9eb5631 Mon Sep 17 00:00:00 2001 From: Antoine Regardin Date: Thu, 27 Jun 2024 15:55:25 +0200 Subject: [PATCH] !Incomplete version, does not give good results. Big implementation: Lax-Frierichs. Code cleanup. --- .gitignore | 1 + Library/SATh.py | 391 +++++++++++++++++++++++++++++++------------ Library/utilities.py | 50 +++++- 3 files changed, 330 insertions(+), 112 deletions(-) diff --git a/.gitignore b/.gitignore index 4e99418..6466380 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ test.ipynb plotting.ipynb plots.ipynb new.ipynb +new.py tests.ipynb pyvenv.cfg diff --git a/Library/SATh.py b/Library/SATh.py index 31f2cfe..58cd6d5 100644 --- a/Library/SATh.py +++ b/Library/SATh.py @@ -1,6 +1,7 @@ import numpy as np import scipy.sparse as sparse from scipy.sparse.linalg import gmres +from scipy.optimize import newton_krylov import matplotlib.pyplot as plt from Library.utilities import * @@ -10,44 +11,60 @@ class Problem(): def __init__(self, **kwargs): self.params_dict = { + #Numerical domain "a": 0, "b": 1, "Nx": 500, + "tf": 0.1, "cfl": 5, + "mesh_type": "offset_constantDx", + + #Type of problem and parameters of the associated functions "f": "linear_advection", "alpha": 1, - "mesh_type": "offset_constantDx", - "params": 0.2, + "Boundary": "constant", #"constant"=Dirichlet "init_func": "jump1", - "tf": 0.1, - "Theta_st":.5, - "Theta_min":0.2, - "Theta_max":1, + "init_function_params": [0.2], #Location of the singularity/bell + + #Type of Scheme "Scheme": "SATh", #/"Theta" "Flux": "UP", #/"LF" - "Theta_computation":"Newton", #"Fixed-Point" has been abandoned - "Theta_choice_epsilon":1e-6, - "Theta_choice_method":"MinMax", #/"Smoothing" - "Method_convergence_epsilon":1e-6, + + #Theta-parameters + "Theta_st": 0.5, + "Theta_min": 0.5, + "Theta_max": 1, #This is only useful for plots with different theta values, with the simple theta scheme + + #Methods for Thetas computation and related parameters + "Theta_solving_method": "Newton", #"Fixed-Point" has been abandoned + "Method_convergence_epsilon": 1e-6, + "Theta_choice_method": "MinMax", #/"Smoothing" /->"MinMax" is the discontinuous function + #Parameter to control the Smoothing method Function: + "kappa": 10, + #Parameter for MinMax method threshold: + "Theta_choice_epsilon": 1e-6, + "Newton_solver": "LO_NewtonKrylov", #"Jacobian", #/"LO_NewtonKrylov"-> for Matrix-free Newton Krylov Method (using linear operators) + #In the case of SATh-UP (with "Jacobian" solving method): + "Jacobian_method": "Classic", #/"Smoothing" + + #Others "Timer":False, "print_Newton_iter":False, - "Path": "pictures", - "Boundary":"constant", - "tanh_factor":100, + "Path": "../pictures", } + #replacing by input values in the dict: for key in kwargs.keys(): self.params_dict[key] = kwargs[key] - self.params = self.params_dict["params"] self.tf = self.params_dict["tf"] self.alpha = self.params_dict["alpha"] - self.mesh = Mesh(self.params_dict["a"], self.params_dict["b"], self.params_dict["Nx"], self.params_dict["cfl"], self.params_dict["Boundary"], self.params_dict["mesh_type"]) self.mats = Matrices(self.mesh, self.params_dict["Boundary"], self.alpha) - self.funcs = Functions(self.mesh, self.params, self.tf, self.params_dict["init_func"]) + self.funcs = Functions(self.mesh, self.params_dict["init_function_params"], + self.tf, self.params_dict["init_func"]) if self.params_dict["Scheme"] == "Theta": #Simple/Standard Theta Scheme self.theta = self.params_dict["Theta_st"] @@ -62,44 +79,60 @@ def __init__(self, **kwargs): else: print("Wrong Scheme type in the inputs") - + def print_params(self): return self.params_dict def sol_ex(self): - return self.funcs.exact(self.mesh.nodes, self.params, self.tf) + return self.funcs.exact(self.mesh.nodes, + self.params_dict["init_function_params"], + self.tf) def f(self, u): if self.params_dict["f"] == "linear_advection": return self.alpha * u + + def df(self, u): + if self.params_dict["f"] == "linear_advection": + return self.alpha -class SATh_Solver: #Works only for nonperiodical boundary conditions! +class SATh_Solver: def __init__(self, env): self.env = env self.theta_st = env.params_dict["Theta_st"] self.theta_min = env.params_dict["Theta_min"] self.thetas = np.ones(env.mesh.Nx+1) * self.theta_st + self.dthetas = np.zeros_like(self.thetas) + self.theta_computation = env.params_dict["Theta_solving_method"]# self.u_down = self.env.funcs.init_sol.copy() self.u_up, self.u_til = np.empty_like(self.u_down), np.empty_like(self.u_down) - self.left_bound_val = self.env.funcs.init_sol[0] #!Change to self.bound_vals = [left,right] + self.bound_vals = [self.env.funcs.init_sol[0],self.env.funcs.init_sol[-1]] self.w, self.v = np.empty_like(self.u_down), np.empty_like(self.u_down) + self.f, self.df = self.env.f, self.env.df - self.theta_computation = self.env.params_dict["Theta_computation"] + self.kappa = self.env.params_dict["kappa"] + self.near0_eps = 1e-14 if env.params_dict["Flux"]=="UP": self.alpha = self.env.alpha - self.lam = env.mesh.dt/env.mesh.dx #CFL, here with the case of the flux function f(u) = au + self.lam = env.mesh.dt/env.mesh.dx elif env.params_dict["Flux"]=="LF": - self.alpha = self.alpha_LF() + #self.alpha = self.alpha_LF() + self.alpha = self.env.alpha self.lam = .5 * env.mesh.dt/env.mesh.dx else: print("Unknown Flux type") - - self.tanh_factor = self.env.params_dict["tanh_factor"] + self.Th = Theta_Managing(self) #The functions associated to the choices of thetas are stored away + + # + if self.env.params_dict["Flux"]=="LF" and self.env.params_dict["Jacobian_method"]=="Smoothing": + raise ValueError("Jacobian method 'Smoothing' is not compatible with Lax-Friedrichs") + # + def timer(self, action=None): if action == "set": @@ -107,119 +140,259 @@ def timer(self, action=None): else: print("-",end="") - def sech(self, x): - return 1/np.cosh(x) - def alpha_LF(self): - pass - - def theta_choice(self, i, epsilon=1e-6): - if self.env.params_dict["Theta_choice_method"] == "MinMax": - if np.abs(self.w[i]) > epsilon: - self.thetas[i] = min(max(self.theta_min, np.abs(self.v[i]/self.w[i]) ), 1) + def LF_Newton_Matrices(self, block, i): + #This function is used to build the Jacobian matrix for the "Jacobian" Newton method + mat = np.empty(shape=(2,2)) + + if block=="A": + if np.abs(self.w[i-1])epsilon : + if np.abs(self.w[i])>=epsilon : if self.v[i]/self.w[i] > self.theta_min: - J = np.array([[1, self.alpha * self.lam], [-(self.v[i]**2)*self.alpha*self.lam / (2*(self.w[i]**2)), 1 + self.alpha*self.lam/self.w[i]]]) + J = np.array([[1, self.alpha * self.lam], + [-(self.v[i]**2)*self.alpha*self.lam / (2*(self.w[i]**2)), + 1 + self.alpha*self.lam/self.w[i]]]) else : - J = np.array([[1+self.lam*self.theta_min*self.alpha, 0], [self.lam * self.alpha*(self.theta_min**2) / 2, 1]]) + J = np.array([[1+self.lam*self.theta_min*self.alpha, 0], + [self.lam * self.alpha*(self.theta_min**2) / 2, 1]]) else : - J = np.array([[1+self.lam*self.theta_st*self.alpha, 0], [self.lam * (self.theta_st**2) / 2, 1]]) + J = np.array([[1+self.lam*self.theta_st*self.alpha, 0], + [self.lam * (self.theta_st**2) / 2, 1]]) - elif self.env.params_dict["Flux"] == "LF": - pass + elif self.env.params_dict["Flux"] == "LF": + J = np.zeros(shape=(2*(self.env.mesh.Nx+1),2*(self.env.mesh.Nx+1))) + i=0 + while i < 2*(self.env.mesh.Nx+1): + j = i//2 + if i!=0: + J[i:i+2,i-2:i] = self.LF_Newton_Matrices(block="A",i=j) + J[i:i+2,i:i+2] = self.LF_Newton_Matrices(block="B",i=j) + if i!=2*self.env.mesh.Nx: + J[i:i+2,i+2:i+4] = self.LF_Newton_Matrices(block="C",i=j) + i += 2 + + elif self.env.params_dict["Jacobian_method"] == "Smoothing": + k = self.kappa + if np.abs(self.w[i])>=self.near0_eps: + J = np.array([[1 - (1/4)* k * self.v[i] * self.lam * self.Th.sech(k*(-.5+ self.v[i]/self.w[i]))**2 / self.w[i] + ( + self.lam * (.75 + .25*np.tanh(k*(-.5 + self.v[i]/self.w[i])))) , + + (1/4) * k * self.lam * self.Th.sech(k*(-.5 + self.v[i]/self.w[i]))**2], + + [-((1/4)*k * self.v[i] * self.lam * self.Th.sech(k*(-.5 + self.v[i]/self.w[i]))**2 * ( + .75 + .25*np.tanh(k*(-.5 + self.v[i]/self.w[i])))) / self.w[i] + ( + .5 * self.lam * (.75 + .25*np.tanh(k * (-.5 + self.v[i]/self.w[i])))**2) , + + 1 + (1/4)*k * self.lam * self.Th.sech(k * (-.5 + self.v[i]/self.w[i]))**2 * (3/4 + (1/4)*np.tanh(k * (-.5 + self.v[i]/self.w[i])))]]) + + #print(np.linalg.norm(J)) + else: + self.thetas[i] = 1. + J = np.array([[1+self.lam,0],[self.lam/2,1]]) - if self.env.params_dict["Theta_choice_method"] == "Smoothing": - x = self.tanh_factor - J = np.array([[1 - 25 * self.v[i] * self.lam * self.sech(100*(-.5+ self.v[i]/self.w[i]))**2 / self.w[i] + self.lam * (.75 + .25*np.tanh(x*(-.5 + self.v[i]/self.w[i]))) , - 25 * self.lam * self.sech(100*(-.5 + self.v[i]/self.w[i]))**2], - [-(25 * self.v[i] * self.lam * self.sech(100*(-.5 + self.v[i]/self.w[i]))**2 * (.75 + .25*np.tanh(x*(-.5 + self.v[i]/self.w[i])))) / self.w[i] + .5 * self.lam * (.75 + .25*np.tanh(x * (-.5 + self.v[i]/self.w[i])))**2 , - 1 + 25 * self.lam * self.sech(100 * (-.5 + self.v[i]/self.w[i]))**2 * (3/4 + (1/4)*np.tanh(x * (-.5 + self.v[i]/self.w[i])))]]) - #print(np.linalg.norm(J)) return J def Newton(self, epsilon=1e-6, maxiter=10): w_ = np.copy(self.u_down) w_[0] = 0 - #v = np.empty_like(self.u_down) self.v = self.u_til - self.u_down - self.v[0] = 0 # + self.v[0] = 0 self.w = np.zeros_like(w_) - #(Neumann at the left) iter=0 - while np.linalg.norm(self.w - w_) > epsilon and maxiter>iter: - for i in range(1,self.env.mesh.Nx +1): - w_[i] = self.w[i] - - near0_eps = 1e-12 - #if w[i] != 0: - if np.abs(self.w[i]) > near0_eps: - #update thetas - self.theta_choice(i, epsilon=self.env.params_dict["Theta_choice_epsilon"]) + if self.env.params_dict["Newton_solver"] == "LO_NewtonKrylov" and self.env.params_dict["Flux"]!="UP": + self.storew = self.w.copy() + self.storev = self.v.copy() + X = self.doubletab(self.w,self.v) + #X = np.ones(shape=(self.w.shape[0]*2)) + X = newton_krylov(self.F, X, + iter=maxiter, + verbose=False, + f_tol=epsilon) + #update the thetas: + for i in range(self.w.size): + self.Th.theta_choice(self.thetas, i, + epsilon=self.env.params_dict["Theta_choice_epsilon"]) + self.Th.dthetas_update(self.dthetas) + + """#Does not currently work + elif self.env.params_dict["Newton_solver"] == "Jacobian" or self.env.params_dict["Flux"]=="UP": + + while np.linalg.norm(self.w - w_) >= epsilon and iter= self.solver.theta_min else 0 for elem in self.solver.v/self.solver.w]) + elif self.method == "Smoothing": + for i in range(dthetas.shape[0]): + if np.abs(self.solver.w[i]) > self.near0_eps: + #print("max v",np.max(self.solver.v),"max w",np.max(self.solver.w),"np.max(self.v/self.w)", np.max(self.solver.v/self.solver.w), "argmax v/w:", np.argmax(self.solver.v/self.solver.w)) + dthetas[i] = self.kappa * (1/4) * self.sech((-0.5+self.solver.v[i]/self.solver.w[i])*self.kappa)**2 + else: + dthetas[i] = 0 + + + def theta_choice(self, thetas, i, epsilon=1e-6): + if self.method == "MinMax": + if np.abs(self.solver.w[i]) > epsilon: + thetas[i] = min(max(self.solver.theta_min, np.abs(self.solver.v[i]/self.solver.w[i]) ), 1) + else: + thetas[i] = self.solver.theta_st + + elif self.method == "Smoothing": + #if self.w[i]==0: # + if np.abs(self.solver.w[i])=0 : self.Dx = self.Dx_PtR(mesh, boundary) else: - print("alpha<0 not available yet") + raise ValueError("alpha<0 not available yet") def Dx_PtR(self, mesh, b): if b == "periodical": @@ -91,7 +133,7 @@ def __init__(self, mesh, params, tf, init_type): elif init_type=="jump2": self.init_func = self.init_jump2 else: - print("invalid init function type") + raise ValueError("invalid init function type") self.init_sol = self.init_func(mesh.nodes, params) self.exact_sol = self.exact(mesh.nodes, params, tf) @@ -112,7 +154,7 @@ def init_jump2(self, x, params): # #shape: ___ # ___| |___ if len(params)!=2: - print("2 values needed for the coordinates of the perturbation") + raise ValueError("2 values needed for the coordinates of the perturbation") u = np.zeros_like(x, dtype=float) for i in range(u.shape[0]): if (x[i]=params[0]):