From 7d3d2a157cf3ee902fdf12b70d413c78387e7d9a Mon Sep 17 00:00:00 2001 From: Abhishek Nandy Date: Tue, 29 Apr 2025 18:28:46 +0530 Subject: [PATCH 01/12] Add Differentiable Physics: Mass-Spring System example --- differentiable_physics/mass_spring.py | 135 ++++++++++++++++++++++++ differentiable_physics/readme.md | 33 ++++++ differentiable_physics/requirements.txt | 1 + 3 files changed, 169 insertions(+) create mode 100644 differentiable_physics/mass_spring.py create mode 100644 differentiable_physics/readme.md create mode 100644 differentiable_physics/requirements.txt diff --git a/differentiable_physics/mass_spring.py b/differentiable_physics/mass_spring.py new file mode 100644 index 0000000000..9986f6c1d5 --- /dev/null +++ b/differentiable_physics/mass_spring.py @@ -0,0 +1,135 @@ +import torch +import torch.nn as nn +import torch.optim as optim +import argparse + + +class MassSpringSystem(nn.Module): + def __init__(self, num_particles, springs, mass=1.0, dt=0.01, gravity=9.81, device="cpu"): + super().__init__() + self.device = device + self.mass = mass + self.springs = springs + self.dt = dt + self.gravity = gravity + + # 🛑 Particle 0 fixed at origin + self.initial_position_0 = torch.tensor([0.0, 0.0], device=device) + + # 🛑 Only remaining particles are trainable + self.initial_positions_rest = nn.Parameter(torch.randn(num_particles - 1, 2, device=device)) + + # Velocities + self.velocities = torch.zeros(num_particles, 2, device=device) + + def forward(self, steps): + positions = torch.cat([self.initial_position_0.unsqueeze(0), self.initial_positions_rest], dim=0) + velocities = self.velocities + + for _ in range(steps): + forces = torch.zeros_like(positions) + + # Compute spring forces + for (i, j, rest_length, stiffness) in self.springs: + xi, xj = positions[i], positions[j] + dir_vec = xj - xi + dist = dir_vec.norm() + force = stiffness * (dist - rest_length) * dir_vec / (dist + 1e-6) + forces[i] += force + forces[j] -= force + + # Apply gravity + forces[:, 1] -= self.gravity * self.mass + + # Integrate (semi-implicit Euler) + acceleration = forces / self.mass + velocities = velocities + acceleration * self.dt + positions = positions + velocities * self.dt + + # Fix particle 0 after integration + positions[0] = self.initial_position_0 + velocities[0] = torch.tensor([0.0, 0.0], device=positions.device) + + return positions + + + +def train(args): + """ + Train the MassSpringSystem to match a target configuration. + """ + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + system = MassSpringSystem( + num_particles=args.num_particles, + springs=[(0, 1, 1.0, args.stiffness)], + mass=args.mass, + dt=args.dt, + gravity=args.gravity, + device=device, + ) + + optimizer = optim.Adam(system.parameters(), lr=args.lr) + target_positions = torch.tensor( + [[0.0, 0.0], [1.0, 0.0]], device=device + ) # Target: particle 0 at (0,0), particle 1 at (1,0) + + for epoch in range(args.epochs): + optimizer.zero_grad() + final_positions = system(args.steps) # <--- final_positions comes from forward() + loss = (final_positions - target_positions).pow(2).mean() + loss.backward() + optimizer.step() + + if (epoch + 1) % args.log_interval == 0: + print(f"Epoch {epoch+1}/{args.epochs}, Loss: {loss.item():.6f}") + + print("\nTraining completed.") + print(f"Final positions:\n{final_positions.detach().cpu().numpy()}") # <--- print final_positions + print(f"Target positions:\n{target_positions.cpu().numpy()}") + + +def evaluate(args): + """ + Evaluate the trained MassSpringSystem without optimization. + """ + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + system = MassSpringSystem( + num_particles=args.num_particles, + springs=[(0, 1, 1.0, args.stiffness)], + mass=args.mass, + dt=args.dt, + gravity=args.gravity, # <-- Gravity passed here too + device=device, + ) + + with torch.no_grad(): + final_positions = system(args.steps) + print(f"Final positions after {args.steps} steps:\n{final_positions.cpu().numpy()}") + + +def parse_args(): + parser = argparse.ArgumentParser(description="Differentiable Physics: Mass-Spring System") + parser.add_argument("--epochs", type=int, default=1000, help="Number of training epochs") + parser.add_argument("--steps", type=int, default=50, help="Number of simulation steps per forward pass") + parser.add_argument("--lr", type=float, default=0.01, help="Learning rate") + parser.add_argument("--dt", type=float, default=0.01, help="Time step for integration") + parser.add_argument("--mass", type=float, default=1.0, help="Mass of each particle") + parser.add_argument("--stiffness", type=float, default=10.0, help="Spring stiffness constant") + parser.add_argument("--num_particles", type=int, default=2, help="Number of particles in the system") + parser.add_argument("--mode", choices=["train", "eval"], default="train", help="Mode: train or eval") + parser.add_argument("--log_interval", type=int, default=100, help="Print loss every n epochs") + parser.add_argument("--gravity", type=float, default=9.81, help="Gravity strength") + return parser.parse_args() + + +def main(): + args = parse_args() + + if args.mode == "train": + train(args) + elif args.mode == "eval": + evaluate(args) + + +if __name__ == "__main__": + main() diff --git a/differentiable_physics/readme.md b/differentiable_physics/readme.md new file mode 100644 index 0000000000..204c3c5a1e --- /dev/null +++ b/differentiable_physics/readme.md @@ -0,0 +1,33 @@ +# Differentiable Physics: Mass-Spring System + +This example demonstrates a simple differentiable mass-spring system using PyTorch. + +Particles are connected by springs and evolve under the forces exerted by the springs and gravity. +The system is fully differentiable, allowing the optimization of particle positions to match a target configuration using gradient-based learning. + +--- + +## Files + +- `mass_spring.py` — Implements the mass-spring simulation, training loop, and evaluation. +- `README.md` — Usage instructions and description. + +--- + +## Requirements + +- Python 3.8+ +- PyTorch + +No external dependencies are required apart from PyTorch. + +--- + +## Usage + +First, ensure PyTorch is installed. + +### Train the system + +```bash +python mass_spring.py --mode train diff --git a/differentiable_physics/requirements.txt b/differentiable_physics/requirements.txt new file mode 100644 index 0000000000..12c6d5d5ea --- /dev/null +++ b/differentiable_physics/requirements.txt @@ -0,0 +1 @@ +torch From 39a0c8eb7e4c8b338290f2d9b3e5afa9231b1274 Mon Sep 17 00:00:00 2001 From: Abhishek Nandy Date: Wed, 14 May 2025 03:29:08 +0530 Subject: [PATCH 02/12] Add differentiable_physics to run_all() in test script --- run_python_examples.sh | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/run_python_examples.sh b/run_python_examples.sh index b15f9397d7..8fd6cac20d 100755 --- a/run_python_examples.sh +++ b/run_python_examples.sh @@ -1,7 +1,7 @@ #!/bin/bash # # This script runs through the code in each of the python examples. -# The purpose is just as an integration test, not to actually train models in any meaningful way. +# The purpose is just as an integration test, not to actually train models in any meaningful way. # For that reason, most of these set epochs = 1 and --dry-run. # # Optionally specify a comma separated list of examples to run. Can be run as: @@ -15,26 +15,12 @@ # # To test examples on hardware accelerator (CUDA, MPS, XPU, etc.), run as: # USE_ACCEL=True ./run_python_examples.sh -# NOTE: USE_ACCEL relies on torch.accelerator API and not all examples are converted -# to use it at the moment. Thus, expect failures using this flag on non-CUDA accelerators -# and consider to run examples one by one. # -# Script requires uv to be installed. When executed, script will install prerequisites from -# `requirements.txt` for each example. If ran within activated virtual environment (uv venv, -# python -m venv, conda) this might reinstall some of the packages. To change pip installation -# index or to pass additional pip install options, run as: -# PIP_INSTALL_ARGS="--pre -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html" \ -# ./run_python_examples.sh -# -# To force script to create virtual environment for each example, run as: -# VIRTUAL_ENV=".venv" ./run_python_examples.sh -# Script will remove environments it creates in a teardown step after execution of each example. +# Script requires uv to be installed. It installs requirements from requirements.txt per example. BASE_DIR="$(pwd)/$(dirname $0)" source $BASE_DIR/utils.sh -# TODO: Leave only USE_ACCEL and drop USE_CUDA once all examples will be converted -# to torch.accelerator API. For now, just add USE_ACCEL as an alias for USE_CUDA. if [ -n "$USE_ACCEL" ]; then USE_CUDA=$USE_ACCEL fi @@ -92,10 +78,11 @@ function language_translation() { function mnist() { uv run main.py --epochs 1 --dry-run || error "mnist example failed" } + function mnist_forward_forward() { uv run main.py --epochs 1 --no_mps --no_cuda || error "mnist forward forward failed" - } + function mnist_hogwild() { uv run main.py --epochs 1 --dry-run $CUDA_FLAG || error "mnist hogwild failed" } @@ -119,13 +106,12 @@ function reinforcement_learning() { function snli() { echo "installing 'en' model if not installed" - uv run -m spacy download en || { error "couldn't download 'en' model needed for snli"; return; } + uv run -m spacy download en || { error "couldn't download 'en' model needed for snli"; return; } echo "training..." uv run train.py --epochs 1 --dev_every 1 --no-bidirectional --dry-run || error "couldn't train snli" } function fx() { - # uv run custom_tracer.py || error "fx custom tracer has failed" UnboundLocalError: local variable 'tabulate' referenced before assignment uv run invert.py || error "fx invert has failed" uv run module_tracer.py || error "fx module tracer has failed" uv run primitive_library.py || error "fx primitive library has failed" @@ -140,7 +126,7 @@ function super_resolution() { } function time_sequence_prediction() { - uv run generate_sine_wave.py || { error "generate sine wave failed"; return; } + uv run generate_sine_wave.py || { error "generate sine wave failed"; return; } uv run train.py --steps 2 || error "time sequence prediction training failed" } @@ -164,6 +150,12 @@ function gat() { uv run main.py --epochs 1 --dry-run || error "graph attention network failed" } +function differentiable_physics() { + pushd differentiable_physics + python -m uv run mass_spring.py --mode train --epochs 5 --steps 3 || error "differentiable_physics example failed" + popd +} + eval "base_$(declare -f stop)" function stop() { @@ -196,12 +188,9 @@ function stop() { } function run_all() { - # cpp moved to `run_cpp_examples.sh``` run dcgan - # distributed moved to `run_distributed_examples.sh` run fast_neural_style run imagenet - # language_translation run mnist run mnist_forward_forward run mnist_hogwild @@ -212,14 +201,13 @@ function run_all() { run super_resolution run time_sequence_prediction run vae - # vision_transformer - example broken see https://github.com/pytorch/examples/issues/1184 and https://github.com/pytorch/examples/pull/1258 for more details run word_language_model run fx run gcn run gat + run differentiable_physics # ✅ Your example now runs in CI! } -# by default, run all examples if [ "" == "$EXAMPLES" ]; then run_all else @@ -236,7 +224,5 @@ if [ "" == "$ERRORS" ]; then else echo "Some python examples failed:" printf "$ERRORS\n" - #Exit with error (0-255) in case of failure in one of the tests. exit 1 - fi From 77c8abcd95283c6f3ba3a8d15fbe5a6b4e9b88d7 Mon Sep 17 00:00:00 2001 From: Abhishek Nandy Date: Wed, 14 May 2025 04:02:01 +0530 Subject: [PATCH 03/12] Add visualization and update training code in mass_spring.py --- differentiable_physics/mass_spring.py | 43 ++++++++++++++------- differentiable_physics/mass_spring_viz.png | Bin 0 -> 19019 bytes differentiable_physics/readme.md | 9 +++++ 3 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 differentiable_physics/mass_spring_viz.png diff --git a/differentiable_physics/mass_spring.py b/differentiable_physics/mass_spring.py index 9986f6c1d5..fbc49bdf54 100644 --- a/differentiable_physics/mass_spring.py +++ b/differentiable_physics/mass_spring.py @@ -2,6 +2,8 @@ import torch.nn as nn import torch.optim as optim import argparse +import matplotlib.pyplot as plt +import os class MassSpringSystem(nn.Module): @@ -13,10 +15,10 @@ def __init__(self, num_particles, springs, mass=1.0, dt=0.01, gravity=9.81, devi self.dt = dt self.gravity = gravity - # 🛑 Particle 0 fixed at origin + # Particle 0 is fixed at the origin self.initial_position_0 = torch.tensor([0.0, 0.0], device=device) - # 🛑 Only remaining particles are trainable + # Remaining particles are trainable self.initial_positions_rest = nn.Parameter(torch.randn(num_particles - 1, 2, device=device)) # Velocities @@ -41,23 +43,35 @@ def forward(self, steps): # Apply gravity forces[:, 1] -= self.gravity * self.mass - # Integrate (semi-implicit Euler) + # Semi-implicit Euler integration acceleration = forces / self.mass velocities = velocities + acceleration * self.dt positions = positions + velocities * self.dt - # Fix particle 0 after integration + # Fix particle 0 at origin positions[0] = self.initial_position_0 velocities[0] = torch.tensor([0.0, 0.0], device=positions.device) return positions +def visualize_positions(initial, final, target, save_path="mass_spring_viz.png"): + plt.figure(figsize=(6, 4)) + plt.scatter(initial[:, 0], initial[:, 1], c='blue', label='Initial', marker='x') + plt.scatter(final[:, 0], final[:, 1], c='green', label='Final', marker='o') + plt.scatter(target[:, 0], target[:, 1], c='red', label='Target', marker='*') + plt.title("Mass-Spring System Positions") + plt.xlabel("X") + plt.ylabel("Y") + plt.legend() + plt.grid(True) + plt.tight_layout() + plt.savefig(save_path) + print(f"Saved visualization to {os.path.abspath(save_path)}") + plt.close() + def train(args): - """ - Train the MassSpringSystem to match a target configuration. - """ device = torch.device("cuda" if torch.cuda.is_available() else "cpu") system = MassSpringSystem( num_particles=args.num_particles, @@ -71,11 +85,11 @@ def train(args): optimizer = optim.Adam(system.parameters(), lr=args.lr) target_positions = torch.tensor( [[0.0, 0.0], [1.0, 0.0]], device=device - ) # Target: particle 0 at (0,0), particle 1 at (1,0) + ) for epoch in range(args.epochs): optimizer.zero_grad() - final_positions = system(args.steps) # <--- final_positions comes from forward() + final_positions = system(args.steps) loss = (final_positions - target_positions).pow(2).mean() loss.backward() optimizer.step() @@ -83,22 +97,23 @@ def train(args): if (epoch + 1) % args.log_interval == 0: print(f"Epoch {epoch+1}/{args.epochs}, Loss: {loss.item():.6f}") + # Visualization + initial_positions = torch.cat([system.initial_position_0.unsqueeze(0), system.initial_positions_rest.detach()], dim=0).cpu().numpy() + visualize_positions(initial_positions, final_positions.detach().cpu().numpy(), target_positions.cpu().numpy()) + print("\nTraining completed.") - print(f"Final positions:\n{final_positions.detach().cpu().numpy()}") # <--- print final_positions + print(f"Final positions:\n{final_positions.detach().cpu().numpy()}") print(f"Target positions:\n{target_positions.cpu().numpy()}") def evaluate(args): - """ - Evaluate the trained MassSpringSystem without optimization. - """ device = torch.device("cuda" if torch.cuda.is_available() else "cpu") system = MassSpringSystem( num_particles=args.num_particles, springs=[(0, 1, 1.0, args.stiffness)], mass=args.mass, dt=args.dt, - gravity=args.gravity, # <-- Gravity passed here too + gravity=args.gravity, device=device, ) diff --git a/differentiable_physics/mass_spring_viz.png b/differentiable_physics/mass_spring_viz.png new file mode 100644 index 0000000000000000000000000000000000000000..88da86d471bdef411ee4012cce0f81031786f40d GIT binary patch literal 19019 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 MO2-wGi_@% literal 0 HcmV?d00001 diff --git a/differentiable_physics/readme.md b/differentiable_physics/readme.md index 204c3c5a1e..bd9d8e0ab3 100644 --- a/differentiable_physics/readme.md +++ b/differentiable_physics/readme.md @@ -31,3 +31,12 @@ First, ensure PyTorch is installed. ```bash python mass_spring.py --mode train + +## Visualization + +

+ +

+ +This plot shows the learned alignment of a 2-particle spring system with its target configuration. + From 84072d581674a1623d7cf4dbe6cd4865d03eccb7 Mon Sep 17 00:00:00 2001 From: Abhishek Nandy Date: Sun, 18 May 2025 19:01:00 +0530 Subject: [PATCH 04/12] Finalize differentiable_physics with visualization and CI integration --- differentiable_physics/mass_spring_viz.png | Bin 19019 -> 18547 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/differentiable_physics/mass_spring_viz.png b/differentiable_physics/mass_spring_viz.png index 88da86d471bdef411ee4012cce0f81031786f40d..afd45e5d76d116ab8385d971a7c5975abb50ea00 100644 GIT binary patch literal 18547 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 literal 19019 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 MO2-wGi_@% From 8a7cf5e4fcc06582a968b7ba8348e99c9faa5b27 Mon Sep 17 00:00:00 2001 From: Abhishek Nandy Date: Sun, 18 May 2025 19:16:13 +0530 Subject: [PATCH 05/12] Finalize differentiable_physics with visualization and CI integration --- differentiable_physics/mass_spring.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/differentiable_physics/mass_spring.py b/differentiable_physics/mass_spring.py index fbc49bdf54..e7d83d487f 100644 --- a/differentiable_physics/mass_spring.py +++ b/differentiable_physics/mass_spring.py @@ -72,7 +72,9 @@ def visualize_positions(initial, final, target, save_path="mass_spring_viz.png") def train(args): - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + #device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + device = torch.accelerator.current_accelerator() if torch.accelerator.is_available() else torch.device("cpu") + print(f"Using device: {device}") system = MassSpringSystem( num_particles=args.num_particles, springs=[(0, 1, 1.0, args.stiffness)], @@ -107,7 +109,9 @@ def train(args): def evaluate(args): - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + #device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + device = torch.accelerator.current_accelerator() if torch.accelerator.is_available() else torch.device("cpu") + print(f"Using device: {device}") system = MassSpringSystem( num_particles=args.num_particles, springs=[(0, 1, 1.0, args.stiffness)], From f20fa201749a528fcdeba61c5e909b44e34100a6 Mon Sep 17 00:00:00 2001 From: Abhishek Nandy Date: Mon, 19 May 2025 02:39:01 +0530 Subject: [PATCH 06/12] Finalize differentiable_physics with the updates --- differentiable_physics/readme.md | 13 +++++++------ differentiable_physics/requirements.txt | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/differentiable_physics/readme.md b/differentiable_physics/readme.md index bd9d8e0ab3..6c0f60512b 100644 --- a/differentiable_physics/readme.md +++ b/differentiable_physics/readme.md @@ -12,12 +12,14 @@ The system is fully differentiable, allowing the optimization of particle positi - `mass_spring.py` — Implements the mass-spring simulation, training loop, and evaluation. - `README.md` — Usage instructions and description. + --- ## Requirements - Python 3.8+ - PyTorch +- pip install -r requirements.txt No external dependencies are required apart from PyTorch. @@ -27,16 +29,15 @@ No external dependencies are required apart from PyTorch. First, ensure PyTorch is installed. -### Train the system +#### Train the system ```bash python mass_spring.py --mode train -## Visualization -

- -

+##### Visualization + +After training, the system's final positions are compared to the target positions. The plot below illustrates this comparison: -This plot shows the learned alignment of a 2-particle spring system with its target configuration. +![Mass-Spring System Visualization](mass_spring_viz.png) diff --git a/differentiable_physics/requirements.txt b/differentiable_physics/requirements.txt index 12c6d5d5ea..52f936ea1d 100644 --- a/differentiable_physics/requirements.txt +++ b/differentiable_physics/requirements.txt @@ -1 +1,2 @@ torch +matplotlib From e14251f8348d8ebaa185ac1be836ee7c172a6a30 Mon Sep 17 00:00:00 2001 From: Abhishek Nandy Date: Mon, 19 May 2025 02:47:42 +0530 Subject: [PATCH 07/12] Update requirements.txt for differentiable_physics --- differentiable_physics/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/differentiable_physics/requirements.txt b/differentiable_physics/requirements.txt index 52f936ea1d..0fde5c79c6 100644 --- a/differentiable_physics/requirements.txt +++ b/differentiable_physics/requirements.txt @@ -1,2 +1,3 @@ torch matplotlib + From 96e04f1d9784a8ab4ac2133c879ddaa57a539202 Mon Sep 17 00:00:00 2001 From: Abhishek Nandy Date: Tue, 20 May 2025 03:35:07 +0530 Subject: [PATCH 08/12] Update run_python_examples.sh to test differentiable_physics in CI --- run_python_examples.sh | 59 +++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/run_python_examples.sh b/run_python_examples.sh index bf2e75035c..7540b2fc1b 100755 --- a/run_python_examples.sh +++ b/run_python_examples.sh @@ -1,27 +1,40 @@ #!/bin/bash # # This script runs through the code in each of the python examples. -# The purpose is just as an integration test, not to actually train models in any meaningful way. +# The purpose is just as an integration test, not to actually train models in any meaningful way. # For that reason, most of these set epochs = 1 and --dry-run. # -# To run all examples: +# Optionally specify a comma separated list of examples to run. Can be run as: +# * To run all examples: # ./run_python_examples.sh -# -# To run specific examples: +# * To run few specific examples: # ./run_python_examples.sh "dcgan,fast_neural_style" # -# USE_CUDA=True ./run_python_examples.sh → for CUDA -# USE_ACCEL=True ./run_python_examples.sh → for any accelerator (CUDA/MPS/XPU) +# To test examples on CUDA accelerator, run as: +# USE_CUDA=True ./run_python_examples.sh +# +# To test examples on hardware accelerator (CUDA, MPS, XPU, etc.), run as: +# USE_ACCEL=True ./run_python_examples.sh +# NOTE: USE_ACCEL relies on torch.accelerator API and not all examples are converted +# to use it at the moment. Thus, expect failures using this flag on non-CUDA accelerators +# and consider to run examples one by one. # -# To use a custom pip install source: -# PIP_INSTALL_ARGS="--pre -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html" ./run_python_examples.sh +# Script requires uv to be installed. When executed, script will install prerequisites from +# `requirements.txt` for each example. If ran within activated virtual environment (uv venv, +# python -m venv, conda) this might reinstall some of the packages. To change pip installation +# index or to pass additional pip install options, run as: +# PIP_INSTALL_ARGS="--pre -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html" \ +# ./run_python_examples.sh # -# To force venv per example: +# To force script to create virtual environment for each example, run as: # VIRTUAL_ENV=".venv" ./run_python_examples.sh +# Script will remove environments it creates in a teardown step after execution of each example. BASE_DIR="$(pwd)/$(dirname $0)" source $BASE_DIR/utils.sh +# TODO: Leave only USE_ACCEL and drop USE_CUDA once all examples will be converted +# to torch.accelerator API. For now, just add USE_ACCEL as an alias for USE_CUDA. if [ -n "$USE_ACCEL" ]; then USE_CUDA=$USE_ACCEL fi @@ -40,7 +53,7 @@ case $USE_CUDA in ACCEL_FLAG="" ;; "") - exit 1 + exit 1; ;; esac @@ -54,6 +67,7 @@ function fast_neural_style() { uv run download_saved_models.py fi test -d "saved_models" || { error "saved models not found"; return; } + echo "running fast neural style model" uv run neural_style/neural_style.py eval --content-image images/content-images/amber.jpg --model saved_models/candy.pth --output-image images/output-images/amber-candy.jpg $ACCEL_FLAG || error "neural_style.py failed" } @@ -78,11 +92,10 @@ function language_translation() { function mnist() { uv run main.py --epochs 1 --dry-run || error "mnist example failed" } - function mnist_forward_forward() { uv run main.py --epochs 1 --no_accel || error "mnist forward forward failed" -} +} function mnist_hogwild() { uv run main.py --epochs 1 --dry-run $CUDA_FLAG || error "mnist hogwild failed" } @@ -106,12 +119,13 @@ function reinforcement_learning() { function snli() { echo "installing 'en' model if not installed" - uv run -m spacy download en || { error "couldn't download 'en' model needed for snli"; return; } + uv run -m spacy download en || { error "couldn't download 'en' model needed for snli"; return; } echo "training..." uv run train.py --epochs 1 --dev_every 1 --no-bidirectional --dry-run || error "couldn't train snli" } function fx() { + # uv run custom_tracer.py || error "fx custom tracer has failed" UnboundLocalError: local variable 'tabulate' referenced before assignment uv run invert.py || error "fx invert has failed" uv run module_tracer.py || error "fx module tracer has failed" uv run primitive_library.py || error "fx primitive library has failed" @@ -126,7 +140,7 @@ function super_resolution() { } function time_sequence_prediction() { - uv run generate_sine_wave.py || { error "generate sine wave failed"; return; } + uv run generate_sine_wave.py || { error "generate sine wave failed"; return; } uv run train.py --steps 2 || error "time sequence prediction training failed" } @@ -140,6 +154,9 @@ function vision_transformer() { function word_language_model() { uv run main.py --epochs 1 --dry-run $CUDA_FLAG --mps || error "word_language_model failed" + for model in "RNN_TANH" "RNN_RELU" "LSTM" "GRU" "Transformer"; do + uv run main.py --model $model --epochs 1 --dry-run $CUDA_FLAG --mps || error "word_language_model failed" + done } function gcn() { @@ -151,11 +168,10 @@ function gat() { } function differentiable_physics() { - pushd differentiable_physics - python -m uv run mass_spring.py --mode train --epochs 5 --steps 3 || error "differentiable_physics example failed" - popd + uv run mass_spring.py --mode train --epochs 5 --steps 3 || error "differentiable_physics example failed" } + eval "base_$(declare -f stop)" function stop() { @@ -188,9 +204,12 @@ function stop() { } function run_all() { + # cpp moved to `run_cpp_examples.sh``` run dcgan + # distributed moved to `run_distributed_examples.sh` run fast_neural_style run imagenet + # language_translation run mnist run mnist_forward_forward run mnist_hogwild @@ -201,6 +220,7 @@ function run_all() { run super_resolution run time_sequence_prediction run vae + # vision_transformer - example broken see https://github.com/pytorch/examples/issues/1184 and https://github.com/pytorch/examples/pull/1258 for more details run word_language_model run fx run gcn @@ -208,6 +228,7 @@ function run_all() { run differentiable_physics } +# by default, run all examples if [ "" == "$EXAMPLES" ]; then run_all else @@ -224,5 +245,7 @@ if [ "" == "$ERRORS" ]; then else echo "Some python examples failed:" printf "$ERRORS\n" + #Exit with error (0-255) in case of failure in one of the tests. exit 1 -fi + +fi \ No newline at end of file From 35b0afa61068d9bbb9f1691b40ba3456b296c8fe Mon Sep 17 00:00:00 2001 From: Abhishek Nandy Date: Wed, 21 May 2025 01:35:47 +0530 Subject: [PATCH 09/12] Add mass spring example and update requirements --- differentiable_physics/mass_spring_viz.png | Bin 18547 -> 17726 bytes differentiable_physics/requirements.txt | 2 +- run_python_examples.sh | 3 --- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/differentiable_physics/mass_spring_viz.png b/differentiable_physics/mass_spring_viz.png index afd45e5d76d116ab8385d971a7c5975abb50ea00..0b3a08509c1e6ca356a907e8c7f931a3f146873d 100644 GIT binary patch literal 17726 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 tMNO12KYpZ^cnfn$)_&e_#bLLstW)B literal 18547 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 diff --git a/differentiable_physics/requirements.txt b/differentiable_physics/requirements.txt index 0fde5c79c6..f88df426f8 100644 --- a/differentiable_physics/requirements.txt +++ b/differentiable_physics/requirements.txt @@ -1,3 +1,3 @@ -torch +torch>=2.6 matplotlib diff --git a/run_python_examples.sh b/run_python_examples.sh index 7540b2fc1b..29b948588a 100755 --- a/run_python_examples.sh +++ b/run_python_examples.sh @@ -154,9 +154,6 @@ function vision_transformer() { function word_language_model() { uv run main.py --epochs 1 --dry-run $CUDA_FLAG --mps || error "word_language_model failed" - for model in "RNN_TANH" "RNN_RELU" "LSTM" "GRU" "Transformer"; do - uv run main.py --model $model --epochs 1 --dry-run $CUDA_FLAG --mps || error "word_language_model failed" - done } function gcn() { From f1a806e435756348b577aeace15dae5254fd914c Mon Sep 17 00:00:00 2001 From: Abhishek Nandy Date: Wed, 21 May 2025 01:42:40 +0530 Subject: [PATCH 10/12] Add mass spring example and update requirements --- run_python_examples.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_python_examples.sh b/run_python_examples.sh index 29b948588a..d5c34eb67d 100755 --- a/run_python_examples.sh +++ b/run_python_examples.sh @@ -245,4 +245,4 @@ else #Exit with error (0-255) in case of failure in one of the tests. exit 1 -fi \ No newline at end of file +fi From b4c7bcf1d5fcc29e48b76e61c536e6ef2e06fd81 Mon Sep 17 00:00:00 2001 From: abhitorch81 Date: Wed, 25 Jun 2025 12:57:38 +0530 Subject: [PATCH 11/12] Updated README and visualization from corporate ID (abhitorch81) --- differentiable_physics/readme.md | 51 ++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/differentiable_physics/readme.md b/differentiable_physics/readme.md index 6c0f60512b..66f335b086 100644 --- a/differentiable_physics/readme.md +++ b/differentiable_physics/readme.md @@ -1,29 +1,46 @@ # Differentiable Physics: Mass-Spring System -This example demonstrates a simple differentiable mass-spring system using PyTorch. +This example demonstrates a simple differentiable **mass-spring system** using PyTorch. -Particles are connected by springs and evolve under the forces exerted by the springs and gravity. -The system is fully differentiable, allowing the optimization of particle positions to match a target configuration using gradient-based learning. +A set of particles is connected via springs and evolves over time under the influence of: +- **Spring forces** (via Hooke’s Law) +- **Gravity** (acting in the negative Y-direction) + +The system is fully differentiable, enabling **gradient-based optimization** of the **initial positions** of the particles so that their **final positions** match a desired **target configuration**. + +This idea is inspired by differentiable simulation frameworks such as those presented in recent research (see reference below). --- -## Files +## Files -- `mass_spring.py` — Implements the mass-spring simulation, training loop, and evaluation. -- `README.md` — Usage instructions and description. +- `mass_spring.py` — Implements the simulation, training loop, and evaluation logic. +- `README.md` — Description, instructions, and visualization output. +- `mass_spring_viz.png` — Output visualization of the final vs target configuration. + +--- +## Key Concepts + +| Term | Description | +|-------------------|-----------------------------------------------------------------------------| +| Initial Position | Learnable 2D coordinates (x, y) of each particle before simulation begins. | +| Target Position | Desired final 2D position after simulation. Used to compute loss. | +| Gravity | Constant force `[0, -9.8]` pulling particles downward in Y direction. | +| Spring Forces | Modeled using Hooke’s Law. Particles connected by springs exert forces. | +| Dimensionality | All particle positions and forces are 2D vectors. | --- ## Requirements - Python 3.8+ -- PyTorch -- pip install -r requirements.txt +- PyTorch ≥ 2.0 -No external dependencies are required apart from PyTorch. +Install requirements (if needed): +```bash +pip install -r requirements.txt ---- ## Usage @@ -35,9 +52,17 @@ First, ensure PyTorch is installed. python mass_spring.py --mode train -##### Visualization +![Mass-Spring System Visualization](mass_spring_viz.png) -After training, the system's final positions are compared to the target positions. The plot below illustrates this comparison: +*Mass-Spring System Visualization comparing final vs target positions.* + + + +## References + +[1] Sanchez-Gonzalez, A. et al. (2020). +Learning to Simulate Complex Physics with Graph Networks. +arXiv preprint arXiv:2002.09405. +Available: https://arxiv.org/abs/2002.09405 -![Mass-Spring System Visualization](mass_spring_viz.png) From 42dacb5b81afda3158af3e364e09d9e3924995a6 Mon Sep 17 00:00:00 2001 From: Abhishek Nandy Date: Wed, 25 Jun 2025 13:50:54 +0530 Subject: [PATCH 12/12] Update readme.md --- differentiable_physics/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/differentiable_physics/readme.md b/differentiable_physics/readme.md index 66f335b086..452eb5d374 100644 --- a/differentiable_physics/readme.md +++ b/differentiable_physics/readme.md @@ -38,7 +38,7 @@ This idea is inspired by differentiable simulation frameworks such as those pres - PyTorch ≥ 2.0 Install requirements (if needed): -```bash + pip install -r requirements.txt @@ -48,7 +48,7 @@ First, ensure PyTorch is installed. #### Train the system -```bash + python mass_spring.py --mode train