From 0b0cc6ae79e834c2980907b95b46cc72b2479b70 Mon Sep 17 00:00:00 2001 From: Pantelis Sopasakis Date: Mon, 30 Mar 2026 17:42:12 +0100 Subject: [PATCH 1/7] add dimension() to constraints --- python/opengen/constraints/affine_space.py | 3 +++ python/opengen/constraints/ball1.py | 3 +++ python/opengen/constraints/ball2.py | 3 +++ python/opengen/constraints/ball_inf.py | 3 +++ python/opengen/constraints/cartesian.py | 3 +++ python/opengen/constraints/constraint.py | 9 +++++++++ python/opengen/constraints/finite_set.py | 2 +- python/opengen/constraints/no_constraints.py | 3 +++ python/opengen/constraints/simplex.py | 3 +++ python/opengen/constraints/soc.py | 3 +++ python/opengen/constraints/zero.py | 1 + 11 files changed, 35 insertions(+), 1 deletion(-) diff --git a/python/opengen/constraints/affine_space.py b/python/opengen/constraints/affine_space.py index 1eb054c6..ae38bfea 100644 --- a/python/opengen/constraints/affine_space.py +++ b/python/opengen/constraints/affine_space.py @@ -54,3 +54,6 @@ def is_compact(self): """Affine spaces are not compact sets """ return False + + def dimension(self): + return super().dimension() diff --git a/python/opengen/constraints/ball1.py b/python/opengen/constraints/ball1.py index 7ca05849..56082eb7 100644 --- a/python/opengen/constraints/ball1.py +++ b/python/opengen/constraints/ball1.py @@ -73,3 +73,6 @@ def is_convex(self): def is_compact(self): return True + + def dimension(self): + return super().dimension() \ No newline at end of file diff --git a/python/opengen/constraints/ball2.py b/python/opengen/constraints/ball2.py index f0bb43be..a993d613 100644 --- a/python/opengen/constraints/ball2.py +++ b/python/opengen/constraints/ball2.py @@ -91,3 +91,6 @@ def is_convex(self): def is_compact(self): return True + + def dimension(self): + return super().dimension() \ No newline at end of file diff --git a/python/opengen/constraints/ball_inf.py b/python/opengen/constraints/ball_inf.py index 738eaf4f..72c28dbc 100644 --- a/python/opengen/constraints/ball_inf.py +++ b/python/opengen/constraints/ball_inf.py @@ -88,3 +88,6 @@ def is_convex(self): def is_compact(self): return True + + def dimension(self): + return super().dimension() \ No newline at end of file diff --git a/python/opengen/constraints/cartesian.py b/python/opengen/constraints/cartesian.py index c4b1d662..a5b8e40a 100644 --- a/python/opengen/constraints/cartesian.py +++ b/python/opengen/constraints/cartesian.py @@ -124,3 +124,6 @@ def is_compact(self): if not set_i.is_compact(): return False return True + + def dimension(self): + return super().dimension() \ No newline at end of file diff --git a/python/opengen/constraints/constraint.py b/python/opengen/constraints/constraint.py index 6076f223..8fee3aa8 100644 --- a/python/opengen/constraints/constraint.py +++ b/python/opengen/constraints/constraint.py @@ -32,3 +32,12 @@ def is_compact(self): Whether the set is compact """ return False + + def dimension(self): + """ + Constraint dimension + + Derived classes can override this method to return the dimension of the + constraint, where possible, or return `None` if the constraint does not + have a fixed dimension. + """ \ No newline at end of file diff --git a/python/opengen/constraints/finite_set.py b/python/opengen/constraints/finite_set.py index b590caab..37908a64 100644 --- a/python/opengen/constraints/finite_set.py +++ b/python/opengen/constraints/finite_set.py @@ -7,7 +7,7 @@ class FiniteSet(Constraint): """Finite set - A set of the form :math:`A = \{a_1, a_2, \ldots, a_K\}` + A set of the form :math:`A = \\{a_1, a_2, \\ldots, a_K\\}` """ def __init__(self, points=None): diff --git a/python/opengen/constraints/no_constraints.py b/python/opengen/constraints/no_constraints.py index a81722d4..e16507dc 100644 --- a/python/opengen/constraints/no_constraints.py +++ b/python/opengen/constraints/no_constraints.py @@ -21,3 +21,6 @@ def is_convex(self): def is_compact(self): return False + + def dimension(self): + return super().dimension() \ No newline at end of file diff --git a/python/opengen/constraints/simplex.py b/python/opengen/constraints/simplex.py index 1a07fa93..763ed883 100644 --- a/python/opengen/constraints/simplex.py +++ b/python/opengen/constraints/simplex.py @@ -103,3 +103,6 @@ def is_convex(self): def is_compact(self): """Whether the set is compact (`True`)""" return True + + def dimension(self): + return super().dimension() \ No newline at end of file diff --git a/python/opengen/constraints/soc.py b/python/opengen/constraints/soc.py index eb1279e5..49c76579 100644 --- a/python/opengen/constraints/soc.py +++ b/python/opengen/constraints/soc.py @@ -85,3 +85,6 @@ def is_convex(self): def is_compact(self): return False + + def dimension(self): + return super().dimension() \ No newline at end of file diff --git a/python/opengen/constraints/zero.py b/python/opengen/constraints/zero.py index 8205445c..95761bab 100644 --- a/python/opengen/constraints/zero.py +++ b/python/opengen/constraints/zero.py @@ -28,3 +28,4 @@ def is_convex(self): def is_compact(self): return True + From 80567941e058b55ea8bf5eee8a6bbc957290ae76 Mon Sep 17 00:00:00 2001 From: Pantelis Sopasakis Date: Mon, 30 Mar 2026 18:06:56 +0100 Subject: [PATCH 2/7] dimension() implemented in constraints --- python/opengen/constraints/ball1.py | 4 +- python/opengen/constraints/ball2.py | 6 +- python/opengen/constraints/ball_inf.py | 4 +- python/opengen/constraints/cartesian.py | 2 +- python/opengen/constraints/no_constraints.py | 2 +- python/opengen/constraints/rectangle.py | 2 +- python/opengen/constraints/simplex.py | 2 +- python/opengen/constraints/soc.py | 2 +- python/opengen/constraints/sphere2.py | 5 ++ python/opengen/constraints/zero.py | 7 +- python/test/test_constraints.py | 72 +++++++++++++++++++- 11 files changed, 95 insertions(+), 13 deletions(-) diff --git a/python/opengen/constraints/ball1.py b/python/opengen/constraints/ball1.py index 56082eb7..deee203d 100644 --- a/python/opengen/constraints/ball1.py +++ b/python/opengen/constraints/ball1.py @@ -75,4 +75,6 @@ def is_compact(self): return True def dimension(self): - return super().dimension() \ No newline at end of file + if self.center is None: + return None + return len(self.center) \ No newline at end of file diff --git a/python/opengen/constraints/ball2.py b/python/opengen/constraints/ball2.py index a993d613..70fdffd0 100644 --- a/python/opengen/constraints/ball2.py +++ b/python/opengen/constraints/ball2.py @@ -7,7 +7,7 @@ class Ball2(Constraint): """A Euclidean ball constraint - A constraint of the form :math:`\|u-u_0\| \leq r`, where :math:`u_0` is the center + A constraint of the form :math:`\Vert u-u_0 \Vert \leq r`, where :math:`u_0` is the center of the ball and `r` is its radius """ @@ -93,4 +93,6 @@ def is_compact(self): return True def dimension(self): - return super().dimension() \ No newline at end of file + if self.center is None: + return None + return len(self.center) \ No newline at end of file diff --git a/python/opengen/constraints/ball_inf.py b/python/opengen/constraints/ball_inf.py index 72c28dbc..4f4ea4ec 100644 --- a/python/opengen/constraints/ball_inf.py +++ b/python/opengen/constraints/ball_inf.py @@ -90,4 +90,6 @@ def is_compact(self): return True def dimension(self): - return super().dimension() \ No newline at end of file + if self.center is None: + return None + return len(self.center) \ No newline at end of file diff --git a/python/opengen/constraints/cartesian.py b/python/opengen/constraints/cartesian.py index a5b8e40a..448ebb16 100644 --- a/python/opengen/constraints/cartesian.py +++ b/python/opengen/constraints/cartesian.py @@ -126,4 +126,4 @@ def is_compact(self): return True def dimension(self): - return super().dimension() \ No newline at end of file + return self.segments[-1] + 1 \ No newline at end of file diff --git a/python/opengen/constraints/no_constraints.py b/python/opengen/constraints/no_constraints.py index e16507dc..7dd72a18 100644 --- a/python/opengen/constraints/no_constraints.py +++ b/python/opengen/constraints/no_constraints.py @@ -23,4 +23,4 @@ def is_compact(self): return False def dimension(self): - return super().dimension() \ No newline at end of file + return None \ No newline at end of file diff --git a/python/opengen/constraints/rectangle.py b/python/opengen/constraints/rectangle.py index c6340d09..f77e92b8 100644 --- a/python/opengen/constraints/rectangle.py +++ b/python/opengen/constraints/rectangle.py @@ -31,7 +31,7 @@ def __check_xmin_xmax(cls, xmin, xmax): if xmin_element > xmax_element: raise Exception("xmin must be <= xmax") - def __init__(self, xmin, xmax): + def __init__(self, xmin=None, xmax=None): """Construct a new instance of Rectangle :param xmin: minimum bounds (can be ``None``) diff --git a/python/opengen/constraints/simplex.py b/python/opengen/constraints/simplex.py index 763ed883..113767e3 100644 --- a/python/opengen/constraints/simplex.py +++ b/python/opengen/constraints/simplex.py @@ -105,4 +105,4 @@ def is_compact(self): return True def dimension(self): - return super().dimension() \ No newline at end of file + return None \ No newline at end of file diff --git a/python/opengen/constraints/soc.py b/python/opengen/constraints/soc.py index 49c76579..466058df 100644 --- a/python/opengen/constraints/soc.py +++ b/python/opengen/constraints/soc.py @@ -87,4 +87,4 @@ def is_compact(self): return False def dimension(self): - return super().dimension() \ No newline at end of file + return None \ No newline at end of file diff --git a/python/opengen/constraints/sphere2.py b/python/opengen/constraints/sphere2.py index f8fca5f2..5283da53 100644 --- a/python/opengen/constraints/sphere2.py +++ b/python/opengen/constraints/sphere2.py @@ -75,3 +75,8 @@ def is_convex(self): def is_compact(self): return True + + def dimension(self): + if self.center is None: + return None + return len(self.center) \ No newline at end of file diff --git a/python/opengen/constraints/zero.py b/python/opengen/constraints/zero.py index 95761bab..96cb357e 100644 --- a/python/opengen/constraints/zero.py +++ b/python/opengen/constraints/zero.py @@ -7,15 +7,16 @@ class Zero(Constraint): """A set that contains only the origin - The singleton :math:`\{0\}` + The singleton :math:`\\{0\\}` """ def __init__(self): """ - Constructor for set :math:`Z = \{0\}` + Constructor for set :math:`Z = \\{0\\}` """ + pass def distance_squared(self, u): return fn.norm2_squared(u) @@ -29,3 +30,5 @@ def is_convex(self): def is_compact(self): return True + def dimension(self): + return None \ No newline at end of file diff --git a/python/test/test_constraints.py b/python/test/test_constraints.py index f080d518..53135eaa 100644 --- a/python/test/test_constraints.py +++ b/python/test/test_constraints.py @@ -11,7 +11,7 @@ class ConstraintsTestCase(unittest.TestCase): # Infinity Ball # ----------------------------------------------------------------------- - def test_ball_inf_origin(self): + def test_ball_inf_origin(self): ball = og.constraints.BallInf(None, 1) x = np.array([3, 2]) x_sym = cs.SX.sym("x", 2) @@ -58,6 +58,13 @@ def test_ball_inf_origin_compact(self): ball = og.constraints.BallInf() self.assertTrue(ball.is_compact()) + def test_dimension_ballInf(self): + ball = og.constraints.BallInf() + self.assertIsNone(ball.dimension()) + + ball = og.constraints.BallInf(center=[1., 2., -3.]) + self.assertEqual(3, ball.dimension()) + # ----------------------------------------------------------------------- # Euclidean Ball # ----------------------------------------------------------------------- @@ -122,6 +129,13 @@ def test_ball_euclidean_origin_compact(self): ball = og.constraints.Ball2() self.assertTrue(ball.is_compact()) + def test_dimension_ball2(self): + ball = og.constraints.Ball2() + self.assertIsNone(ball.dimension()) + + ball = og.constraints.Ball2(center=[1., 2., -3.]) + self.assertEqual(3, ball.dimension()) + # ----------------------------------------------------------------------- # Rectangle # ----------------------------------------------------------------------- @@ -194,6 +208,14 @@ def test_rectangle_is_orthant(self): self.assertFalse(rect.is_orthant()) rect = og.constraints.Rectangle([-1.0, float('-inf')], [10.0, 3.0]) self.assertFalse(rect.is_orthant()) + + def test_rectangle_dimension(self): + rec_only_xmin = og.constraints.Rectangle(xmin=[1]) + rec_only_xmax = og.constraints.Rectangle(xmax=[5, 6]) + rec_xmin_and_xmax = og.constraints.Rectangle(xmin=[0, -1, 2], xmax=[1, 0, 2]) + self.assertEqual(1, rec_only_xmin.dimension()) + self.assertEqual(2, rec_only_xmax.dimension()) + self.assertEqual(3, rec_xmin_and_xmax.dimension()) # ----------------------------------------------------------------------- # Second-Order Cone (SOC) @@ -264,6 +286,10 @@ def test_second_order_cone_convex(self): def test_second_order_cone_convex(self): soc = og.constraints.SecondOrderCone() self.assertFalse(soc.is_compact()) + + def test_soc_dimension(self): + soc = og.constraints.SecondOrderCone() + self.assertIsNone(soc.dimension()) # ----------------------------------------------------------------------- # No Constraints @@ -282,6 +308,10 @@ def test_no_constraints_convex(self): def test_no_constraints_compact(self): whole_rn = og.constraints.NoConstraints() self.assertFalse(whole_rn.is_compact()) + + def test_no_constraints_dimension(self): + whole_rn = og.constraints.NoConstraints() + self.assertIsNone(whole_rn.dimension()) # ----------------------------------------------------------------------- # Cartesian product of constraints @@ -377,6 +407,18 @@ def test_cartesian_convex(self): [5, 10, 11], [ball_inf, ball_eucl, free]) self.assertFalse(cartesian.is_compact()) + def test_cartesian_dimension(self): + inf = float('inf') + ball_inf = og.constraints.BallInf(None, 1) + ball_eucl = og.constraints.Ball2(None, 1) + rect = og.constraints.Rectangle(xmin=[0.0, 1.0, -inf, 2.0], + xmax=[1.0, inf, 10.0, 10.0]) + cartesian = og.constraints.CartesianProduct( + [1, 4, 8], [ball_inf, ball_eucl, rect]) + self.assertEqual(9, cartesian.dimension()) + + + # ----------------------------------------------------------------------- # Finite Set # ----------------------------------------------------------------------- @@ -476,6 +518,10 @@ def test_simplex_projection_random_optimality(self): self.assertLessEqual( np.dot(x-x_star, z-x_star), 1e-10, "Simplex optimality conditions failed") + def test_simplex_dimension(self): + simplex = og.constraints.Simplex(alpha=1.0) + self.assertIsNone(simplex.dimension()) + # ----------------------------------------------------------------------- # Ball1 # ----------------------------------------------------------------------- @@ -531,6 +577,13 @@ 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)") + def test_dimension_ball1(self): + ball = og.constraints.Ball1() + self.assertIsNone(ball.dimension()) + + ball = og.constraints.Ball1(center=[1., 2., -3.]) + self.assertEqual(3, ball.dimension()) + # ----------------------------------------------------------------------- # Sphere2 # ----------------------------------------------------------------------- @@ -555,8 +608,23 @@ def test_sphere2_no_center(self): sphere = og.constraints.Sphere2(radius=0.5) u = [0, 0, 0, 0] dist = sphere.distance_squared(u) - self.assertAlmostEqual(0.25, dist, places=12) + self.assertAlmostEqual(0.25, dist, places=12) + + def test_dimension_sphere2(self): + sphere = og.constraints.Sphere2() + self.assertIsNone(sphere.dimension()) + + sphere = og.constraints.Sphere2(center=[1., 2., -3.]) + self.assertEqual(3, sphere.dimension()) + + + # ----------------------------------------------------------------------- + # Zero + # ----------------------------------------------------------------------- + def test_zero_dimension(self): + z = og.opengen.constraints.Zero() + self.assertIsNone(z.dimension()) if __name__ == '__main__': unittest.main() From 38f7049c59ec6418ec8307d71efd2d949289ea1b Mon Sep 17 00:00:00 2001 From: Pantelis Sopasakis Date: Mon, 30 Mar 2026 18:33:14 +0100 Subject: [PATCH 3/7] catch inconsistent inputs - checking if u and U are consistent - solve issue with __init__ making code completionts impossible --- python/opengen/__init__.py | 26 +++++++++++++++------ python/opengen/builder/optimizer_builder.py | 9 +++++++ python/test/test_constraints.py | 2 +- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/python/opengen/__init__.py b/python/opengen/__init__.py index d7cedb79..58cb9a85 100644 --- a/python/opengen/__init__.py +++ b/python/opengen/__init__.py @@ -1,7 +1,19 @@ -import opengen.definitions -import opengen.builder -import opengen.config -import opengen.functions -import opengen.constraints -import opengen.tcp -import opengen.ocp +from . import ( + definitions, + builder, + config, + functions, + constraints, + tcp, + ocp, +) + +__all__ = [ + "definitions", + "builder", + "config", + "functions", + "constraints", + "tcp", + "ocp", +] diff --git a/python/opengen/builder/optimizer_builder.py b/python/opengen/builder/optimizer_builder.py index 51500422..841d3662 100644 --- a/python/opengen/builder/optimizer_builder.py +++ b/python/opengen/builder/optimizer_builder.py @@ -645,6 +645,15 @@ def __initialize(self): def __check_user_provided_parameters(self): self.__logger.info("Checking user parameters") + + # Check constraints dimensions + dim_constraints = self.__problem.constraints.dimension() + dim_decision_variables = self.__problem.dim_decision_variables() + if dim_constraints is not None and dim_decision_variables != dim_constraints: + raise ValueError(f"Inconsistent dimensions - decision variables: {dim_decision_variables}", + f"set of constraints: {dim_constraints}") + + # Preconditioning... if self.__solver_config.preconditioning: # Preconditioning is not allowed when we have general ALM-type constraints of the form # F1(u, p) in C, unless C is {0} or an orthant (special case of rectangle). diff --git a/python/test/test_constraints.py b/python/test/test_constraints.py index 53135eaa..fa1fcc87 100644 --- a/python/test/test_constraints.py +++ b/python/test/test_constraints.py @@ -622,7 +622,7 @@ def test_dimension_sphere2(self): # Zero # ----------------------------------------------------------------------- - def test_zero_dimension(self): + def test_zero_dimension(self): z = og.opengen.constraints.Zero() self.assertIsNone(z.dimension()) From 1bcde5894ffd9bc48414bff416f9780f51e80c3e Mon Sep 17 00:00:00 2001 From: Pantelis Sopasakis Date: Mon, 30 Mar 2026 18:37:14 +0100 Subject: [PATCH 4/7] overhaul of __init__.py --- python/opengen/builder/__init__.py | 15 +++++-- python/opengen/builder/optimizer_builder.py | 11 ++--- python/opengen/config/__init__.py | 20 ++++++--- python/opengen/constraints/__init__.py | 45 ++++++++++++++------- python/opengen/functions/__init__.py | 30 +++++++++----- python/opengen/tcp/__init__.py | 15 +++++-- python/test/__init__.py | 3 ++ 7 files changed, 99 insertions(+), 40 deletions(-) diff --git a/python/opengen/builder/__init__.py b/python/opengen/builder/__init__.py index ceb1edb6..7f127ba1 100644 --- a/python/opengen/builder/__init__.py +++ b/python/opengen/builder/__init__.py @@ -1,3 +1,12 @@ -from .optimizer_builder import * -from .problem import * -from .set_y_calculator import * +from .optimizer_builder import OpEnOptimizerBuilder +from .problem import Problem +from .set_y_calculator import SetYCalculator +from .ros_builder import RosBuilder, ROS2Builder + +__all__ = [ + "OpEnOptimizerBuilder", + "Problem", + "SetYCalculator", + "RosBuilder", + "ROS2Builder", +] diff --git a/python/opengen/builder/optimizer_builder.py b/python/opengen/builder/optimizer_builder.py index 841d3662..4993764d 100644 --- a/python/opengen/builder/optimizer_builder.py +++ b/python/opengen/builder/optimizer_builder.py @@ -2,6 +2,7 @@ import shutil import yaml +import opengen as og import opengen.config as og_cfg import opengen.definitions as og_dfn import opengen.constraints as og_cstr @@ -43,10 +44,10 @@ class OpEnOptimizerBuilder: """ def __init__(self, - problem, - metadata=og_cfg.OptimizerMeta(), - build_configuration=og_cfg.BuildConfiguration(), - solver_configuration=og_cfg.SolverConfiguration()): + problem: og.builder.Problem, + metadata: og_cfg.OptimizerMeta =og_cfg.OptimizerMeta(), + build_configuration: og_cfg.BuildConfiguration =og_cfg.BuildConfiguration(), + solver_configuration: og_cfg.SolverConfiguration=og_cfg.SolverConfiguration()): """Constructor of OpEnOptimizerBuilder :param problem: instance of :class:`~opengen.builder.problem.Problem` @@ -645,7 +646,7 @@ def __initialize(self): def __check_user_provided_parameters(self): self.__logger.info("Checking user parameters") - + # Check constraints dimensions dim_constraints = self.__problem.constraints.dimension() dim_decision_variables = self.__problem.dim_decision_variables() diff --git a/python/opengen/config/__init__.py b/python/opengen/config/__init__.py index 3b81ad6b..74b924eb 100644 --- a/python/opengen/config/__init__.py +++ b/python/opengen/config/__init__.py @@ -1,5 +1,15 @@ -from .meta import * -from .solver_config import * -from .build_config import * -from .tcp_server_config import * -from .ros_config import * +from .meta import OptimizerMeta, SEMVER_PATTERN +from .solver_config import SolverConfiguration +from .build_config import BuildConfiguration, RustAllocator +from .tcp_server_config import TcpServerConfiguration +from .ros_config import RosConfiguration + +__all__ = [ + "OptimizerMeta", + "SEMVER_PATTERN", + "SolverConfiguration", + "BuildConfiguration", + "RustAllocator", + "TcpServerConfiguration", + "RosConfiguration", +] diff --git a/python/opengen/constraints/__init__.py b/python/opengen/constraints/__init__.py index e361c496..ce529003 100644 --- a/python/opengen/constraints/__init__.py +++ b/python/opengen/constraints/__init__.py @@ -1,14 +1,31 @@ -from .ball1 import * -from .ball2 import * -from .sphere2 import * -from .rectangle import * -from .constraint import * -from .ball_inf import * -from .soc import * -from .no_constraints import * -from .cartesian import * -from .zero import * -from .finite_set import * -from .halfspace import * -from .simplex import * -from .affine_space import * +from .ball1 import Ball1 +from .ball2 import Ball2 +from .sphere2 import Sphere2 +from .rectangle import Rectangle +from .constraint import Constraint +from .ball_inf import BallInf +from .soc import SecondOrderCone +from .no_constraints import NoConstraints +from .cartesian import CartesianProduct +from .zero import Zero +from .finite_set import FiniteSet +from .halfspace import Halfspace +from .simplex import Simplex +from .affine_space import AffineSpace + +__all__ = [ + "Ball1", + "Ball2", + "Sphere2", + "Rectangle", + "Constraint", + "BallInf", + "SecondOrderCone", + "NoConstraints", + "CartesianProduct", + "Zero", + "FiniteSet", + "Halfspace", + "Simplex", + "AffineSpace", +] diff --git a/python/opengen/functions/__init__.py b/python/opengen/functions/__init__.py index 2f8b3339..11508d8b 100644 --- a/python/opengen/functions/__init__.py +++ b/python/opengen/functions/__init__.py @@ -1,9 +1,21 @@ -from .rosenbrock import * -from .fmin import * -from .fmax import * -from .is_symbolic import * -from .is_numeric import * -from .sign import * -from .norm2 import * -from .fabs import * -from .norm2_squared import * +from .rosenbrock import rosenbrock +from .fmin import fmin +from .fmax import fmax +from .is_symbolic import is_symbolic +from .is_numeric import is_numeric +from .sign import sign +from .norm2 import norm2 +from .fabs import fabs +from .norm2_squared import norm2_squared + +__all__ = [ + "rosenbrock", + "fmin", + "fmax", + "is_symbolic", + "is_numeric", + "sign", + "norm2", + "fabs", + "norm2_squared", +] diff --git a/python/opengen/tcp/__init__.py b/python/opengen/tcp/__init__.py index 49783d98..17d36335 100644 --- a/python/opengen/tcp/__init__.py +++ b/python/opengen/tcp/__init__.py @@ -1,4 +1,11 @@ -from .optimizer_tcp_manager import * -from .solver_status import * -from .solver_error import * -from .solver_response import * +from .optimizer_tcp_manager import OptimizerTcpManager +from .solver_status import SolverStatus +from .solver_error import SolverError +from .solver_response import SolverResponse + +__all__ = [ + "OptimizerTcpManager", + "SolverStatus", + "SolverError", + "SolverResponse", +] diff --git a/python/test/__init__.py b/python/test/__init__.py index e69de29b..fbd4941c 100644 --- a/python/test/__init__.py +++ b/python/test/__init__.py @@ -0,0 +1,3 @@ +"""Test package for the project.""" + +__all__ = [] From 9feab7d072f6b49a634dbfa4463838a7d2b2a3d3 Mon Sep 17 00:00:00 2001 From: Pantelis Sopasakis Date: Mon, 30 Mar 2026 18:46:38 +0100 Subject: [PATCH 5/7] lazy imports --- python/opengen/__init__.py | 43 ++++++++++++++++----- python/opengen/builder/optimizer_builder.py | 2 + python/test/test_constraints.py | 2 +- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/python/opengen/__init__.py b/python/opengen/__init__.py index 58cb9a85..daf6cf89 100644 --- a/python/opengen/__init__.py +++ b/python/opengen/__init__.py @@ -1,12 +1,16 @@ -from . import ( - definitions, - builder, - config, - functions, - constraints, - tcp, - ocp, -) +"""Top-level package for OpEn with lazy submodule imports. + +This module defers importing heavy subpackages to attribute access +to avoid circular import problems during package initialization. + +Lazy submodule imports defer the loading of Python modules and their +attributes until they are first accessed, reducing startup time and +memory usage. This is achieved using PEP 562 (__getattr__ and __dir__) +to intercept attribute access and load the underlying code only when +necessary. +""" + +from importlib import import_module __all__ = [ "definitions", @@ -17,3 +21,24 @@ "tcp", "ocp", ] + + +def __getattr__(name): + """Lazily import submodules on attribute access. + + Example: accessing ``opengen.builder`` will import + ``opengen.builder`` and cache it on the package module. + + This defers importing heavy subpackages until they're actually used + (lazy imports), reducing startup cost and helping avoid import-time + circular dependencies. + """ + if name in __all__: + module = import_module(f"{__name__}.{name}") + globals()[name] = module + return module + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +def __dir__(): + return sorted(list(__all__) + list(globals().keys())) diff --git a/python/opengen/builder/optimizer_builder.py b/python/opengen/builder/optimizer_builder.py index 4993764d..d4e2c2b8 100644 --- a/python/opengen/builder/optimizer_builder.py +++ b/python/opengen/builder/optimizer_builder.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import subprocess import shutil import yaml diff --git a/python/test/test_constraints.py b/python/test/test_constraints.py index fa1fcc87..2f58acc1 100644 --- a/python/test/test_constraints.py +++ b/python/test/test_constraints.py @@ -623,7 +623,7 @@ def test_dimension_sphere2(self): # ----------------------------------------------------------------------- def test_zero_dimension(self): - z = og.opengen.constraints.Zero() + z = og.constraints.Zero() self.assertIsNone(z.dimension()) if __name__ == '__main__': From 4165e33ae7c94746c94943f65d7c8164ee956133 Mon Sep 17 00:00:00 2001 From: Pantelis Sopasakis Date: Mon, 30 Mar 2026 18:51:35 +0100 Subject: [PATCH 6/7] update CHANGELOG --- python/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/CHANGELOG.md b/python/CHANGELOG.md index ab870a1d..cbda879e 100644 --- a/python/CHANGELOG.md +++ b/python/CHANGELOG.md @@ -37,6 +37,8 @@ Note: This is the Changelog file of `opengen` - the Python interface of OpEn - ROS2 generated packages now use `uint16` for `error_code`, matching the current positive error-code range while keeping the wire format compact - MATLAB-related Python docs now reference the new `python/` package layout instead of the removed `open-codegen/` path - ROS2 integration tests now clean stale nested `colcon` build artifacts and preserve the active shell environment more reliably in micromamba/conda setups +- Fixed issues in `__init__.py`: lazy imports and discoverability +- Now checking whether the constraints have the correct dimension before attempting to build an optimizer ## [0.10.1] - 2026-03-25 From 618ce38267cf2e6e0b3bc1e4a05fb4a907e54aac Mon Sep 17 00:00:00 2001 From: Pantelis Sopasakis Date: Mon, 30 Mar 2026 19:04:21 +0100 Subject: [PATCH 7/7] testing --- python/test/test.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/python/test/test.py b/python/test/test.py index 229a0917..4122e55b 100644 --- a/python/test/test.py +++ b/python/test/test.py @@ -68,7 +68,25 @@ def test_with_open_version_rejects_invalid_version(self): "invalid OpEn version", ): build_config.with_open_version("^1.2") + + +class SmokeTests(unittest.TestCase): + def test_incompatible_constraints_dimensions(self): + u = cs.SX.sym("u", 5) + p = cs.SX.sym("p", 2) + phi = og.functions.rosenbrock(u, p) + bounds = og.constraints.Rectangle(xmin=[1, 2], xmax=[3, 4]) + problem = og.builder.Problem(u, p, phi) \ + .with_constraints(bounds) + meta = og.config.OptimizerMeta().with_optimizer_name("abcd") + build_config = og.config.BuildConfiguration() \ + .with_open_version(local_path=os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "rust"))) \ + .with_build_directory(".whatever") + solver_cfg = og.config.SolverConfiguration() + builder = og.builder.OpEnOptimizerBuilder(problem, meta, build_config, solver_cfg) + with self.assertRaises(ValueError): + builder.build() class OcpSolutionTestCase(unittest.TestCase):