| import os |
| from datetime import datetime |
|
|
| import jax |
| import jax.numpy as jnp |
| import matplotlib |
| matplotlib.use("Agg") |
| import matplotlib.pyplot as plt |
| import numpy as np |
| import yaml |
| from flax import serialization |
|
|
| from checks import run_checks |
| from model import OrbitMLP |
| from physics_engine import energy as compute_energy, rk4_step |
| from train import ( |
| create_train_state, |
| generate_trajectories, |
| make_predict_trajectory, |
| make_train_step, |
| ) |
|
|
|
|
| def load_config(config_path): |
| with open(config_path, "r") as f: |
| config = yaml.safe_load(f) |
| return config |
|
|
|
|
| def main(): |
| now = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
|
| run_checks() |
|
|
| config = load_config("config.yaml") |
|
|
| seed = config["seed"] |
| num_trajs = config["data"]["num_trajs"] |
| num_steps = config["data"]["num_steps"] |
| dt = config["data"]["dt"] |
| gm = config["physics"]["gm"] |
| lambda_energy = config["physics"].get("lambda_energy", 0.1) |
| lambda_angular = config["physics"].get("lambda_angular", 0.1) |
| num_epochs = config["training"]["num_epochs"] |
| batch_size = config["training"]["batch_size"] |
| learning_rate = config["training"]["learning_rate"] |
| hidden_dim = config["model"]["hidden_dim"] |
| num_blocks = config["model"]["num_blocks"] |
|
|
| rng = jax.random.PRNGKey(seed) |
|
|
| rng_data, rng_init, rng_shuffle_st = jax.random.split(rng, 3) |
| states, targets = generate_trajectories( |
| rng_data, num_trajs=num_trajs, num_steps=num_steps, dt=dt, gm=gm |
| ) |
|
|
| B, T, D = states.shape |
| flat_inputs = states[:, :-1, :].reshape(-1, 4) |
| flat_targets = targets.reshape(-1, 4) |
| num_samples = flat_inputs.shape[0] |
|
|
| model = OrbitMLP(hidden_dim=hidden_dim, num_blocks=num_blocks) |
| train_state = create_train_state(rng_init, model, learning_rate=learning_rate) |
|
|
| train_step = make_train_step(model, lambda_energy=lambda_energy, gm=gm, lambda_angular=lambda_angular) |
|
|
| print(f"Training on {num_samples} samples for {num_epochs} epochs...") |
| print(f"{'Epoch':>8s} {'Loss':>10s} {'MSE':>10s} {'Energy':>10s}") |
|
|
| rng_shuffle = rng_shuffle_st |
| for epoch in range(num_epochs): |
| rng_shuffle, rng_epoch = jax.random.split(rng_shuffle) |
| perm = jax.random.permutation(rng_epoch, num_samples) |
| epoch_loss = 0.0 |
| epoch_mse = 0.0 |
| epoch_energy_loss = 0.0 |
| num_batches = 0 |
|
|
| for start in range(0, num_samples, batch_size): |
| indices = perm[start : start + batch_size] |
| batch_in = flat_inputs[indices] |
| batch_target = flat_targets[indices] |
|
|
| train_state, metrics = train_step( |
| train_state, batch_in, batch_target |
| ) |
| epoch_loss += float(metrics["loss"]) |
| epoch_mse += float(metrics["mse"]) |
| epoch_energy_loss += float(metrics["energy_loss"]) |
| num_batches += 1 |
|
|
| if (epoch + 1) % 30 == 0: |
| print( |
| f"{epoch + 1:>8d} " |
| f"{epoch_loss / num_batches:>10.6f} " |
| f"{epoch_mse / num_batches:>10.6f} " |
| f"{epoch_energy_loss / num_batches:>10.6f}" |
| ) |
|
|
| bytes_params = serialization.to_bytes(train_state.params) |
| model_path = os.path.join("models", f"orbitmlp_{now}.flax") |
| with open(model_path, "wb") as f: |
| f.write(bytes_params) |
| print(f"Model saved to {model_path}") |
|
|
| rng_test = jax.random.PRNGKey(999) |
| test_states, _ = generate_trajectories( |
| rng_test, num_trajs=1, num_steps=num_steps, dt=dt, gm=gm |
| ) |
| init_state_arr = test_states[0, 0] |
|
|
| rk4_traj = np.zeros((num_steps + 1, 4), dtype=np.float32) |
| rk4_traj[0] = np.array(init_state_arr) |
| s = init_state_arr |
| for i in range(num_steps): |
| s, _ = rk4_step(s, dt, gm) |
| rk4_traj[i + 1] = np.array(s) |
|
|
| predict_trajectory = make_predict_trajectory(model) |
| nn_traj = np.array( |
| predict_trajectory(train_state.params, init_state_arr, num_steps) |
| ) |
|
|
| rk4_energies = np.array( |
| [compute_energy(rk4_traj[i], gm) for i in range(num_steps + 1)] |
| ) |
| nn_energies = np.array( |
| [compute_energy(nn_traj[i], gm) for i in range(num_steps + 1)] |
| ) |
|
|
| fig, axes = plt.subplots(1, 3, figsize=(18, 5)) |
|
|
| ax = axes[0] |
| ax.plot(rk4_traj[:, 0], rk4_traj[:, 1], "b-", label="RK4 (truth)", alpha=0.8) |
| ax.plot(nn_traj[:, 0], nn_traj[:, 1], "r--", label="OrbitMLP", alpha=0.8) |
| ax.scatter(0, 0, c="k", marker="o", s=80, label="Central mass") |
| ax.set_xlabel("x") |
| ax.set_ylabel("y") |
| ax.set_title("Orbit Comparison") |
| ax.legend() |
| ax.set_aspect("equal") |
|
|
| ax = axes[1] |
| ax.plot(range(num_steps + 1), rk4_traj[:, 0], "b-", label="RK4 x", alpha=0.8) |
| ax.plot(range(num_steps + 1), nn_traj[:, 0], "r--", label="NN x", alpha=0.8) |
| ax.plot(range(num_steps + 1), rk4_traj[:, 1], "c-", label="RK4 y", alpha=0.8) |
| ax.plot(range(num_steps + 1), nn_traj[:, 1], "m--", label="NN y", alpha=0.8) |
| ax.set_xlabel("Step") |
| ax.set_ylabel("Position") |
| ax.set_title("Position vs Time") |
| ax.legend() |
|
|
| ax = axes[2] |
| ax.plot(range(num_steps + 1), rk4_energies, "b-", label="RK4 energy", alpha=0.8) |
| ax.plot(range(num_steps + 1), nn_energies, "r--", label="NN energy", alpha=0.8) |
| initial_e = rk4_energies[0] |
| ax.axhline(y=initial_e, color="k", linestyle=":", alpha=0.5, label=f"E0 = {initial_e:.4f}") |
| ax.set_xlabel("Step") |
| ax.set_ylabel("Total Energy") |
| ax.set_title("Energy Conservation") |
| ax.legend() |
|
|
| plt.tight_layout() |
| plot_filename = f"orbit_{now}.png" |
| plot_path = os.path.join("results", plot_filename) |
| plt.savefig(plot_path, dpi=150) |
| plt.close() |
|
|
| avg_loss = epoch_loss / num_batches |
| avg_mse = epoch_mse / num_batches |
| avg_e = epoch_energy_loss / num_batches |
| print(f"Plot saved to {plot_path}") |
| print(f"Final loss: {avg_loss:.6f} | MSE: {avg_mse:.6f} | Energy: {avg_e:.6f}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |