Little example. Here, we try to pack \(n\) circles with a given radius \(r_i\) into a larger disc with an unknown radius \(R\). The goal is to minimize \(R\). The underlying model is simple:
Packing of Circles |
---|
\[\begin{align} \min\> & \color{darkred}R \\ & \sum_c \left(\color{darkred}p_{i,c}-\color{darkred}p_{j,c}\right)^2 \ge \left(\color{darkblue}r_i+\color{darkblue}r_j\right)^2 & \forall i\lt j \\ & \sum_c \color{darkred}p_{i,c}^2 \le \left(\color{darkred}R-\color{darkblue}r_i\right)^2 & \forall i \\ & \color{darkred}R \ge 0\\ & c \in \{x,y\} \\ \end{align}\] |
The first constraint says that inner circles cannot overlap. The second constraint states that inner circles should be inside the outer disc. These types of models are very difficult for global solvers. However, very good solutions can be found using a simple multistart NLP approach: solve several trials with a different random starting point.
Data
---- 30 PARAMETER radius lognormally distributed circle1 2.324, circle2 3.202, circle3 3.427, circle4 1.089, circle5 1.886, circle6 1.672, circle7 2.232 circle8 4.337, circle9 1.860, circle10 2.719, circle11 1.726, circle12 3.228, circle13 1.952, circle14 2.132 circle15 6.971, circle16 1.896, circle17 1.420, circle18 1.367, circle19 3.189, circle20 4.921, circle21 1.527 circle22 3.877, circle23 4.521, circle24 1.893, circle25 1.649, circle26 2.727, circle27 1.653, circle28 1.727 circle29 3.786, circle30 5.963, circle31 8.221, circle32 1.989, circle33 2.437, circle34 1.680, circle35 2.576 circle36 2.384, circle37 2.089, circle38 1.619, circle39 7.323, circle40 4.109, circle41 4.492, circle42 1.866 circle43 3.967, circle44 4.954, circle45 4.144, circle46 1.329, circle47 7.765, circle48 3.806, circle49 2.958 circle50 6.200
Solve loops
Visualization
Of course, we need some visualization to assess the results. Here, I use simple HTML and SVG (Scalable Vector Graphics), which are easy to generate. An advantage: rendering HTML does not require installing special software. In some corporate environments, this can be a big plus. In addition, HTML is easy to generate (just write a text file) and debug (the text file is human-readable). It is quite low-tech, but that is an advantage, I think.The results of my experiments look like:
Conclusions
- A single solve
- A serial multi-start loop with 25 solves
- A parallel multi-start loop with 250 solves
Appendix: GAMS Model
$onText
Pack n circles of given radius within a disc. Minimize the size of this disc.
This problem has nonconvex constraints. We have three approaches: 1. single solve 2. serial loop: 25 solves 3. parallel loop, 250 solves. No more than NumCores jobs at the time. $offtext
$set numSerialJobs 25 $set numParallelJobs 250
*--------------------------------------- * data *---------------------------------------
set i 'circles' /circle1*circle50/ ;
parameter radius(i) 'lognormally distributed'; radius(i) = exp(1+normal(0,0.5)); display radius;
*--------------------------------------- * additional sets *---------------------------------------
alias (i,j); sets ij(i,j) 'compare i<j' c 'coordinates' /x,y/ ; ij(i,j) = ord(i)<ord(j); display ij;
*--------------------------------------- * model *---------------------------------------
variables r 'radius of outer circle' p(i,c) 'position' ; r.lo = 0;
Equations overlap(i,j) 'no-overlap' inside(i) 'inside outer circle' ;
overlap(ij(i,j)).. sum(c, sqr(p(i,c)-p(j,c))) =g= sqr(radius(i)+radius(j));
inside(i).. sum(c, sqr(p(i,c))) =l= sqr(r-radius(i));
model pack /all/;
option nlp=conopt; scalar t; t = timeElapsed; p.l(i,c) = uniform(-20,20); r.l = 100; solve pack minimizing r using nlp; display p.l,r.l;
abort$(pack.modelstat <> %modelStat.locallyOptimal%) "no solution";
parameter solutions(*,*,*); solutions('Single',i,c) = p.l(i,c); solutions('Single',i,'r') = radius(i); solutions('Single','outer','r') = r.l; solutions('Single','numjobs','statistics') = 1; solutions('Single','time','statistics') = TimeElapsed-t; display solutions;
*--------------------------------------- * multistart approach: serial version *---------------------------------------
set trials /trial1*trial%numSerialJobs%/;
scalar bestr /inf/;
* turn off info written to listing file pack.solprint = 2;
t = TimeElapsed; loop(trials, p.l(i,c) = uniform(-10,10); r.l = 100; solve pack minimizing r using nlp; continue$(pack.modelstat <> %modelStat.locallyOptimal%); if (r.l<bestr, bestr = r.l; solutions(trials,'outer','r') = r.l; solutions('Multistart Serial',i,c) = p.l(i,c); solutions('Multistart Serial',i,'r') = radius(i); solutions('Multistart Serial','outer','r') = r.l; ); );
solutions('Multistart Serial','numjobs','statistics') = %numSerialJobs%; solutions('Multistart Serial','time','statistics') = TimeElapsed-t; display solutions;
*--------------------------------------- * multistart approach: parallel version *---------------------------------------
set ptrials /ptrial1*ptrial%numParallelJobs%/ launched(ptrials) 'jobj that have started' TimeIntervals '0.1 second intervals' /t1*t10000/ ; launched(ptrials) = no;
Parameter h(ptrials) 'model handles' sleepresult 'capture rerult of sleep function (not used)' numRunning 'number of running jobs' maxRunning 'max number of jobs running at the same time' MaxWaitTime 'abandon collection loop after this much time' /7200/ ; maxRunning = numCores; h(ptrials)=0;
t = timeElapsed;
* * job submission loop * pack.solveLink = 3; loop(ptrials, p.l(i,c) = uniform(-10,10); r.l = 100; loop(timeIntervals, numrunning = sum(launched$(handleStatus(h(launched)) = 1), 1); break$(numrunning < maxRunning); sleepResult = sleep(0.1); ); solve pack minimizing r using nlp; h(ptrials) = pack.handle; launched(ptrials) = yes; );
* * result collection loop * scalar StartCollectionTime; StartCollectionTime = TimeElapsed; bestr = inf; repeat loop(ptrials$h(ptrials), if(handleStatus(h(ptrials)) = 2, pack.handle = h(ptrials); execute_loadhandle pack; if(pack.modelstat = %modelStat.locallyOptimal%, if (r.l<bestr, bestr = r.l; solutions(ptrials,'outer','r') = r.l; solutions('Multistart Parallel',i,c) = p.l(i,c); solutions('Multistart Parallel',i,'r') = radius(i); solutions('Multistart Parallel','outer','r') = r.l; ); ); display$handleDelete(h(ptrials)) 'trouble deleting handles'; h(ptrials) = 0; ); ); break$(card(h)=0); display$readyCollect(h, 1000) 'Problem waiting for next instance to complete'; until timeElapsed-StartCollectionTime > MaxWaitTime;
solutions('Multistart Parallel','numjobs','statistics') = %numParallelJobs%; solutions('Multistart Parallel','time','statistics') = TimeElapsed-t; display solutions;
*--------------------------------------- * global solver can do this in one solve * this is a difficult model for global * solvers. *--------------------------------------- $ontext option nlp=baron; solve pack minimizing r using nlp; $offtext *--------------------------------------- * plot results *---------------------------------------
$set htmlfile 'result.html'
scalar r0 'radius of single job'; r0 = solutions('single','outer','r');
file f /%htmlfile%/; put f;
set run / Single, 'Multistart Serial','MultiStart Parallel'/;
put '<h1>Results of Circle Packing Model</h1>'/; put "<p>Pack ",card(i):0:0," circles with given radii in a container." " We minimize the size of the outer black disc. </p>"; put "<table><tr>"/;
loop(run, put '<td><svg width="400" height="300" viewBox="', (-1.1*r0):0:3,' ',(-1.1*r0):0:3,' ',(2.2*r0):0:3,' ',(2.2*r0):0:3,'">'/; put '<circle cx="0" cy="0" r="',solutions(run,'outer','r'):0:3,'"/>'/; loop(i, put '<circle cx="',solutions(run,i,'x'):0:3, '" cy="',solutions(run,i,'y'):0:3, '" r="',solutions(run,i,'r'):0:3, '" fill="darkorange"/>'/; ); put '</svg><p style="text-align: center;"><b>',run.tl:0," Solve</b><br>", " Number of optimization jobs: ",solutions(run,'numjobs','statistics'):0:0,"<br>", " Time (seconds): ",solutions(run,'time','statistics'):0:1,"<br>", " Length outer radius: ",solutions(run,'outer','r'):0:3,"<br>", "</p></td>"/; );
put "</tr></table>"/; putclose;
executeTool 'win32.ShellExecute "%htmlfile%"' |
From a circle packing contest in 2005 (http://www.recmath.org/contest/CirclePacking/), I recall that a useful heuristic was to use a multistart NLP solver for a reduced version of the problem that considers only the largest circles and then, after fixing their locations, solve the resulting problem for the remaining smaller circles.
ReplyDeleteI have looked at that once. Fixing gave me occasionally some bad results. May be for this log-normal distribution case it may work, as there are many very small circles.
Delete