Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 44 additions & 7 deletions python/opengen/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,44 @@
import opengen.definitions
import opengen.builder
import opengen.config
import opengen.functions
import opengen.constraints
import opengen.tcp
import opengen.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",
"builder",
"config",
"functions",
"constraints",
"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()))
15 changes: 12 additions & 3 deletions python/opengen/builder/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
20 changes: 16 additions & 4 deletions python/opengen/builder/optimizer_builder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from __future__ import annotations

import subprocess
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
Expand Down Expand Up @@ -43,10 +46,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`
Expand Down Expand Up @@ -645,6 +648,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).
Expand Down
20 changes: 15 additions & 5 deletions python/opengen/config/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
45 changes: 31 additions & 14 deletions python/opengen/constraints/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
3 changes: 3 additions & 0 deletions python/opengen/constraints/affine_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,6 @@ def is_compact(self):
"""Affine spaces are not compact sets
"""
return False

def dimension(self):
return super().dimension()
5 changes: 5 additions & 0 deletions python/opengen/constraints/ball1.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,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)
7 changes: 6 additions & 1 deletion python/opengen/constraints/ball2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

"""
Expand Down Expand Up @@ -91,3 +91,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)
5 changes: 5 additions & 0 deletions python/opengen/constraints/ball_inf.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,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)
3 changes: 3 additions & 0 deletions python/opengen/constraints/cartesian.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,6 @@ def is_compact(self):
if not set_i.is_compact():
return False
return True

def dimension(self):
return self.segments[-1] + 1
9 changes: 9 additions & 0 deletions python/opengen/constraints/constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
2 changes: 1 addition & 1 deletion python/opengen/constraints/finite_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
3 changes: 3 additions & 0 deletions python/opengen/constraints/no_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ def is_convex(self):

def is_compact(self):
return False

def dimension(self):
return None
2 changes: 1 addition & 1 deletion python/opengen/constraints/rectangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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``)
Expand Down
3 changes: 3 additions & 0 deletions python/opengen/constraints/simplex.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,6 @@ def is_convex(self):
def is_compact(self):
"""Whether the set is compact (`True`)"""
return True

def dimension(self):
return None
3 changes: 3 additions & 0 deletions python/opengen/constraints/soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,6 @@ def is_convex(self):

def is_compact(self):
return False

def dimension(self):
return None
5 changes: 5 additions & 0 deletions python/opengen/constraints/sphere2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
8 changes: 6 additions & 2 deletions python/opengen/constraints/zero.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -28,3 +29,6 @@ def is_convex(self):

def is_compact(self):
return True

def dimension(self):
return None
30 changes: 21 additions & 9 deletions python/opengen/functions/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
15 changes: 11 additions & 4 deletions python/opengen/tcp/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
3 changes: 3 additions & 0 deletions python/test/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Test package for the project."""

__all__ = []
Loading
Loading