I have a function f[x]
with a high-dimensional vector input and a scalar output. I am looking to minimize this function while constraining some of the components of x
. What is an efficient way of doing this? My key concern is efficiency.
A trivial example
FindMinimum[{Norm[x], Indexed[x, 1] == 1.0}, {x, {1,2,3}}]
Here x
is a 3D vector and I constrained its first component to be 1.0. This certainly works, but when using Indexed
to constrain certain components, the performance becomes very bad. Is there a way to constrain some components of the vector without sacrificing performance?
A more realistic example
The following is a more involved example that better represents my actual use case, and can be used to test performance. We want to find the minimal energy state of a system of interconnected springs.
Let us generate a random system of springs:
n = 20;
SeedRandom[1234];
pts = Join[
RandomPoint[Disk[], n],
CirclePoints[n]
];
mesh = DelaunayMesh[pts]
The edges of this mesh will be our springs. We will take the rest lengths of the springs from the above-plotted configuration.
restLengths = PropertyValue[{mesh, 1}, MeshCellMeasure];
edges = MeshCells[mesh, 1][[All, 1]];
For simplicity, the energy of a spring is the squared difference between its actual length and rest length.
Clear[energy]
energy[coords_ /; MatrixQ[coords, NumericQ]] :=
With[{diff = Subtract @@ (coords[[#]] &) /@ Transpose[edges]},
Total[(Norm /@ diff - restLengths)^2]
]
Update: Here I meant to write Norm[#]^2&
instead of Norm
. I will keep the example as-is in order not to pull out the rug from under the already posted answer.
Let us now make the system 3-dimensional and choose our initial configuration:
coords = Append[#, 0.] & /@ MeshCoordinates[mesh];
With these coordinates, the energy is zero:
energy[coords] // Chop
(* 0 *)
Let us select the point closest to the centre, and pull it out of the initial plane by setting a non-zero $z$ coordinate:
v = First@Nearest[coords -> "Index", {0, 0, 0}];
coords[[v, 3]] = 0.3;
Now let us choose which points we keep fixed. Here, these are the points on the perimeter, as well as the point that we pulled out of the plane.
fixed = Append[
Range[n + 1, 2 n],
v
];
Due to pulling that single point, the energy is no longer zero:
energy[coords]
(* 0.0509467 *)
We are now almost ready to minimize the energy
function, but first we must construct the appropiate constraints for FindMinimum
, in order to keep some of the points fixed during minimization.
constraints = Table[
Indexed[x, i] == coords[[i]],
{i, fixed}
];
Here's the problem: Performance is really bad with constraints applied. Compare minimization with and without constraints:
In[187]:=
result =
FindMinimum[{energy[x], constraints}, {x, coords},
MaxIterations -> 5]; // AbsoluteTiming
During evaluation of In[187]:= FindMinimum::cvmit: Failed to converge to the requested accuracy or precision within 5 iterations.
Out[187]= {15.676, Null}
In[189]:=
In[190]:=
result =
FindMinimum[energy[x], {x, coords},
MaxIterations -> 5]; // AbsoluteTiming
During evaluation of In[190]:= FindMinimum::lstol: The line search decreased the step size to within the tolerance specified by AccuracyGoal and PrecisionGoal but was unable to find a sufficient decrease in the function. You may need more than MachinePrecision digits of working precision to meet these tolerances.
Out[190]= {0.361019, Null}
Question: How can I fix some of the coordinates without compromising performance? Is there another way than using Indexed
?
Note that I need the flexibility to choose which coordinates to constrain (in this case: which points to fix). This is why I do not want to implement energy
in such a way that its input would consist only of non-fixed coordinates.
In my real use case, the function I am minimizing is a compiled "black box" for better evaluation performance. I would hope to work with an x
that has a dimension on the order of 100–1000.
BTW, for the above example, this is an easy way to plot the final configuration:
Graphics3D@GraphicsComplex[x /. result[[2]], Line[edges]]
FindMinimum[{f[x,y,z], x==1},...]
, tryFindMinimum[f[1,y,z],...]
You could achieve that with a replacement while constructing the objective. $\endgroup$Norm
occurs in the objective)... $\endgroup$x
variable. If I used a separate symbol for each component ofx
, it would kill performance. But perhaps you are right, and I should just map a "constrained version" (i.e. values already substituted) ofx
into the originalx
, using some helper functions. This is exactly what Henrik seems to be doing. $\endgroup$1234
. $\endgroup$