Skip to content

Commit d142914

Browse files
authored
Merge pull request #421 from alphaville/fix/420-constraint-dimension
Constraint dimension checking and lazy imports
2 parents ecb9bed + 618ce38 commit d142914

24 files changed

Lines changed: 296 additions & 53 deletions

python/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ Note: This is the Changelog file of `opengen` - the Python interface of OpEn
3737
- ROS2 generated packages now use `uint16` for `error_code`, matching the current positive error-code range while keeping the wire format compact
3838
- MATLAB-related Python docs now reference the new `python/` package layout instead of the removed `open-codegen/` path
3939
- ROS2 integration tests now clean stale nested `colcon` build artifacts and preserve the active shell environment more reliably in micromamba/conda setups
40+
- Fixed issues in `__init__.py`: lazy imports and discoverability
41+
- Now checking whether the constraints have the correct dimension before attempting to build an optimizer
4042

4143

4244
## [0.10.1] - 2026-03-25

python/opengen/__init__.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,44 @@
1-
import opengen.definitions
2-
import opengen.builder
3-
import opengen.config
4-
import opengen.functions
5-
import opengen.constraints
6-
import opengen.tcp
7-
import opengen.ocp
1+
"""Top-level package for OpEn with lazy submodule imports.
2+
3+
This module defers importing heavy subpackages to attribute access
4+
to avoid circular import problems during package initialization.
5+
6+
Lazy submodule imports defer the loading of Python modules and their
7+
attributes until they are first accessed, reducing startup time and
8+
memory usage. This is achieved using PEP 562 (__getattr__ and __dir__)
9+
to intercept attribute access and load the underlying code only when
10+
necessary.
11+
"""
12+
13+
from importlib import import_module
14+
15+
__all__ = [
16+
"definitions",
17+
"builder",
18+
"config",
19+
"functions",
20+
"constraints",
21+
"tcp",
22+
"ocp",
23+
]
24+
25+
26+
def __getattr__(name):
27+
"""Lazily import submodules on attribute access.
28+
29+
Example: accessing ``opengen.builder`` will import
30+
``opengen.builder`` and cache it on the package module.
31+
32+
This defers importing heavy subpackages until they're actually used
33+
(lazy imports), reducing startup cost and helping avoid import-time
34+
circular dependencies.
35+
"""
36+
if name in __all__:
37+
module = import_module(f"{__name__}.{name}")
38+
globals()[name] = module
39+
return module
40+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
41+
42+
43+
def __dir__():
44+
return sorted(list(__all__) + list(globals().keys()))

python/opengen/builder/__init__.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1-
from .optimizer_builder import *
2-
from .problem import *
3-
from .set_y_calculator import *
1+
from .optimizer_builder import OpEnOptimizerBuilder
2+
from .problem import Problem
3+
from .set_y_calculator import SetYCalculator
4+
from .ros_builder import RosBuilder, ROS2Builder
5+
6+
__all__ = [
7+
"OpEnOptimizerBuilder",
8+
"Problem",
9+
"SetYCalculator",
10+
"RosBuilder",
11+
"ROS2Builder",
12+
]

python/opengen/builder/optimizer_builder.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
from __future__ import annotations
2+
13
import subprocess
24
import shutil
35
import yaml
46

7+
import opengen as og
58
import opengen.config as og_cfg
69
import opengen.definitions as og_dfn
710
import opengen.constraints as og_cstr
@@ -43,10 +46,10 @@ class OpEnOptimizerBuilder:
4346
"""
4447

4548
def __init__(self,
46-
problem,
47-
metadata=og_cfg.OptimizerMeta(),
48-
build_configuration=og_cfg.BuildConfiguration(),
49-
solver_configuration=og_cfg.SolverConfiguration()):
49+
problem: og.builder.Problem,
50+
metadata: og_cfg.OptimizerMeta =og_cfg.OptimizerMeta(),
51+
build_configuration: og_cfg.BuildConfiguration =og_cfg.BuildConfiguration(),
52+
solver_configuration: og_cfg.SolverConfiguration=og_cfg.SolverConfiguration()):
5053
"""Constructor of OpEnOptimizerBuilder
5154
5255
:param problem: instance of :class:`~opengen.builder.problem.Problem`
@@ -645,6 +648,15 @@ def __initialize(self):
645648

646649
def __check_user_provided_parameters(self):
647650
self.__logger.info("Checking user parameters")
651+
652+
# Check constraints dimensions
653+
dim_constraints = self.__problem.constraints.dimension()
654+
dim_decision_variables = self.__problem.dim_decision_variables()
655+
if dim_constraints is not None and dim_decision_variables != dim_constraints:
656+
raise ValueError(f"Inconsistent dimensions - decision variables: {dim_decision_variables}",
657+
f"set of constraints: {dim_constraints}")
658+
659+
# Preconditioning...
648660
if self.__solver_config.preconditioning:
649661
# Preconditioning is not allowed when we have general ALM-type constraints of the form
650662
# F1(u, p) in C, unless C is {0} or an orthant (special case of rectangle).

python/opengen/config/__init__.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1-
from .meta import *
2-
from .solver_config import *
3-
from .build_config import *
4-
from .tcp_server_config import *
5-
from .ros_config import *
1+
from .meta import OptimizerMeta, SEMVER_PATTERN
2+
from .solver_config import SolverConfiguration
3+
from .build_config import BuildConfiguration, RustAllocator
4+
from .tcp_server_config import TcpServerConfiguration
5+
from .ros_config import RosConfiguration
6+
7+
__all__ = [
8+
"OptimizerMeta",
9+
"SEMVER_PATTERN",
10+
"SolverConfiguration",
11+
"BuildConfiguration",
12+
"RustAllocator",
13+
"TcpServerConfiguration",
14+
"RosConfiguration",
15+
]
Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
1-
from .ball1 import *
2-
from .ball2 import *
3-
from .sphere2 import *
4-
from .rectangle import *
5-
from .constraint import *
6-
from .ball_inf import *
7-
from .soc import *
8-
from .no_constraints import *
9-
from .cartesian import *
10-
from .zero import *
11-
from .finite_set import *
12-
from .halfspace import *
13-
from .simplex import *
14-
from .affine_space import *
1+
from .ball1 import Ball1
2+
from .ball2 import Ball2
3+
from .sphere2 import Sphere2
4+
from .rectangle import Rectangle
5+
from .constraint import Constraint
6+
from .ball_inf import BallInf
7+
from .soc import SecondOrderCone
8+
from .no_constraints import NoConstraints
9+
from .cartesian import CartesianProduct
10+
from .zero import Zero
11+
from .finite_set import FiniteSet
12+
from .halfspace import Halfspace
13+
from .simplex import Simplex
14+
from .affine_space import AffineSpace
15+
16+
__all__ = [
17+
"Ball1",
18+
"Ball2",
19+
"Sphere2",
20+
"Rectangle",
21+
"Constraint",
22+
"BallInf",
23+
"SecondOrderCone",
24+
"NoConstraints",
25+
"CartesianProduct",
26+
"Zero",
27+
"FiniteSet",
28+
"Halfspace",
29+
"Simplex",
30+
"AffineSpace",
31+
]

python/opengen/constraints/affine_space.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,6 @@ def is_compact(self):
5454
"""Affine spaces are not compact sets
5555
"""
5656
return False
57+
58+
def dimension(self):
59+
return super().dimension()

python/opengen/constraints/ball1.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,8 @@ def is_convex(self):
7373

7474
def is_compact(self):
7575
return True
76+
77+
def dimension(self):
78+
if self.center is None:
79+
return None
80+
return len(self.center)

python/opengen/constraints/ball2.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class Ball2(Constraint):
88
"""A Euclidean ball constraint
99
10-
A constraint of the form :math:`\|u-u_0\| \leq r`, where :math:`u_0` is the center
10+
A constraint of the form :math:`\Vert u-u_0 \Vert \leq r`, where :math:`u_0` is the center
1111
of the ball and `r` is its radius
1212
1313
"""
@@ -91,3 +91,8 @@ def is_convex(self):
9191

9292
def is_compact(self):
9393
return True
94+
95+
def dimension(self):
96+
if self.center is None:
97+
return None
98+
return len(self.center)

python/opengen/constraints/ball_inf.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,8 @@ def is_convex(self):
8888

8989
def is_compact(self):
9090
return True
91+
92+
def dimension(self):
93+
if self.center is None:
94+
return None
95+
return len(self.center)

0 commit comments

Comments
 (0)