physai 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
physai/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from .pinn import PINN
2
+ from .physics import *
3
+ from .losses import *
4
+ from .trainer import *
5
+ from .visualization import *
6
+ from .utils import *
physai/losses.py ADDED
@@ -0,0 +1,66 @@
1
+ import torch
2
+
3
+ # ------------------------------
4
+ # Basic Residual Loss
5
+ # ------------------------------
6
+ def residual_loss(residual):
7
+ """
8
+ Compute the mean squared residual loss for PINNs.
9
+ residual: tensor or tuple of tensors (for systems like Navier-Stokes)
10
+ """
11
+ if isinstance(residual, tuple):
12
+ # Sum of losses for multiple residuals
13
+ return sum(torch.mean(r**2) for r in residual)
14
+ else:
15
+ return torch.mean(residual**2)
16
+
17
+ # ------------------------------
18
+ # Boundary / Initial Condition Loss
19
+ # ------------------------------
20
+ def bc_loss(pred, target):
21
+ """
22
+ Compute MSE loss for boundary or initial conditions.
23
+ pred: predicted values at BC/IC points
24
+ target: true values at BC/IC points
25
+ """
26
+ return torch.mean((pred - target)**2)
27
+
28
+ # ------------------------------
29
+ # Combined PINN Loss
30
+ # ------------------------------
31
+ def pinn_loss(model, collocation_points, pde_type, bc_points=None, bc_values=None, **kwargs):
32
+ """
33
+ Compute total loss for PINNs: PDE residual + BC loss
34
+ model: PINN model
35
+ collocation_points: points where PDE is enforced
36
+ pde_type: string specifying PDE/ODE type
37
+ bc_points: tensor for boundary/initial points
38
+ bc_values: tensor for boundary/initial values
39
+ kwargs: extra parameters for PDE residual (nu, r, K, gamma, etc.)
40
+ """
41
+ from physai.physics import pde_residual
42
+
43
+ # PDE Residual
44
+ residual = pde_residual(model, collocation_points, pde_type, **kwargs)
45
+ res_loss = residual_loss(residual)
46
+
47
+ # Boundary / Initial Condition Loss
48
+ bc_l = torch.tensor(0.0)
49
+ if bc_points is not None and bc_values is not None:
50
+ pred_bc = model(bc_points)
51
+ bc_l = bc_loss(pred_bc, bc_values)
52
+
53
+ # Total loss (sum)
54
+ total_loss = res_loss + bc_l
55
+ return total_loss, res_loss, bc_l
56
+
57
+ # ------------------------------
58
+ # Weighted Loss
59
+ # ------------------------------
60
+ def weighted_pinn_loss(model, collocation_points, pde_type, bc_points=None, bc_values=None, pde_weight=1.0, bc_weight=1.0, **kwargs):
61
+ """
62
+ Weighted PINN loss for flexibility
63
+ """
64
+ total, res_loss, bc_l = pinn_loss(model, collocation_points, pde_type, bc_points, bc_values, **kwargs)
65
+ total = pde_weight*res_loss + bc_weight*bc_l
66
+ return total, res_loss, bc_l
physai/pde_residual.py ADDED
@@ -0,0 +1,128 @@
1
+ import torch
2
+
3
+ # ------------------------------
4
+ # Utility: derivatives
5
+ # ------------------------------
6
+ def derivative(y, x, order=1):
7
+ for _ in range(order):
8
+ y = torch.autograd.grad(y, x, grad_outputs=torch.ones_like(y), create_graph=True)[0]
9
+ return y
10
+
11
+ # ------------------------------
12
+ # Unified PDE/ODE Residual
13
+ # ------------------------------
14
+ def pde_residual(model, inputs, pde_type, **kwargs):
15
+ """
16
+ Compute residual for various ODEs/PDEs automatically.
17
+
18
+ model: torch.nn model
19
+ inputs: tensor of shape [N, D] where D = 1,2,3 (x, y, z / x, t / x, y, t)
20
+ pde_type: str, e.g. "heat", "wave", "burgers", "kdv", "laplace", "poisson", "schrodinger",
21
+ "navier_stokes_2d", "logistic", "sho", "damped_ho",
22
+ "markov", "planck", "newton_cooling", "photoelectric"
23
+ kwargs: extra parameters (nu, c, alpha, beta, V, f, r, K, gamma, etc.)
24
+ """
25
+
26
+ # Enable gradients
27
+ inputs = inputs.clone().detach().requires_grad_(True)
28
+ u_val = model(inputs)
29
+
30
+ # ------------------ 1D ODEs ------------------
31
+ if pde_type == "logistic":
32
+ r = kwargs.get("r", 1.0)
33
+ K = kwargs.get("K", 1.0)
34
+ return derivative(u_val, inputs[:,0:1]) - r*u_val*(1 - u_val/K)
35
+
36
+ if pde_type == "sho":
37
+ return derivative(u_val, inputs[:,0:1], 2) + u_val
38
+
39
+ if pde_type == "damped_ho":
40
+ gamma = kwargs.get("gamma", 0.1)
41
+ return derivative(u_val, inputs[:,0:1], 2) + gamma*derivative(u_val, inputs[:,0:1]) + u_val
42
+
43
+ if pde_type == "newton_cooling":
44
+ T_env = kwargs.get("T_env", 25.0)
45
+ k = kwargs.get("k", 0.1)
46
+ return derivative(u_val, inputs[:,0:1]) + k*(u_val - T_env)
47
+
48
+ # ------------------ 1D PDEs ------------------
49
+ if pde_type == "heat":
50
+ x = inputs[:,0:1]; t = inputs[:,1:2]
51
+ return derivative(u_val, t) - derivative(u_val, x, 2)
52
+
53
+ if pde_type == "wave":
54
+ x = inputs[:,0:1]; t = inputs[:,1:2]
55
+ c = kwargs.get("c", 1.0)
56
+ return derivative(u_val, t, 2) - c**2 * derivative(u_val, x, 2)
57
+
58
+ if pde_type == "burgers":
59
+ x = inputs[:,0:1]; t = inputs[:,1:2]; nu = kwargs.get("nu", 0.01)
60
+ return derivative(u_val, t) + u_val*derivative(u_val, x) - nu*derivative(u_val, x, 2)
61
+
62
+ if pde_type == "kdv":
63
+ x = inputs[:,0:1]; t = inputs[:,1:2]
64
+ alpha = kwargs.get("alpha", 6.0)
65
+ beta = kwargs.get("beta", 1.0)
66
+ return derivative(u_val, t) + alpha*u_val*derivative(u_val, x) + beta*derivative(u_val, x, 3)
67
+
68
+ if pde_type == "convection_diffusion":
69
+ x = inputs[:,0:1]; t = inputs[:,1:2]
70
+ c = kwargs.get("c", 1.0); D = kwargs.get("D", 0.01)
71
+ return derivative(u_val, t) + c*derivative(u_val, x) - D*derivative(u_val, x, 2)
72
+
73
+ if pde_type == "markov":
74
+ x = inputs[:,0:1]; t = inputs[:,1:2]
75
+ D = kwargs.get("D", 0.1)
76
+ return derivative(u_val, t) - D*derivative(u_val, x, 2)
77
+
78
+ if pde_type == "planck":
79
+ freq = inputs[:,0:1]; T = inputs[:,1:2]
80
+ exact = kwargs.get("exact_planck")
81
+ if exact is None:
82
+ raise ValueError("Provide exact_planck(freq, T) function for Planck residual")
83
+ return u_val - exact(freq, T)
84
+
85
+ if pde_type == "photoelectric":
86
+ freq = inputs[:,0:1]; work_func = kwargs.get("work_func", 1.0)
87
+ return u_val - torch.maximum(torch.zeros_like(freq), freq - work_func)
88
+
89
+ # ------------------ 2D PDEs ------------------
90
+ if pde_type == "laplace":
91
+ x, y = inputs[:,0:1], inputs[:,1:2]
92
+ return derivative(u_val, x, 2) + derivative(u_val, y, 2)
93
+
94
+ if pde_type == "poisson":
95
+ x, y = inputs[:,0:1], inputs[:,1:2]
96
+ f = kwargs.get("f", lambda x,y: torch.zeros_like(x))
97
+ return derivative(u_val, x, 2) + derivative(u_val, y, 2) - f(x, y)
98
+
99
+ if pde_type == "schrodinger":
100
+ x, t = inputs[:,0:1], inputs[:,1:2]
101
+ hbar = kwargs.get("hbar", 1.0); m = kwargs.get("m", 1.0)
102
+ V = kwargs.get("V", None)
103
+ V_val = V(inputs) if V is not None else torch.zeros_like(u_val)
104
+ return 1j*hbar*derivative(u_val, t) + (hbar**2/(2*m))*derivative(u_val, x, 2) - V_val*u_val
105
+
106
+ if pde_type == "navier_stokes_2d":
107
+ x, y, t = inputs[:,0:1], inputs[:,1:2], inputs[:,2:3]
108
+ model_u = kwargs.get("model_u")
109
+ model_v = kwargs.get("model_v")
110
+ model_p = kwargs.get("model_p")
111
+ nu = kwargs.get("nu", 0.01)
112
+ U = model_u(inputs); V = model_v(inputs); P = model_p(inputs)
113
+ res_u = derivative(U, t) + U*derivative(U, x) + V*derivative(U, y) + derivative(P, x) - nu*(derivative(U, x,2)+derivative(U, y,2))
114
+ res_v = derivative(V, t) + U*derivative(V, x) + V*derivative(V, y) + derivative(P, y) - nu*(derivative(V, x,2)+derivative(V, y,2))
115
+ res_cont = derivative(U, x) + derivative(V, y)
116
+ return res_u, res_v, res_cont
117
+
118
+ # ------------------ 3D PDEs ------------------
119
+ if pde_type == "laplace_3d":
120
+ x, y, z = inputs[:,0:1], inputs[:,1:2], inputs[:,2:3]
121
+ return derivative(u_val, x, 2) + derivative(u_val, y, 2) + derivative(u_val, z, 2)
122
+
123
+ if pde_type == "poisson_3d":
124
+ x, y, z = inputs[:,0:1], inputs[:,1:2], inputs[:,2:3]
125
+ f = kwargs.get("f", lambda x,y,z: torch.zeros_like(x))
126
+ return derivative(u_val, x, 2) + derivative(u_val, y, 2) + derivative(u_val, z, 2) - f(x,y,z)
127
+
128
+ raise ValueError(f"PDE/ODE type '{pde_type}' not implemented")
physai/physics.py ADDED
@@ -0,0 +1,76 @@
1
+ import torch
2
+
3
+ def derivative(y, x, order=1):
4
+ for _ in range(order):
5
+ y = torch.autograd.grad(y, x, grad_outputs=torch.ones_like(y), create_graph=True)[0]
6
+ return y
7
+
8
+ # 1D ODEs
9
+ def dy_dx_equals_y(x, model):
10
+ x.requires_grad_(True)
11
+ y = model(x)
12
+ return derivative(y, x) - y
13
+
14
+ def dy_dx_equals_func(x, model, func):
15
+ x.requires_grad_(True)
16
+ y = model(x)
17
+ return derivative(y, x) - func(x)
18
+
19
+ def second_order_ode(x, model):
20
+ x.requires_grad_(True)
21
+ y = model(x)
22
+ return derivative(y, x, 2) + y
23
+
24
+ def damped_harmonic_oscillator(x, model, gamma=0.1):
25
+ x.requires_grad_(True)
26
+ y = model(x)
27
+ return derivative(y, x, 2) + gamma*derivative(y, x) + y
28
+
29
+ def logistic_growth(x, model, r=1.0, K=1.0):
30
+ x.requires_grad_(True)
31
+ y = model(x)
32
+ return derivative(y, x) - r*y*(1 - y/K)
33
+
34
+ # 1D PDEs
35
+ def heat_equation(u, x, t, model):
36
+ x.requires_grad_(True)
37
+ t.requires_grad_(True)
38
+ u_val = model(torch.cat([x, t], dim=1))
39
+ return derivative(u_val, t) - derivative(u_val, x, 2)
40
+
41
+ def wave_equation(u, x, t, model, c=1.0):
42
+ x.requires_grad_(True)
43
+ t.requires_grad_(True)
44
+ u_val = model(torch.cat([x, t], dim=1))
45
+ return derivative(u_val, t, 2) - c**2 * derivative(u_val, x, 2)
46
+
47
+ def burgers_equation(u, x, t, model, nu=0.01):
48
+ x.requires_grad_(True)
49
+ t.requires_grad_(True)
50
+ u_val = model(torch.cat([x, t], dim=1))
51
+ return derivative(u_val, t) + u_val*derivative(u_val, x) - nu*derivative(u_val, x, 2)
52
+
53
+ def kdv_equation(u, x, t, model, alpha=6.0, beta=1.0):
54
+ x.requires_grad_(True)
55
+ t.requires_grad_(True)
56
+ u_val = model(torch.cat([x, t], dim=1))
57
+ return derivative(u_val, t) + alpha*u_val*derivative(u_val, x) + beta*derivative(u_val, x, 3)
58
+
59
+ def convection_diffusion(u, x, t, model, c=1.0, D=0.01):
60
+ x.requires_grad_(True)
61
+ t.requires_grad_(True)
62
+ u_val = model(torch.cat([x, t], dim=1))
63
+ return derivative(u_val, t) + c*derivative(u_val, x) - D*derivative(u_val, x, 2)
64
+
65
+ # 2D PDEs
66
+ def laplace_equation(u, x, y, model):
67
+ x.requires_grad_(True)
68
+ y.requires_grad_(True)
69
+ u_val = model(torch.cat([x, y], dim=1))
70
+ return derivative(u_val, x, 2) + derivative(u_val, y, 2)
71
+
72
+ def poisson_equation(u, x, y, f, model):
73
+ x.requires_grad_(True)
74
+ y.requires_grad_(True)
75
+ u_val = model(torch.cat([x, y], dim=1))
76
+ return derivative(u_val, x, 2) + derivative(u_val, y, 2) - f(x, y)
physai/pinn.py ADDED
@@ -0,0 +1,78 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.optim as optim
4
+ from torch.cuda.amp import autocast
5
+ from torch.amp import grad_scaler as GradScaler
6
+ class PINN(nn.Module):
7
+ """Physics-Informed Neural Network (PINN) with advanced optimization features."""
8
+
9
+ def __init__(self, layers, activation='tanh', device=None):
10
+ super().__init__()
11
+ # Set device
12
+ self.device = device if device else ('cuda' if torch.cuda.is_available() else 'cpu')
13
+ # Build the neural network
14
+ self.model = self._build_network(layers, activation).to(self.device)
15
+ # Store training history
16
+ self.history = {"loss": []}
17
+
18
+ def _build_network(self, layers, activation):
19
+ """Construct a fully connected network with specified activation and Xavier initialization."""
20
+ net = []
21
+ activations = {
22
+ 'tanh': nn.Tanh(),
23
+ 'relu': nn.ReLU(),
24
+ 'gelu': nn.GELU(),
25
+ 'silu': nn.SiLU()
26
+ }
27
+ act = activations.get(activation.lower(), nn.Tanh())
28
+
29
+ for i in range(len(layers) - 1):
30
+ linear = nn.Linear(layers[i], layers[i + 1])
31
+ nn.init.xavier_uniform_(linear.weight)
32
+ nn.init.zeros_(linear.bias)
33
+ net.append(linear)
34
+ if i < len(layers) - 2:
35
+ net.append(act)
36
+ return nn.Sequential(*net)
37
+
38
+ def forward(self, x):
39
+ return self.model(x)
40
+
41
+ def physics_loss(self, x, physics_fn):
42
+ """Compute physics-integrated loss."""
43
+ x = x.to(self.device).requires_grad_(True)
44
+ y = self.forward(x)
45
+ residual = physics_fn(x, y)
46
+ return torch.mean(residual ** 2)
47
+
48
+ def train_model(self, x, physics_fn, lr=1e-3, epochs=1000, verbose=True,
49
+ clip_grad=None, scheduler=None, use_amp=True):
50
+ """Train the PINN with advanced options: AMP, gradient clipping, scheduler."""
51
+ optimizer = optim.Adam(self.parameters(), lr=lr)
52
+ scaler = GradScaler(enabled=use_amp)
53
+
54
+ if scheduler:
55
+ scheduler = scheduler(optimizer)
56
+
57
+ for epoch in range(epochs):
58
+ optimizer.zero_grad()
59
+ with autocast(enabled=use_amp):
60
+ loss = self.physics_loss(x, physics_fn)
61
+
62
+ scaler.scale(loss).backward()
63
+
64
+ if clip_grad:
65
+ scaler.unscale_(optimizer)
66
+ nn.utils.clip_grad_norm_(self.parameters(), clip_grad)
67
+
68
+ scaler.step(optimizer)
69
+ scaler.update()
70
+
71
+ if scheduler:
72
+ scheduler.step()
73
+
74
+ self.history["loss"].append(loss.item())
75
+ if verbose and (epoch % max(epochs // 10, 1) == 0 or epoch == epochs - 1):
76
+ print(f"Epoch {epoch+1}/{epochs} - Loss: {loss.item():.6f}")
77
+
78
+ return self.history
physai/trainer.py ADDED
@@ -0,0 +1,68 @@
1
+ import torch
2
+ from torch import optim
3
+ from .losses import pinn_loss
4
+ from .visualization import plot_loss
5
+
6
+ class Trainer:
7
+ """
8
+ Trainer class for Physics-Informed Neural Networks (PINNs)
9
+ Supports:
10
+ - Mixed precision training (AMP)
11
+ - Gradient clipping
12
+ - Optional learning rate scheduler
13
+ - Logging & plotting
14
+ - Flexible handling of ODE/PDE types
15
+ """
16
+
17
+ def __init__(self, model, collocation_points, pde_type, bc_points=None, bc_values=None, device=None):
18
+ self.model = model
19
+ self.x = collocation_points
20
+ self.pde_type = pde_type
21
+ self.bc_x = bc_points
22
+ self.bc_y = bc_values
23
+ self.device = device or ('cuda' if torch.cuda.is_available() else 'cpu')
24
+
25
+ self.model.to(self.device)
26
+ self.history = {"total_loss": [], "res_loss": [], "bc_loss": []}
27
+ self.scaler = torch.cuda.amp.GradScaler(enabled=self.device.startswith("cuda"))
28
+
29
+ def train(self, epochs=1000, lr=1e-3, scheduler_fn=None, clip_grad=None, verbose=True, **kwargs):
30
+ optimizer = optim.Adam(self.model.parameters(), lr=lr)
31
+ scheduler = scheduler_fn(optimizer) if scheduler_fn else None
32
+
33
+ x = self.x.to(self.device)
34
+ bc_x = self.bc_x.to(self.device) if self.bc_x is not None else None
35
+ bc_y = self.bc_y.to(self.device) if self.bc_y is not None else None
36
+
37
+ for epoch in range(epochs):
38
+ optimizer.zero_grad()
39
+ with torch.cuda.amp.autocast(enabled=self.device.startswith("cuda")):
40
+ total, res_l, bc_l = pinn_loss(
41
+ self.model, x, self.pde_type, bc_points=bc_x, bc_values=bc_y, **kwargs
42
+ )
43
+
44
+ self.scaler.scale(total).backward()
45
+
46
+ if clip_grad:
47
+ self.scaler.unscale_(optimizer)
48
+ torch.nn.utils.clip_grad_norm_(self.model.parameters(), clip_grad)
49
+
50
+ self.scaler.step(optimizer)
51
+ self.scaler.update()
52
+
53
+ if scheduler:
54
+ scheduler.step()
55
+
56
+ self.history["total_loss"].append(total.item())
57
+ self.history["res_loss"].append(res_l.item())
58
+ self.history["bc_loss"].append(bc_l.item())
59
+
60
+ if verbose and (epoch % max(epochs // 10, 1) == 0 or epoch == epochs - 1):
61
+ print(f"Epoch {epoch+1}/{epochs} | Total: {total.item():.6e} | "
62
+ f"Res: {res_l.item():.6e} | BC: {bc_l.item():.6e}")
63
+
64
+ return self.history
65
+
66
+ def plot_training_loss(self):
67
+ """Plot training loss curves"""
68
+ plot_loss(self.history, title=f"Training Loss for {self.pde_type}")
physai/utils.py ADDED
@@ -0,0 +1,4 @@
1
+ import torch
2
+
3
+ def to_device(tensor, device='cpu'):
4
+ return tensor.to(device)
@@ -0,0 +1,137 @@
1
+ import torch
2
+ import matplotlib.pyplot as plt
3
+ import matplotlib.animation as animation
4
+ from mpl_toolkits.mplot3d import Axes3D
5
+
6
+ # ------------------------------
7
+ # 1D plotting for ODEs / PDEs
8
+ # ------------------------------
9
+ def plot_1d_solution(model, x, exact=None, title="1D Solution", device=None):
10
+ """
11
+ Plot 1D solution of a model or function.
12
+ model: torch model or callable
13
+ x: tensor of shape [N,1]
14
+ exact: optional exact solution for comparison
15
+ """
16
+ device = device or ('cuda' if torch.cuda.is_available() else 'cpu')
17
+ x = x.to(device)
18
+
19
+ with torch.no_grad():
20
+ y_pred = model(x).cpu().numpy()
21
+
22
+ plt.figure(figsize=(8,5))
23
+ plt.plot(x.cpu().numpy(), y_pred, label="Predicted", lw=2)
24
+ if exact is not None:
25
+ plt.plot(x.cpu().numpy(), exact.cpu().numpy(), "--", label="Exact", lw=2)
26
+ plt.xlabel("x")
27
+ plt.ylabel("y")
28
+ plt.title(title)
29
+ plt.legend()
30
+ plt.grid(True)
31
+ plt.show()
32
+
33
+
34
+ # ------------------------------
35
+ # 2D Surface Plotting
36
+ # ------------------------------
37
+ def plot_2d_surface(model, X, Y, title="2D Surface", device=None):
38
+ """
39
+ Plot 2D surface of a model: inputs X,Y tensors of shape [N,1]
40
+ """
41
+ device = device or ('cuda' if torch.cuda.is_available() else 'cpu')
42
+ X = X.to(device)
43
+ Y = Y.to(device)
44
+ XY = torch.cat([X, Y], dim=1)
45
+
46
+ with torch.no_grad():
47
+ Z = model(XY).cpu().numpy()
48
+
49
+ fig = plt.figure(figsize=(8,6))
50
+ ax = fig.add_subplot(111, projection='3d')
51
+ ax.plot_trisurf(X.cpu().numpy().ravel(), Y.cpu().numpy().ravel(), Z.ravel(), cmap="viridis")
52
+ ax.set_xlabel("X")
53
+ ax.set_ylabel("Y")
54
+ ax.set_zlabel("U")
55
+ ax.set_title(title)
56
+ plt.show()
57
+
58
+
59
+ # ------------------------------
60
+ # 2D / 3D Animation over Time
61
+ # ------------------------------
62
+ def animate_2d(model, x, t, title="2D PDE Evolution", interval=100, device=None):
63
+ """
64
+ Animate a 2D PDE solution over time
65
+ x: tensor [N,1] spatial points
66
+ t: tensor [M,1] time points
67
+ """
68
+ device = device or ('cuda' if torch.cuda.is_available() else 'cpu')
69
+ x = x.to(device)
70
+ t = t.to(device)
71
+
72
+ fig, ax = plt.subplots()
73
+ line, = ax.plot([], [], lw=2)
74
+
75
+ ax.set_xlim(float(x.min()), float(x.max()))
76
+ ax.set_ylim(-1.0, 1.0) # adjust according to problem
77
+ ax.set_xlabel("x")
78
+ ax.set_ylabel("u(x,t)")
79
+ ax.set_title(title)
80
+
81
+ def init():
82
+ line.set_data([], [])
83
+ return line,
84
+
85
+ def update(frame):
86
+ t_i = t[frame].repeat(x.shape[0],1)
87
+ xt = torch.cat([x, t_i], dim=1)
88
+ with torch.no_grad():
89
+ y = model(xt).cpu().numpy()
90
+ line.set_data(x.cpu().numpy(), y.ravel())
91
+ ax.set_title(f"{title} | t={t[frame].item():.2f}")
92
+ return line,
93
+
94
+ ani = animation.FuncAnimation(fig, update, frames=len(t), init_func=init, blit=True, interval=interval)
95
+ plt.show()
96
+ return ani
97
+
98
+
99
+ # ------------------------------
100
+ # Loss Plotting
101
+ # ------------------------------
102
+ def plot_loss(history, title="Training Loss"):
103
+ """
104
+ Plot training loss curves
105
+ history: dict with keys 'total_loss', 'res_loss', 'bc_loss'
106
+ """
107
+ plt.figure(figsize=(8,5))
108
+ plt.plot(history["total_loss"], label="Total Loss", lw=2)
109
+ plt.plot(history["res_loss"], label="Residual Loss", lw=2)
110
+ plt.plot(history["bc_loss"], label="BC Loss", lw=2)
111
+ plt.xlabel("Epochs")
112
+ plt.ylabel("Loss")
113
+ plt.yscale("log")
114
+ plt.title(title)
115
+ plt.legend()
116
+ plt.grid(True)
117
+ plt.show()
118
+
119
+
120
+ # ------------------------------
121
+ # Optional callback for Trainer
122
+ # ------------------------------
123
+ def visualization_callback(model, x, t=None, kind="1d", interval=100, device=None):
124
+ """
125
+ General callback function to visualize PINN during training.
126
+ kind: "1d", "2d_surface", "2d_animation"
127
+ x: spatial points
128
+ t: optional time points
129
+ """
130
+ if kind == "1d":
131
+ plot_1d_solution(model, x, device=device)
132
+ elif kind == "2d_surface":
133
+ assert t is not None, "Provide Y grid for 2D surface"
134
+ plot_2d_surface(model, x, t, device=device)
135
+ elif kind == "2d_animation":
136
+ assert t is not None, "Provide time points for animation"
137
+ animate_2d(model, x, t, interval=interval, device=device)
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: physai
3
+ Version: 0.1.0
4
+ Summary: Physics-Informed Neural Network (PINN) library for solving ODEs/PDEs with visualization and training tools
5
+ Author-email: Mankrit Singh <whataninfinity@gmail.com>
6
+ License: MIT
7
+ Project-URL: GitHub, https://github.com/MS-AGI/PhysAI
8
+ Keywords: PINN,Physics-Informed Neural Network,ODE,PDE,Deep Learning
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: torch>=2.0.0
13
+ Requires-Dist: matplotlib
14
+ Requires-Dist: numpy
15
+ Dynamic: license-file
16
+
17
+ # PhysAI - Physics-Informed Neural Networks (PINNs)
18
+
19
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
20
+ [![License: MIT](https://img.shields.io/badge/License-MIT-indigo.svg)](https://opensource.org/licenses/MIT)
21
+
22
+ ---
23
+
24
+ ## **Overview**
25
+
26
+ **PhysAI** is a Python package for solving **ordinary differential equations (ODEs) and partial differential equations (PDEs)** using **Physics-Informed Neural Networks (PINNs)**. It integrates physics directly into neural network training, allowing the solution of classic physics problems without relying on traditional numerical solvers.
27
+
28
+ PhysAI supports a wide range of physics problems including:
29
+
30
+ * **ODEs:** Logistic growth, Newton’s law of cooling, damped/simple harmonic oscillators, Markov processes.
31
+ * **PDEs:** Heat equation, wave equation, Burgers' equation, KdV equation, convection-diffusion.
32
+ * **Quantum Mechanics:** Schrödinger equation.
33
+ * **Electromagnetism & Quantum Phenomena:** Planck’s law, photoelectric effect.
34
+ * **Fluid Dynamics:** 2D incompressible Navier-Stokes.
35
+ * **Static Problems:** Laplace and Poisson equations (2D/3D).
36
+
37
+ Key features:
38
+
39
+ * Flexible **PDE/ODE residual computation** for various physics laws.
40
+ * **Mixed-precision training** for faster computation on GPUs.
41
+ * Gradient clipping and learning rate schedulers supported.
42
+ * **Visualization and animations** of solutions using matplotlib.
43
+ * Weighted loss functions combining residual and boundary conditions.
44
+
45
+ ---
46
+
47
+ ## **Installation**
48
+
49
+ Clone the repository and install dependencies:
50
+
51
+ ```bash
52
+ git clone https://github.com/yourusername/physai.git
53
+ cd physai
54
+ pip install -r requirements.txt
55
+ ```
56
+
57
+ Python >= 3.10 recommended.
58
+
59
+ ---
60
+
61
+ ## **Repository Structure**
62
+
63
+ ```
64
+ physai/
65
+ ├── __init__.py
66
+ ├── models.py # PINN neural network class
67
+ ├── trainer.py # trainer cls for PINNs
68
+ ├── visualization.py
69
+ ├── pde_residual.py # residuals for ODEs/PDEs
70
+ ├── losses.py
71
+ ├── utils.py
72
+ examples/
73
+ ├── example_schrodinger.py
74
+ ├── example_newton_cooling.py
75
+ ├── example_markov.py
76
+ ├── example_photoelectric.py
77
+ ├── example_planck.py
78
+ README.md
79
+ requirements.txt
80
+ .gitignore
81
+ ```
82
+
83
+ ---
84
+
85
+ ## **Quick Start Example**
86
+
87
+ ```python
88
+ import torch
89
+ from physai.models import PINN
90
+ from physai.pde_residual import pde_residual
91
+ from physai.losses import pinn_loss
92
+ from physai.visualization import plot_1d_solution
93
+
94
+ # Define a 1D logistic growth ODE
95
+ def logistic(x, y):
96
+ r, K = 1.0, 1.0
97
+ return torch.autograd.grad(y, x, grad_outputs=torch.ones_like(y), create_graph=True)[0] - r*y*(1 - y/K)
98
+
99
+ # Create a PINN model
100
+ model = PINN(layers=[1, 20, 20, 1], activation='tanh')
101
+
102
+ # Training points
103
+ x_train = torch.linspace(0, 5, 100).reshape(-1,1)
104
+
105
+ # Train the model
106
+ from physai.trainer import Trainer
107
+ trainer = Trainer(model, collocation_points=x_train, pde_type='logistic')
108
+ history = trainer.train(epochs=500, lr=1e-3)
109
+
110
+ # Plot solution
111
+ plot_1d_solution(model, x_train, title='Logistic Growth')
112
+ ```
113
+
114
+ ---
115
+
116
+ ## **Training a PDE Example: 1D Heat Equation**
117
+
118
+ ```python
119
+ import torch
120
+ from physai.models import PINN
121
+ from physai.trainer import Trainer
122
+ from physai.visualization import animate_2d
123
+
124
+ # PINN model
125
+ model = PINN(layers=[2, 50, 50, 1], activation='tanh')
126
+
127
+ # Collocation points
128
+ x = torch.linspace(0, 1, 50).reshape(-1,1)
129
+ t = torch.linspace(0, 2, 50).reshape(-1,1)
130
+ inputs = torch.cartesian_prod(x.squeeze(), t.squeeze())
131
+ inputs = inputs.float()
132
+
133
+ # Trainer
134
+ trainer = Trainer(model, collocation_points=inputs, pde_type='heat')
135
+ history = trainer.train(epochs=1000, lr=1e-3)
136
+
137
+ # Animate solution
138
+ animate_2d(model, x, t, title='Heat Equation Evolution')
139
+ ```
140
+
141
+ ---
142
+
143
+ ## **Physics Problems Supported**
144
+
145
+ | Type | Equations / Laws |
146
+ | ------------------ | ------------------------------------------------------------------------------------------ |
147
+ | ODE | Logistic Growth, Simple/Damped Harmonic Oscillator, Newton's Law of Cooling, Markov Chains |
148
+ | PDE | Heat Equation, Wave Equation, Burgers', KdV, Convection-Diffusion, Laplace, Poisson |
149
+ | Quantum | Schrödinger Equation, Planck's Law |
150
+ | Quantum/Electromag | Photoelectric Effect |
151
+ | Fluid Dynamics | 2D Incompressible Navier-Stokes |
152
+ | 3D PDEs | Laplace 3D, Poisson 3D |
153
+
154
+ ---
155
+
156
+ ## **Visualization**
157
+
158
+ * **1D plots:** `plot_1d_solution(model, x)`
159
+ * **2D surface plots:** `plot_2d_surface(model, X, Y)`
160
+ * **Animation over time:** `animate_2d(model, x, t)`
161
+ * **Training loss plots:** `plot_loss(trainer.history)`
162
+
163
+ ---
164
+
165
+ ## **Advanced Features**
166
+
167
+ * Mixed precision training for faster GPU computation
168
+ * Gradient clipping
169
+ * Flexible learning rate scheduling
170
+ * Weighted PINN loss for custom PDE/BC importance
171
+ * Supports custom potentials (`V(x,t)`) for Schrödinger equation
172
+
173
+ ---
174
+
175
+ ## **Citation / Usage in Papers**
176
+
177
+ If you use **PhysAI** in your research, please cite it as:
178
+
179
+ ```
180
+ @misc{PhysAI2025,
181
+ author = {Mankrit Singh},
182
+ title = {PhysAI: Physics-Informed Neural Networks in PyTorch},
183
+ year = {2025},
184
+ howpublished = {\url{https://github.com/MS-AGI/PhysAI}
185
+ }
186
+ ```
187
+
188
+ ---
189
+
190
+ ## **License**
191
+
192
+ MIT License. See [LICENSE](LICENSE) file.
193
+
194
+ ---
195
+
196
+ **Solve physics problems with PINNs and visualize them interactively!**
@@ -0,0 +1,13 @@
1
+ physai/__init__.py,sha256=FrvSe4-UPuJLaQQ4NnysW4XcNE2EE62RhS5dW8qxBr0,147
2
+ physai/losses.py,sha256=A6FUgm1eCczu8qQcHvI1z_WT0XGtRBShssYMUKHl8hc,2351
3
+ physai/pde_residual.py,sha256=AT2WRYMSs0d8eigYKmuJkhGKeJDc7H8erwxdumLpvbo,5596
4
+ physai/physics.py,sha256=kE8Y3NPxuy7H731UB7t6DxghB758kpkmhkrNK2gdOis,2456
5
+ physai/pinn.py,sha256=PpJydZjrnqkXX75HJl-ItYqYQhrRi9XR2w2O8ERSYXc,2863
6
+ physai/trainer.py,sha256=v7nzgevkVTY75Efs8AGWvymWtbb7KaA6FDeAiU1ZpEs,2681
7
+ physai/utils.py,sha256=gbYIH3nofds3u8iRauooTWeHVHi-2URddT7BkAaT0DE,84
8
+ physai/visualization.py,sha256=ccrPTooWPHqKwpVgIwmsphJGkmVvmM4fUmdE5gkXZ7k,4375
9
+ physai-0.1.0.dist-info/licenses/LICENSE,sha256=QEi1eLA1tImN_mOcj5idr2ldBEzXBLSZJ5lKKmmMD2s,1084
10
+ physai-0.1.0.dist-info/METADATA,sha256=Lc0HlkT2mKqQ-L4GqwdZ2G4J-U-5QnNk2cU9ItMjzY4,6347
11
+ physai-0.1.0.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
12
+ physai-0.1.0.dist-info/top_level.txt,sha256=XasGUSPQXLDNo4l-dunwoQqXio1B878qCFcNao4GFqY,7
13
+ physai-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.4.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 MS-AGI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ physai