"""
## GAMSSOURCE: https://www.gams.com/latest/gamslib_ml/libhtml/gamslib_cpack.html
## LICENSETYPE: Demo
## MODELTYPE: QCP
## KEYWORDS: quadratic constraint programming, circle packing problem, mathematics
Packing identical size circles in the unit circle (CPACK)
Given the unit circle (of radius 1), find a set of identical
size circles with an optimized (maximal) radius r so that all
such circles are contained by the unit circle, in a non-overlapping
arrangement.
A test example from the LGO library
Pinter, J D, Nonlinear optimization with GAMS/LGO.
Journal of Global Optimization 38 (2007), 79-101.
"""
from __future__ import annotations
import math
import os
import sys
from gamspy import (
Alias,
Container,
Equation,
Model,
Options,
Ord,
Problem,
Sense,
Set,
Variable,
)
from gamspy.math import sqr
# take number of circles as first argument
k = int(sys.argv[1]) if len(sys.argv) > 1 else 5
print("Number of circles =", k)
c = Container(
system_directory=os.getenv("SYSTEM_DIRECTORY", None),
)
# Set
i = Set(c, name="i", description="circles", records=[str(i) for i in range(k)])
j = Alias(c, name="j", alias_with=i)
ij = Set(c, name="ij", domain=[i, j])
ij[i, j].where[Ord(i) < Ord(j)] = True
# Variables
r = Variable(c, name="r", description="radius of circles")
x = Variable(c, name="x", domain=i, description="abscissa of circle")
y = Variable(c, name="y", domain=i, description="ordinate of circle")
# Equations
circumscribe = Equation(
c,
name="circumscribe",
domain=i,
description="enforce circle is enclosed in unit circle",
)
circumscribe[i] = sqr(1 - r) >= sqr(x[i]) + sqr(y[i])
nonoverlap = Equation(
c,
name="nonoverlap",
domain=[i, j],
description="enforce that circles do not overlap",
)
nonoverlap[ij[i, j]] = sqr(x[i] - x[j]) + sqr(y[i] - y[j]) >= 4 * sqr(r)
x.lo[i] = -1
x.up[i] = 1
y.lo[i] = -1
y.up[i] = 1
x.l[i] = -0.2 + Ord(i) * 0.1
y.l[i] = -0.2 + Ord(i) * 0.1
r.lo[...] = 0.05
r.up[...] = 0.4
m = Model(
c,
name="cpack",
equations=c.getEquations(),
problem=Problem.QCP,
sense=Sense.MAX,
objective=r,
)
# solve with a good global solver
print("Starting solve, be patient (log only shown afterwards)...")
m.solve(options=Options(relative_optimality_gap=0.01))
assert math.isclose(m.objective_value, 0.3702, rel_tol=0.001)
rval = r.records.loc[0, "level"]
print("Maximized radius:", rval)
# draw solution
width = 100
height = int(width / 2)
picture = bytearray(b" " * (width * height))
# enclosing circle at origin of radius 1
for v in range(1000):
phi = 2.0 * math.pi * v / 1000.0
# shift coordinates by 1.1 and scale down by 2.2
xcoord = (math.cos(phi) + 1.1) / 2.2 * width
ycoord = (math.sin(phi) + 1.1) / 2.2 * height
pos = int(xcoord) + int(ycoord) * width
if pos < len(picture):
picture[int(xcoord) + int(ycoord) * width] = 42
# circles that were packed
for circle in range(k):
xl = x.records.loc[circle, "lower"]
yl = y.records.loc[circle, "lower"]
# print('circle at', xl, yl, 'radius', rval)
for v in range(1000):
phi = 2.0 * math.pi * v / 1000.0
xcoord = (xl + rval * math.cos(phi) + 1.1) / 2.2 * width
ycoord = (yl + rval * math.sin(phi) + 1.1) / 2.2 * height
pos = int(xcoord) + int(ycoord) * width
if pos < len(picture):
picture[pos] = 97 + circle
# linebreaks
for v in range(height):
picture[v * width] = 10
# print(picture.decode())