I played around with a few plausible variants on the energy. In all cases I compared the result using the original energy function. Some things I learned:
(1) Some variants will tend to give results that do quite well when gauged via the original energy.
(2) Others (not shown below) will do poorly because they weigh the far values too heavily. This, alas, means we cannot easily use the GaussNewton (LevenbergMarquardt) method, since it is the operations of squaring that hurts us. Well, maybe there are ways around this.
(3) Summing over only distinct pairs rather than all pairs cuts the time in half. I will speculate that the bulk of time is spent in evaluating derivatives and not in the function evaluations themselves, as I am fairly certain Total[Outer[...]] will beat Sum even when the latter only need account for half or so as many pairs.
(4) For one energy variant I got a modest speed improvement using the ConjugateGradient method.
(5) Scaling appears to be quadratic in the number of points (no huge surprise, I guess).
(6) We can handle 200 points in 24 seconds on my desktop machine.
In[254]:=
pts = Apply[{ArcCos[2 #2 - 1], 2 \[Pi] #1} &, RandomReal[1, {100, 2}],
1];
Clear[a];
vars = Array[a, {Length[pts], 2}];
Here is the basic case.
In[292]:=
energy[p_] :=
Module[{cart},
cart = Map[{Sin[#[[1]]]*Cos[#[[2]]], Sin[#[[1]]]*Sin[#[[2]]],
Cos[#[[1]]]} &, p];
Total[Outer[Exp[-Sqrt[(#1 - #2).(#1 - #2)]] &, cart, cart, 1], 2]];
In[293]:=
t = Timing[{min, vals} =
Quiet[FindMinimum[energy[vars],
Transpose[{Flatten@vars, Flatten@pts}],
MaxIterations -> 1000]];];
{t, min}
Out[294]= {{14.1, Null}, 2978.01}
We use Sum over distinct pairs from here onward.
In[295]:=
energy2[p_] :=
Module[{cart},
cart = Map[{Sin[#[[1]]]*Cos[#[[2]]], Sin[#[[1]]]*Sin[#[[2]]],
Cos[#[[1]]]} &, p];
Sum[Exp[-Sqrt[(cart[[j]] - cart[[k]]).(cart[[j]] -
cart[[k]])]], {j, Length[p] - 1}, {k, j + 1, Length[p]}]];
In[296]:=
t2 = Timing[{min2, vals2} =
Quiet[FindMinimum[energy2[vars],
Transpose[{Flatten@vars, Flatten@pts}],
MaxIterations -> 1000]];];
{t2, min2, energy[vars /. vals2]}
Out[297]= {{6.58, Null}, 1439., 2978.01}
Minimize the sum of reciprocals of the pairwise distances squared.
In[298]:= energy3[p_] := Module[{cart},
cart = Map[{Sin[#[[1]]]*Cos[#[[2]]], Sin[#[[1]]]*Sin[#[[2]]],
Cos[#[[1]]]} &, p];
Sum[1/((cart[[j]] - cart[[k]]).(cart[[j]] - cart[[k]])), {j,
Length[p] - 1}, {k, j + 1, Length[p]}]
]
In[299]:=
t3 = Timing[{min3, vals3} =
Quiet[FindMinimum[energy2[vars],
Transpose[{Flatten@vars, Flatten@pts}],
MaxIterations -> 1000]];];
{t3, min3, energy[vars /. vals3]}
Out[300]= {{6.72, Null}, 1439., 2978.01}
This variant on energy happened to get a bit faster using a nondefault method setting.
In[301]:=
t3b = Timing[{min3b, vals3b} =
Quiet[FindMinimum[energy3[vars],
Transpose[{Flatten@vars, Flatten@pts}], MaxIterations -> 1000,
Method -> "ConjugateGradient"]];];
{t3b, min3b, energy[vars /. vals3b]}
Out[302]= {{5.23, Null}, 5340.65, 2978.01}
Maximize sum of distances. I will mention that using the sum of squares, which i would prefer to do, fails to give a useful result. It puts half the points one place and the other half at the polar opposite, I believe. That comes from the further distances getting relatively more weight in the objective function.
In[304]:= energy4[p_] := Module[{cart},
cart = Map[{Sin[#[[1]]]*Cos[#[[2]]], Sin[#[[1]]]*Sin[#[[2]]],
Cos[#[[1]]]} &, p];
Sum[Sqrt[((cart[[j]] - cart[[k]]).(cart[[j]] - cart[[k]]))], {j,
Length[p] - 1}, {k, j + 1, Length[p]}]
]
In[305]:=
t4 = Timing[{min4, vals4} =
Quiet[FindMaximum[energy4[vars],
Transpose[{Flatten@vars, Flatten@pts}],
MaxIterations -> 1000]];];
{t4, min4, energy[vars /. vals4]}
Out[306]= {{8.44, Null}, 6662.64, 2978.}
Similar to a couple of tries above, but with distances instead of squared distances.
In[308]:= energy5[p_] := Module[{cart},
cart = Map[{Sin[#[[1]]]*Cos[#[[2]]], Sin[#[[1]]]*Sin[#[[2]]],
Cos[#[[1]]]} &, p];
Sum[1/((cart[[j]] - cart[[k]]).(cart[[j]] - cart[[k]]))^(1/2), {j,
Length[p] - 1}, {k, j + 1, Length[p]}]
]
In[309]:=
t5 = Timing[{min5, vals5} =
Quiet[FindMinimum[energy5[vars],
Transpose[{Flatten@vars, Flatten@pts}],
MaxIterations -> 1000]];];
{t5, min5, energy[vars /. vals5]}
Out[310]= {{6.04, Null}, 4448.45, 2978.01}
Notice that all of these agreed fairly closely to six places in terms of the original energy function.
Now we'll go to 200 points and use the fastest variant from above.
In[319]:=
pts200 = Apply[{ArcCos[2 #2 - 1], 2 \[Pi] #1} &,
RandomReal[1, {200, 2}], 1];
vars200 = Array[a, {Length[pts200], 2}];
t200 = Timing[{min200, vals200} =
Quiet[FindMinimum[energy3[vars200],
Transpose[{Flatten@vars200, Flatten@pts200}],
MaxIterations -> 1000,
Method -> "ConjugateGradient"]];];
{t200, min200, energy[vars200 /. vals200]}
Out[322]= {{23.59, Null}, 24816.3, 11891.3}
Here I crib Mark McClure's code to show both the original points and the result of the optimization. The pictures will have to speak for themselves because i'm not going to speak for them.
In[323]:=
pts3D = {Sin[#[[1]]]*Cos[#[[2]]], Sin[#[[1]]]*Sin[#[[2]]],
Cos[#[[1]]]} & /@ pts200;
Graphics3D[Point[pts3D]]
In[325] := pts3Db = {Sin[#[[1]]]*Cos[#[[2]]], Sin[#[[1]]]*Sin[#[[2]]],
Cos[#[[1]]]} & /@ (vars200 /. vals200);
Graphics3D[Point[pts3Db]]
_?NumericQ
) is not so good. By allowing symbolic arguments in the function to be minimized, you also allowFindMinimum
to avoid taking numerical derivatives. At least that's how I approached the answer. $\endgroup$