From d310993a3f9dbcb6f2c3c7f2f47bfa04aed72252 Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Tue, 19 Mar 2024 13:42:18 +0000
Subject: [PATCH 01/10] new constraint sphere2.py
---
open-codegen/opengen/constraints/sphere2.py | 62 +++++++++++++++++++++
1 file changed, 62 insertions(+)
create mode 100644 open-codegen/opengen/constraints/sphere2.py
diff --git a/open-codegen/opengen/constraints/sphere2.py b/open-codegen/opengen/constraints/sphere2.py
new file mode 100644
index 00000000..747cd018
--- /dev/null
+++ b/open-codegen/opengen/constraints/sphere2.py
@@ -0,0 +1,62 @@
+import casadi.casadi as cs
+import numpy as np
+from .constraint import Constraint
+import opengen.functions as fn
+
+
+class Sphere2(Constraint):
+ """A Euclidean sphere constraint
+
+ A constraint of the form :math:`\|u-u_0\| = r`, where :math:`u_0` is the center
+ of the ball and `r` is its radius
+
+ """
+
+ def __init__(self, center=None, radius: float = 1.0):
+ """Constructor for a Euclidean sphere constraint
+
+ :param center: center of the sphere; if this is equal to Null, the
+ sphere is centered at the origin
+
+ :param radius: radius of the sphere
+
+ :return: New instance of Sphere2 with given center and radius
+ """
+ if radius <= 0:
+ raise Exception("The radius must be a positive number")
+
+ if center is not None and not isinstance(center, (list, np.ndarray)):
+ raise Exception("center is neither None nor a list nor np.ndarray")
+
+ self.__center = None if center is None else np.array(
+ [float(i) for i in center])
+ self.__radius = float(radius)
+
+ @property
+ def center(self):
+ """Returns the center of the ball"""
+ return self.__center
+
+ @property
+ def radius(self):
+ """Returns the radius of the sphere"""
+ return self.__radius
+
+ def distance_squared(self, u):
+ """Computes the squared distance between a given point `u` and this sphere
+
+ :param u: given point; can be a list of float, a numpy
+ n-dim array (`ndarray`) or a CasADi SX/MX symbol
+
+ :return: distance from set as a float or a CasADi symbol
+ """
+ raise NotImplementedError()
+
+ def project(self, u):
+ raise NotImplementedError()
+
+ def is_convex(self):
+ return True
+
+ def is_compact(self):
+ return True
From 6aa66e6ad4449bffb484552480fe9fd4900a524f Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Tue, 19 Mar 2024 13:54:48 +0000
Subject: [PATCH 02/10] first implementation of sphere2 codegen
---
open-codegen/opengen/constraints/__init__.py | 1 +
open-codegen/opengen/templates/optimizer.rs | 5 ++++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/open-codegen/opengen/constraints/__init__.py b/open-codegen/opengen/constraints/__init__.py
index a0aa9477..4fbf168b 100644
--- a/open-codegen/opengen/constraints/__init__.py
+++ b/open-codegen/opengen/constraints/__init__.py
@@ -1,5 +1,6 @@
from .ball1 import *
from .ball2 import *
+from .sphere2 import *
from .rectangle import *
from .constraint import *
from .ball_inf import *
diff --git a/open-codegen/opengen/templates/optimizer.rs b/open-codegen/opengen/templates/optimizer.rs
index b6019a61..f4f871ef 100644
--- a/open-codegen/opengen/templates/optimizer.rs
+++ b/open-codegen/opengen/templates/optimizer.rs
@@ -65,7 +65,7 @@ pub const {{meta.optimizer_name|upper}}_N2: usize = {{problem.dim_constraints_pe
// ---Parameters of the constraints----------------------------------------------------------------------
-{% if 'Ball1' == problem.constraints.__class__.__name__ or 'Ball2' == problem.constraints.__class__.__name__ or 'BallInf' == problem.constraints.__class__.__name__ -%}
+{% if 'Ball1' == problem.constraints.__class__.__name__ or 'Ball2' == problem.constraints.__class__.__name__ or 'BallInf' == problem.constraints.__class__.__name__ or 'Sphere2' == problem.constraints.__class__.__name__ -%}
/// Constraints: Centre of Ball
const CONSTRAINTS_BALL_XC: Option<&[f64]> = {% if problem.constraints.center is not none %}Some(&[{{problem.constraints.center | join(', ')}}]){% else %}None{% endif %};
@@ -140,6 +140,9 @@ fn make_constraints() -> impl Constraint {
{% elif 'Ball1' == problem.constraints.__class__.__name__ -%}
// - Ball1:
Ball1::new(CONSTRAINTS_BALL_XC, CONSTRAINTS_BALL_RADIUS)
+ {% elif 'Sphere2' == problem.constraints.__class__.__name__ -%}
+ // - Sphere2:
+ Sphere2::new(CONSTRAINTS_BALL_XC, CONSTRAINTS_BALL_RADIUS)
{% elif 'Simplex' == problem.constraints.__class__.__name__ -%}
// - Simplex:
let alpha_simplex : f64 = {{problem.constraints.alpha}};
From ed0677b947ab0dc1d2d6d4a4ea98ce55e1b951d4 Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Tue, 19 Mar 2024 13:58:50 +0000
Subject: [PATCH 03/10] update changelog (opengen)
---
open-codegen/CHANGELOG.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/open-codegen/CHANGELOG.md b/open-codegen/CHANGELOG.md
index 0a0a22f2..0d8523ea 100644
--- a/open-codegen/CHANGELOG.md
+++ b/open-codegen/CHANGELOG.md
@@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
Note: This is the Changelog file of `opengen` - the Python interface of OpEn
+## [0.8.0] - 2024-03-20
+### Added
+
+* Code generation support for Sphere2
## [0.7.1] - 2022-02-14
From 74543987c86d8bb3e334dbb21d597a5543ea832d Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Tue, 19 Mar 2024 13:59:25 +0000
Subject: [PATCH 04/10] changelog (opengen): update link to v0.8.0
---
open-codegen/CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/open-codegen/CHANGELOG.md b/open-codegen/CHANGELOG.md
index 0d8523ea..81a2df78 100644
--- a/open-codegen/CHANGELOG.md
+++ b/open-codegen/CHANGELOG.md
@@ -185,6 +185,7 @@ Note: This is the Changelog file of `opengen` - the Python interface of OpEn
* Project-specific `tcp_iface` TCP interface
* Fixed `lbfgs` typo
+[0.8.0]: https://github.com/alphaville/optimization-engine/compare/opengen-0.7.1...opengen-0.8.0
[0.7.1]: https://github.com/alphaville/optimization-engine/compare/opengen-0.7.0...opengen-0.7.1
[0.7.0]: https://github.com/alphaville/optimization-engine/compare/opengen-0.6.13...opengen-0.7.0
[0.6.13]: https://github.com/alphaville/optimization-engine/compare/opengen-0.6.12...opengen-0.6.13
From 2dc60dbe484d6ba1354ee24a15d5489be32f9a33 Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Tue, 19 Mar 2024 14:05:10 +0000
Subject: [PATCH 05/10] sphere2-constraints supported in Cartesian products
---
open-codegen/opengen/templates/optimizer.rs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/open-codegen/opengen/templates/optimizer.rs b/open-codegen/opengen/templates/optimizer.rs
index f4f871ef..912784db 100644
--- a/open-codegen/opengen/templates/optimizer.rs
+++ b/open-codegen/opengen/templates/optimizer.rs
@@ -187,6 +187,11 @@ fn make_constraints() -> impl Constraint {
let center_{{loop.index}}: Option<&[f64]> = {% if set_i.center is not none %}Some(&[{{set_i.center | join(', ')}}]){% else %}None{% endif %};
let set_{{loop.index}} = Ball1::new(center_{{loop.index}}, radius_{{loop.index}});
let bounds = bounds.add_constraint(idx_{{loop.index}}, set_{{loop.index}});
+ {% elif 'Sphere2' == set_i.__class__.__name__ -%}
+ let radius_{{loop.index}} = {{set_i.radius}};
+ let center_{{loop.index}}: Option<&[f64]> = {% if set_i.center is not none %}Some(&[{{set_i.center | join(', ')}}]){% else %}None{% endif %};
+ let set_{{loop.index}} = Sphere2::new(center_{{loop.index}}, radius_{{loop.index}});
+ let bounds = bounds.add_constraint(idx_{{loop.index}}, set_{{loop.index}});
{% elif 'Simplex' == set_i.__class__.__name__ -%}
let alpha_{{loop.index}} = {{set_i.alpha}};
let set_{{loop.index}} = Simplex::new(alpha_{{loop.index}});
From af208d59b75f6480e7b3e10bda577f6e9565877d Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Tue, 19 Mar 2024 14:27:28 +0000
Subject: [PATCH 06/10] [ci skip] update webpage docs for sphere2
---
docs/python-interface.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/python-interface.md b/docs/python-interface.md
index 679ef0ba..a332ea29 100644
--- a/docs/python-interface.md
+++ b/docs/python-interface.md
@@ -80,6 +80,7 @@ following types of constraints:
| `Ball2` | Euclidean ball: `Ball2(None, r)` creates a Euclidean ball of radius `r` centered at the origin, and `Ball2(xc, r)` is a ball centered at point `xc` (list/np.array) |
| `BallInf` | Ball of infinity norm:`BallInf(None, r)` creates an infinity-norm ball of radius `r` centered at the origin, and `BallInf(xc, r)` is an infinity ball centered at point `xc` (list/np.array) |
| `Ball1` | L1 ball: `Ball(None, r)` creates an ell1-ball of radius `r` centered at the origin, and `BallInf(xc, r)` is an ell1-ball centered at point `xc` (list/np.array)|
+| `Sphere2` | Euclidean sphere: `Sphere2(None, r)` creates a Euclidean sphere of radius `r` centered at the origin, and `Sphere2(xc, r)` is a sphere centered at point `xc` (list/np.array) |
| `Simplex` | A simplex of size $\alpha$ is a set of the form $\Delta_\alpha = \\{x \in \mathbb{R}^n {}:{} x_i \geq 0, \sum_i x_i = \alpha\\}$. Create one with `Simplex(alpha)`. Projections are computed using Condat's [fast projection method](https://link.springer.com/article/10.1007/s10107-015-0946-6). |
| `Halfspace` | A halfspace is a set of the form $\\{u \in \mathbb{R}^{n_u} {}:{} \langle c, u\rangle \leq b \\}$, for a vector $c$ and a scalar $b$. The syntax is straightforwarrd: `Halfspace(c, b)`. |
| `FiniteSet` | Finite set, $\\{u^{(1)},\ldots,u^{(m)}\\}$; the set of point is provided as a list of lists, for example, `FiniteSet([[1,2],[2,3],[4,5]])`. The commonly used set of binary numbers, $\\{0, 1\\}$, is created with `FiniteSet([[0], [1]])`. |
From 5eaad35bff65fe7ed4691edff5b9079c8802f281 Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Tue, 19 Mar 2024 14:28:55 +0000
Subject: [PATCH 07/10] [ci skip] bump opengen version 0.8.0
---
open-codegen/VERSION | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/open-codegen/VERSION b/open-codegen/VERSION
index 7deb86fe..8adc70fd 100644
--- a/open-codegen/VERSION
+++ b/open-codegen/VERSION
@@ -1 +1 @@
-0.7.1
\ No newline at end of file
+0.8.0
\ No newline at end of file
From cb3547e06197399d0cfd017d6187264b42dccbe3 Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Tue, 19 Mar 2024 14:54:04 +0000
Subject: [PATCH 08/10] implementation of sphere2:distance_squared
---
open-codegen/opengen/constraints/sphere2.py | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/open-codegen/opengen/constraints/sphere2.py b/open-codegen/opengen/constraints/sphere2.py
index 747cd018..5bbddbd2 100644
--- a/open-codegen/opengen/constraints/sphere2.py
+++ b/open-codegen/opengen/constraints/sphere2.py
@@ -50,13 +50,26 @@ def distance_squared(self, u):
:return: distance from set as a float or a CasADi symbol
"""
- raise NotImplementedError()
+ v = u
+ if (isinstance(u, list) and all(isinstance(x, (int, float)) for x in u))\
+ or isinstance(u, np.ndarray):
+ if self.__center is None:
+ v = u
+ else:
+ # Note: self.__center is np.ndarray (`u` might be a list)
+ z = self.__center.reshape(len(u))
+ u = np.array(u).reshape(len(u))
+ v = np.subtract(u, z)
+ elif not fn.is_symbolic(u):
+ raise Exception("u is of invalid type")
+
+ return (self.__radius - fn.norm2(v))**2
def project(self, u):
raise NotImplementedError()
def is_convex(self):
- return True
+ return False
def is_compact(self):
return True
From 809f69d34e93dc561fc3d93490797e3dbd060c6e Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Tue, 19 Mar 2024 14:59:52 +0000
Subject: [PATCH 09/10] test for sphere2 constraint
---
open-codegen/test/test_constraints.py | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/open-codegen/test/test_constraints.py b/open-codegen/test/test_constraints.py
index a2022513..464ddb3a 100644
--- a/open-codegen/test/test_constraints.py
+++ b/open-codegen/test/test_constraints.py
@@ -183,7 +183,8 @@ def test_rectangle_noncompact(self):
self.assertFalse(rect.is_compact())
def test_rectangle_is_orthant(self):
- rect = og.constraints.Rectangle([0, float('-inf')], [float('inf'), 0.0])
+ rect = og.constraints.Rectangle(
+ [0, float('-inf')], [float('inf'), 0.0])
self.assertTrue(rect.is_orthant())
rect = og.constraints.Rectangle([0, 0], [float('inf'), float('inf')])
self.assertTrue(rect.is_orthant())
@@ -492,6 +493,17 @@ def test_ball1_project_random_points_center(self):
self.assertLessEqual(
np.dot(e-x_star, x-x_star), 1e-10, "Ball1 optimality conditions failed (2)")
+ # -----------------------------------------------------------------------
+ # Sphere2
+ # -----------------------------------------------------------------------
+
+ def test_sphere2_sq_distance(self):
+ sphere = og.constraints.Sphere2(center=[1, 1, 1, 1], radius=0.5)
+ self.assertFalse(sphere.is_convex())
+ u = [1, 1, 0, 0]
+ dist = sphere.distance_squared(u)
+ self.assertAlmostEqual(0.835786437626905, dist, places=12)
+
if __name__ == '__main__':
unittest.main()
From dd8b65880980e3dc16e0305f0cf607839872a178 Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Wed, 20 Mar 2024 13:03:54 +0000
Subject: [PATCH 10/10] sphere2: rewrote if block for clarity additional test
for Sphere2 with symbolic variable
---
open-codegen/opengen/constraints/sphere2.py | 8 +++++---
open-codegen/test/test_constraints.py | 9 +++++++++
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/open-codegen/opengen/constraints/sphere2.py b/open-codegen/opengen/constraints/sphere2.py
index 5bbddbd2..f8fca5f2 100644
--- a/open-codegen/opengen/constraints/sphere2.py
+++ b/open-codegen/opengen/constraints/sphere2.py
@@ -50,8 +50,10 @@ def distance_squared(self, u):
:return: distance from set as a float or a CasADi symbol
"""
- v = u
- if (isinstance(u, list) and all(isinstance(x, (int, float)) for x in u))\
+ if fn.is_symbolic(u):
+ # Case I: `u` is a CasADi SX symbol
+ v = u if self.__center is None else u - self.__center
+ elif (isinstance(u, list) and all(isinstance(x, (int, float)) for x in u))\
or isinstance(u, np.ndarray):
if self.__center is None:
v = u
@@ -60,7 +62,7 @@ def distance_squared(self, u):
z = self.__center.reshape(len(u))
u = np.array(u).reshape(len(u))
v = np.subtract(u, z)
- elif not fn.is_symbolic(u):
+ else:
raise Exception("u is of invalid type")
return (self.__radius - fn.norm2(v))**2
diff --git a/open-codegen/test/test_constraints.py b/open-codegen/test/test_constraints.py
index 464ddb3a..4421cbd8 100644
--- a/open-codegen/test/test_constraints.py
+++ b/open-codegen/test/test_constraints.py
@@ -504,6 +504,15 @@ def test_sphere2_sq_distance(self):
dist = sphere.distance_squared(u)
self.assertAlmostEqual(0.835786437626905, dist, places=12)
+ def test_sphere2_sq_distance_symbolic(self):
+ sphere = og.constraints.Sphere2(center=[1, 1, 1, 1], radius=0.5)
+ self.assertFalse(sphere.is_convex())
+ u = cs.SX.sym("u", 4)
+ dist = sphere.distance_squared(u)
+ fun = cs.Function('fun', [u], [dist])
+ z = fun([1, 1, 0, 0])
+ self.assertAlmostEqual(0.835786437626905, z, places=12)
+
if __name__ == '__main__':
unittest.main()