Spaces:
Sleeping
Sleeping
| import os | |
| """ | |
| This module provides utility functions for solving and identifying parameters in a 2D linear elasticity PDE using DeepXDE and PyTorch. The main functionalities include defining the exact solution, constructing the PDE, creating and training PINN models, saving/loading models, and making predictions. | |
| Functions: | |
| - exact_solution(points, lam, mu, Q=4.0): | |
| Computes the exact analytical solution for displacement, stress, and source terms at given points for a 2D elasticity problem with specified Lamé parameters (lambda, mu) and a source parameter Q. | |
| - build_pde(lmbd, mu): | |
| Constructs the PDE residuals for the 2D linear elasticity problem in terms of DeepXDE's autodiff backend, parameterized by trainable Lamé parameters (lmbd, mu). Returns a function suitable for DeepXDE's PDE definition. | |
| - create_model(true_lam, true_mu, init_lam=None, init_mu=None, n_points=5000): | |
| Sets up the DeepXDE model for the elasticity problem, including geometry, boundary conditions, observation points, and the neural network. Returns the model and trainable Lamé variables. | |
| - train_model(model, lmbd, mu, iterations_adam=5000, verbose=True): | |
| Trains the DeepXDE model using Adam and L-BFGS optimizers, updating the Lamé parameters. Returns the estimated parameters, loss history, and training state. | |
| - save_model(model, lmbd, mu, filepath): | |
| Saves the trained model's state dictionary and identified Lamé parameters to a file using PyTorch. | |
| - load_model_for_prediction(filepath): | |
| Loads a saved model checkpoint and returns the neural network and identified Lamé parameters for prediction. | |
| - predict(net, n_points=50): | |
| Uses the trained neural network to predict displacement and stress fields on a regular grid over the domain. Returns results as a dictionary containing grid coordinates and predicted fields. | |
| The module is designed for parameter identification and field prediction in 2D elasticity using physics-informed neural networks (PINNs). | |
| """ | |
| # Exemple d'usage pour chaque fonction du module | |
| os.environ["DDE_BACKEND"] = "pytorch" | |
| import numpy as np | |
| import deepxde as dde | |
| import torch | |
| def exact_solution(points, lam, mu, Q=4.0): | |
| x = points[:, 0:1] | |
| y = points[:, 1:2] | |
| ux = np.cos(2 * np.pi * x) * np.sin(np.pi * y) | |
| uy = np.sin(np.pi * x) * (Q / 4) * y**4 | |
| exx = -2 * np.pi * np.sin(2 * np.pi * x) * np.sin(np.pi * y) | |
| eyy = np.sin(np.pi * x) * Q * y**3 | |
| exy = 0.5 * ( | |
| np.pi * np.cos(2 * np.pi * x) * np.cos(np.pi * y) | |
| + np.pi * np.cos(np.pi * x) * (Q / 4) * y**4 | |
| ) | |
| sxx = (lam + 2 * mu) * exx + lam * eyy | |
| syy = (lam + 2 * mu) * eyy + lam * exx | |
| sxy = 2 * mu * exy | |
| fx = lam * ( | |
| 4 * np.pi**2 * np.cos(2 * np.pi * x) * np.sin(np.pi * y) | |
| - np.pi * np.cos(np.pi * x) * Q * y**3 | |
| ) + mu * ( | |
| 9 * np.pi**2 * np.cos(2 * np.pi * x) * np.sin(np.pi * y) | |
| - np.pi * np.cos(np.pi * x) * Q * y**3 | |
| ) | |
| fy = lam * ( | |
| -3 * np.sin(np.pi * x) * Q * y**2 | |
| + 2 * np.pi**2 * np.sin(2 * np.pi * x) * np.cos(np.pi * y) | |
| ) + mu * ( | |
| -6 * np.sin(np.pi * x) * Q * y**2 | |
| + 2 * np.pi**2 * np.sin(2 * np.pi * x) * np.cos(np.pi * y) | |
| + np.pi**2 * np.sin(np.pi * x) * Q * y**4 / 4 | |
| ) | |
| return ux, uy, sxx, syy, sxy, fx, fy | |
| def build_pde(lmbd, mu): | |
| """ | |
| Construit une fonction représentant le système d'équations aux dérivées partielles (EDP) pour l'élasticité linéaire en 2D, basé sur les paramètres de Lamé. | |
| Paramètres | |
| ---------- | |
| lmbd : float | |
| Premier paramètre de Lamé (lambda), caractérisant le matériau. | |
| mu : float | |
| Deuxième paramètre de Lamé (mu), également appelé module de cisaillement. | |
| Retourne | |
| ------- | |
| pde : function | |
| Fonction prenant en entrée deux arguments : | |
| - x : ndarray, coordonnées spatiales (N, 2) | |
| - y : ndarray, variables dépendantes (N, 5) [u_x, u_y, σ_xx, σ_yy, σ_xy] | |
| et retournant une liste de résidus des équations : | |
| - Équilibre des forces (momentum_x, momentum_y) | |
| - Loi de Hooke (hooke_xx, hooke_yy, hooke_xy) | |
| Notes | |
| ----- | |
| Cette fonction est conçue pour être utilisée avec DeepXDE ou des frameworks similaires pour la résolution de PINNs (Physics-Informed Neural Networks). | |
| Les équations implémentées incluent : | |
| - Les équations de l'équilibre mécanique (momentum) | |
| - Les relations constitutives de Hooke pour un matériau isotrope (hooke) | |
| - Un terme de force volumique analytique (fx, fy) dépendant de x et y | |
| """ | |
| def pde(x, y): | |
| ux = y[:, 0:1] | |
| uy = y[:, 1:2] | |
| sxx = y[:, 2:3] | |
| syy = y[:, 3:4] | |
| sxy = y[:, 4:5] | |
| exx = dde.grad.jacobian(ux, x, i=0, j=0) | |
| eyy = dde.grad.jacobian(uy, x, i=0, j=1) | |
| exy = 0.5 * ( | |
| dde.grad.jacobian(ux, x, i=0, j=1) + dde.grad.jacobian(uy, x, i=0, j=0) | |
| ) | |
| Q = 4.0 | |
| pi = np.pi | |
| cos = dde.backend.cos | |
| sin = dde.backend.sin | |
| fx = lmbd * ( | |
| 4 * pi**2 * cos(2 * pi * x[:, 0:1]) * sin(pi * x[:, 1:2]) | |
| - pi * cos(pi * x[:, 0:1]) * Q * x[:, 1:2] ** 3 | |
| ) + mu * ( | |
| 9 * pi**2 * cos(2 * pi * x[:, 0:1]) * sin(pi * x[:, 1:2]) | |
| - pi * cos(pi * x[:, 0:1]) * Q * x[:, 1:2] ** 3 | |
| ) | |
| fy = lmbd * ( | |
| -3 * sin(pi * x[:, 0:1]) * Q * x[:, 1:2] ** 2 | |
| + 2 * pi**2 * sin(2 * pi * x[:, 0:1]) * cos(pi * x[:, 1:2]) | |
| ) + mu * ( | |
| -6 * sin(pi * x[:, 0:1]) * Q * x[:, 1:2] ** 2 | |
| + 2 * pi**2 * sin(2 * pi * x[:, 0:1]) * cos(pi * x[:, 1:2]) | |
| + pi**2 * sin(pi * x[:, 0:1]) * Q * x[:, 1:2] ** 4 / 4 | |
| ) | |
| sxx_x = dde.grad.jacobian(sxx, x, i=0, j=0) | |
| sxy_x = dde.grad.jacobian(sxy, x, i=0, j=0) | |
| sxy_y = dde.grad.jacobian(sxy, x, i=0, j=1) | |
| syy_y = dde.grad.jacobian(syy, x, i=0, j=1) | |
| momentum_x = sxx_x + sxy_y + fx | |
| momentum_y = sxy_x + syy_y + fy | |
| hooke_xx = (lmbd + 2 * mu) * exx + lmbd * eyy - sxx | |
| hooke_yy = (lmbd + 2 * mu) * eyy + lmbd * exx - syy | |
| hooke_xy = 2 * mu * exy - sxy | |
| return [momentum_x, momentum_y, hooke_xx, hooke_yy, hooke_xy] | |
| return pde | |
| def create_model(true_lam, true_mu, init_lam=None, init_mu=None, n_points=5000): | |
| """ | |
| Crée et retourne un modèle DeepXDE pour résoudre un problème d'élasticité linéaire 2D avec conditions aux limites et observations. | |
| Args: | |
| true_lam (float): Valeur réelle du paramètre de Lamé λ (lambda) pour la solution exacte. | |
| true_mu (float): Valeur réelle du paramètre de Lamé μ (mu) pour la solution exacte. | |
| init_lam (float, optional): Valeur initiale pour λ utilisée dans l'entraînement du modèle. Si None, utilise 2 * true_lam. | |
| init_mu (float, optional): Valeur initiale pour μ utilisée dans l'entraînement du modèle. Si None, utilise 2 * true_mu. | |
| n_points (int, optional): Nombre de points d'observation générés aléatoirement dans le domaine pour les conditions initiales. Par défaut à 5000. | |
| Returns: | |
| tuple: (model, lmbd, mu) | |
| - model (dde.Model): Le modèle DeepXDE construit avec les conditions aux limites, observations et réseau de neurones. | |
| - lmbd (dde.Variable): Variable entraînable représentant λ (lambda). | |
| - mu (dde.Variable): Variable entraînable représentant μ (mu). | |
| Remarques: | |
| - Le domaine géométrique est un carré [0, 1] x [0, 1]. | |
| - Les conditions de Dirichlet sont appliquées sur le bord gauche (ux=0) et le bord inférieur (uy=0). | |
| - Les observations sont générées à partir de la solution exacte fournie par la fonction `exact_solution`. | |
| - Le réseau de neurones utilisé est un FNN à 4 couches cachées de 50 neurones chacune, avec activation "tanh". | |
| """ | |
| if init_lam is None: | |
| init_lam = true_lam * 2.0 | |
| if init_mu is None: | |
| init_mu = true_mu * 2.0 | |
| lmbd = dde.Variable(init_lam) | |
| mu = dde.Variable(init_mu) | |
| geom = dde.geometry.Rectangle([0, 0], [1, 1]) | |
| def boundary_left(x, on_boundary): | |
| return on_boundary and np.isclose(x[0], 0) | |
| def boundary_bottom(x, on_boundary): | |
| return on_boundary and np.isclose(x[1], 0) | |
| bc_ux = dde.icbc.DirichletBC(geom, lambda x: 0.0, boundary_left, component=0) | |
| bc_uy = dde.icbc.DirichletBC(geom, lambda x: 0.0, boundary_bottom, component=1) | |
| pde_func = build_pde(lmbd, mu) | |
| X_train = geom.random_points(n_points) | |
| u_ex, v_ex, sxx_ex, syy_ex, sxy_ex, _, _ = exact_solution( | |
| X_train, true_lam, true_mu | |
| ) | |
| obs_values = np.hstack([u_ex, v_ex, sxx_ex, syy_ex, sxy_ex]) | |
| observations = dde.icbc.PointSetBC(X_train, obs_values, component=[0, 1, 2, 3, 4]) | |
| data = dde.data.PDE( | |
| geom, | |
| pde_func, | |
| [bc_ux, bc_uy, observations], | |
| num_domain=2000, | |
| num_boundary=500, | |
| num_test=5000, | |
| ) | |
| net = dde.nn.FNN([2] + [50] * 4 + [5], "tanh", "Glorot uniform") | |
| model = dde.Model(data, net) | |
| return model, lmbd, mu | |
| def train_model(model, lmbd, mu, iterations_adam=5000, verbose=True): | |
| model.compile("adam", lr=0.001, external_trainable_variables=[lmbd, mu]) | |
| losshistory, train_state = model.train( | |
| iterations=iterations_adam, display_every=1000 if verbose else 10000 | |
| ) | |
| model.compile("L-BFGS", external_trainable_variables=[lmbd, mu]) | |
| model.train(display_every=1000 if verbose else 10000) | |
| try: | |
| lambda_est = lmbd.item() | |
| mu_est = mu.item() | |
| except: | |
| lambda_est = float(lmbd.detach().cpu().numpy()) | |
| mu_est = float(mu.detach().cpu().numpy()) | |
| return lambda_est, mu_est, losshistory, train_state | |
| def save_model(model, lmbd, mu, filepath): | |
| torch.save( | |
| { | |
| "model_state_dict": model.net.state_dict(), | |
| "lambda_identified": lmbd.item() if hasattr(lmbd, "item") else float(lmbd), | |
| "mu_identified": mu.item() if hasattr(mu, "item") else float(mu), | |
| }, | |
| filepath, | |
| ) | |
| def load_model_for_prediction(filepath): | |
| net = dde.nn.FNN([2] + [50] * 4 + [5], "tanh", "Glorot uniform") | |
| checkpoint = torch.load(filepath, map_location="cpu") | |
| net.load_state_dict(checkpoint["model_state_dict"]) | |
| lambda_id = checkpoint.get("lambda_identified", None) | |
| mu_id = checkpoint.get("mu_identified", None) | |
| return net, lambda_id, mu_id | |
| def predict(net, n_points=50): | |
| x_lin = np.linspace(0, 1, n_points) | |
| y_lin = np.linspace(0, 1, n_points) | |
| X_grid, Y_grid = np.meshgrid(x_lin, y_lin) | |
| X_plot = np.hstack((X_grid.flatten()[:, None], Y_grid.flatten()[:, None])) | |
| X_tensor = torch.tensor(X_plot, dtype=torch.float32) | |
| with torch.no_grad(): | |
| y_pred = net(X_tensor).numpy() | |
| return { | |
| "X_grid": X_grid, | |
| "Y_grid": Y_grid, | |
| "u_x": y_pred[:, 0].reshape(n_points, n_points), | |
| "u_y": y_pred[:, 1].reshape(n_points, n_points), | |
| "s_xx": y_pred[:, 2].reshape(n_points, n_points), | |
| "s_yy": y_pred[:, 3].reshape(n_points, n_points), | |
| "s_xy": y_pred[:, 4].reshape(n_points, n_points), | |
| } | |
| def generate_noisy_fem_data(points, lam, mu, noise_level=0.01, seed=None): | |
| """ | |
| Génère des données simulées avec bruit gaussien pour simuler des mesures FEM ou expérimentales. | |
| Cette fonction est utilisée pour: | |
| - Simuler des données expérimentales réalistes | |
| - Tester la robustesse du PINN face au bruit | |
| - Générer des datasets pour l'identification de matériaux inconnus | |
| Args: | |
| points: Points d'observation (N, 2) | |
| lam: Paramètre λ (lambda) | |
| mu: Paramètre μ (mu) | |
| noise_level: Niveau de bruit relatif (défaut: 1%) | |
| seed: Graine aléatoire pour reproductibilité | |
| Returns: | |
| tuple: (ux, uy, sxx, syy, sxy, fx, fy) avec bruit ajouté | |
| """ | |
| if seed is not None: | |
| np.random.seed(seed) | |
| ux, uy, sxx, syy, sxy, fx, fy = exact_solution(points, lam, mu) | |
| ux_noisy = ux + np.random.normal(0, noise_level * np.abs(ux).mean(), ux.shape) | |
| uy_noisy = uy + np.random.normal(0, noise_level * np.abs(uy).mean(), uy.shape) | |
| sxx_noisy = sxx + np.random.normal(0, noise_level * np.abs(sxx).mean(), sxx.shape) | |
| syy_noisy = syy + np.random.normal(0, noise_level * np.abs(syy).mean(), syy.shape) | |
| sxy_noisy = sxy + np.random.normal(0, noise_level * np.abs(sxy).mean(), sxy.shape) | |
| return ux_noisy, uy_noisy, sxx_noisy, syy_noisy, sxy_noisy, fx, fy | |
| def create_model_with_noisy_data( | |
| true_lam, | |
| true_mu, | |
| noise_level=0.01, | |
| init_lam=None, | |
| init_mu=None, | |
| n_points=5000, | |
| seed=None, | |
| ): | |
| """ | |
| Crée un modèle avec des données bruitées (simulation FEM/expérimentales). | |
| Args: | |
| true_lam: Valeur réelle de λ | |
| true_mu: Valeur réelle de μ | |
| noise_level: Niveau de bruit (défaut: 1%) | |
| init_lam: Valeur initiale pour l'identification | |
| init_mu: Valeur initiale pour l'identification | |
| n_points: Nombre de points d'observation | |
| seed: Graine aléatoire | |
| Returns: | |
| tuple: (model, lmbd, mu) | |
| """ | |
| if init_lam is None: | |
| init_lam = true_lam * 2.0 | |
| if init_mu is None: | |
| init_mu = true_mu * 2.0 | |
| lmbd = dde.Variable(init_lam) | |
| mu = dde.Variable(init_mu) | |
| geom = dde.geometry.Rectangle([0, 0], [1, 1]) | |
| def boundary_left(x, on_boundary): | |
| return on_boundary and np.isclose(x[0], 0) | |
| def boundary_bottom(x, on_boundary): | |
| return on_boundary and np.isclose(x[1], 0) | |
| bc_ux = dde.icbc.DirichletBC(geom, lambda x: 0.0, boundary_left, component=0) | |
| bc_uy = dde.icbc.DirichletBC(geom, lambda x: 0.0, boundary_bottom, component=1) | |
| pde_func = build_pde(lmbd, mu) | |
| X_train = geom.random_points(n_points) | |
| u_ex, v_ex, sxx_ex, syy_ex, sxy_ex, _, _ = generate_noisy_fem_data( | |
| X_train, true_lam, true_mu, noise_level=noise_level, seed=seed | |
| ) | |
| obs_values = np.hstack([u_ex, v_ex, sxx_ex, syy_ex, sxy_ex]) | |
| observations = dde.icbc.PointSetBC(X_train, obs_values, component=[0, 1, 2, 3, 4]) | |
| data = dde.data.PDE( | |
| geom, | |
| pde_func, | |
| [bc_ux, bc_uy, observations], | |
| num_domain=2000, | |
| num_boundary=500, | |
| num_test=5000, | |
| ) | |
| net = dde.nn.FNN([2] + [50] * 4 + [5], "tanh", "Glorot uniform") | |
| model = dde.Model(data, net) | |
| return model, lmbd, mu | |
| if __name__ == "__main__": | |
| # Paramètres de Lamé vrais | |
| true_lam = 2.0 | |
| true_mu = 1.0 | |
| # 1. Exemple pour exact_solution | |
| points = np.array([[0.5, 0.5], [0.1, 0.9]]) | |
| ux, uy, sxx, syy, sxy, fx, fy = exact_solution(points, true_lam, true_mu) | |
| print("Exemple exact_solution:") | |
| print("ux:", ux) | |
| print("uy:", uy) | |
| # 2. Exemple pour build_pde | |
| # (Utilisé dans create_model, voir ci-dessous) | |
| # 3. Exemple pour create_model | |
| model, lmbd, mu = create_model(true_lam, true_mu) | |
| print("Exemple create_model:") | |
| print("Modèle créé avec lmbd initial:", lmbd.item(), "et mu initial:", mu.item()) | |
| # 4. Exemple pour train_model | |
| lambda_est, mu_est, losshistory, train_state = train_model( | |
| model, lmbd, mu, iterations_adam=100 | |
| ) | |
| print("Exemple train_model:") | |
| print("Paramètres identifiés:", lambda_est, mu_est) | |
| # 5. Exemple pour save_model | |
| save_path = "modele_elasticite.pth" | |
| save_model(model, lmbd, mu, save_path) | |
| print(f"Exemple save_model: modèle sauvegardé dans {save_path}") | |
| # 6. Exemple pour load_model_for_prediction | |
| net_loaded, lambda_id, mu_id = load_model_for_prediction(save_path) | |
| print("Exemple load_model_for_prediction:") | |
| print("lambda identifié:", lambda_id, "mu identifié:", mu_id) | |
| # 7. Exemple pour predict | |
| results = predict(net_loaded, n_points=10) | |
| print("Exemple predict:") | |
| print("u_x shape:", results["u_x"].shape) | |
| print("u_y shape:", results["u_y"].shape) | |