Problem
Assume that four masses are connected to each other by springs while two of them are pinned down at their initial positions. Find the position of masses when system is at its minimum (potential) energy.
I've got this idea after reading this article. In a mass-spring system, the energy is equal to:
$$ U = \frac{1}{4} k \sum_{ij} (l_{ij}-l_{0})^2 $$
where each spring is counted twice. $l_{ij}$ is the distance of two masses $i$ and $j$ and $l_{0}$ is the spring rest length. My initial try was to use steepest descent to relax the system which was not the best option, so I switched to conjugate gradient.
The gradient on mass $i$ is:
$$ \vec{g}_{i} = \frac{k}{2} \sum_{j} (1-\frac{l_{0}}{l_{ij}}) (\vec{r}_i-\vec{r}_{j}) $$
where $j$ runs over only on masses connected to $i$. Obviously if one pins down all masses, the problem has no solution unless the system is already in the minimum potential. In any other case, the solution is a square (if we assume that masses are hard disks!). Although the above potential allows masses to overlap.
Code
To be specific, consider the following system:
SeedRandom[30];
(* initial coordinates of masses *)
mass = RandomReal[{0, 3}, {4, 2}];
(* mass 1 is connected to {2,3} and so on*)
spring = {{1, 2}, {1, 3}, {2, 4}, {3, 4}};
(* spring constant *)
k = 2.;
(* spring rest length*)
l = 1.;
(* step size *)
step = 0.02;
(* tolerance *)
tol = 10^-10.;
(* pinned masses *)
pinned = {2,3};
Then I calculate gradient and energy for the system:
(* gradient for mass i *)
grad[i_] := Sum[(0.5*k)*
(1. - l/EuclideanDistance[mass[[i]], mass[[j]]])*(mass[[i]] -
mass[[j]]), {j, spring[[i]]}];
(* gradient vector*)
gradient[mass_] := Flatten[Last@
Reap@Do[If[MemberQ[pinned, i], Sow[{0., 0.}], Sow[grad[i]]], {i,
1, Length@mass}], 1];
(* energy of the system*)
potential[mass_] := (0.25*k)*Sum[Total[(EuclideanDistance[mass[[i]], mass[[#]]] - l)^2 & /@ spring[[i]]], {i, 1, Length@mass}];
Then by using conjugate gradient, I try to find the minimum potential:
oldgradient = gradient[mass];
oldpotential = potential[mass];
mass = mass - step*gradient[mass];
olddir = newgradient = gradient[mass];
newpotential = potential[mass];
While[Abs[newpotential - oldpotential] > tol,
gamma = ((Norm@Flatten@newgradient)/(Norm@Flatten@oldgradient));
newdir = -newgradient + gamma*olddir;
mass = mass + step*newdir;
olddir = newdir;
oldgradient = newgradient;
newgradient = gradient[mass];
oldpotential = newpotential;
newpotential = potential[mass];
];
And finally, system reaches to this point:
which shows 2 and 3 are held fixed.
Questions
- Do you know of any internal method which does the same job in Matematica?
- While typing the question, I found "spring embedding algorithm" in Mathematica documentation, under
Graph Drawing Algorithm
. This is certainly related, so can we find vertex positions using this method? - To speed up the code, any help would be appreciated. I tried to write potential and gradient as compiled functions, but they don't seem faster.
Although I am aware that calculating energy of each spring twice is not efficient.
cpotential = With[{w = spring},
Compile[{{z, _Real, 2}},
0.25*k*
Sum[Total[(EuclideanDistance[z[[i]], z[[#]]] - l)^2 & /@
w[[i]]], {i, Length@z}],
RuntimeAttributes -> {Listable}, RuntimeOptions -> "Speed",
Parallelization -> True
]
];
LinearSolve
, you could also use directlyLinearPrograming
but possibly the new finite element abilities of NDSolve, in Mathematica 10, may have the means to do this all automatically, but I am still stuck with Mathematica 9 and have no means to play with it :,( . $\endgroup$