Coverage for src / min_circle / msk.py: 94%
17 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-26 19:42 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-26 19:42 +0000
1"""MOSEK implementation for finding the minimum enclosing circle.
3This module provides functions to find the minimum enclosing circle
4using the MOSEK Fusion optimization library.
6Note:
7 The MOSEK dependency is optional. Importing this module no longer requires
8 MOSEK to be installed. The import happens lazily inside the solver function.
9"""
11from typing import Any
13import numpy as np
15from .utils.circle import Circle
17_MOSEK_IMPORT_ERROR = (
18 "MOSEK is required for min_circle_mosek(). Install with `pip install 'min_circle[solvers]'` "
19 "or install the `mosek` package manually."
20)
23def min_circle_mosek(points: np.ndarray, **kwargs: Any) -> Circle:
24 """Find the minimum enclosing circle using MOSEK Fusion.
26 Uses the MOSEK Fusion API to formulate and solve the minimum enclosing circle
27 problem as a second-order cone program (SOCP).
29 Args:
30 points: Array of 2D points with shape (n, 2)
31 **kwargs: Additional keyword arguments to pass to the solver
33 Returns:
34 Circle object containing the center and radius of the minimum enclosing circle
36 Raises:
37 ImportError: If the `mosek` package is not installed. Install with
38 `pip install 'min_circle[solvers]'` or add the `solvers` extra.
40 Notes:
41 Implementation based on MOSEK's minimum ellipsoid tutorial:
42 https://github.com/MOSEK/Tutorials/blob/master/minimum-ellipsoid/minimum-ellipsoid.ipynb
43 """
44 try:
45 import mosek.fusion as mf
46 except Exception as exc: # pragma: no cover - only hit when MOSEK is missing
47 raise ImportError(_MOSEK_IMPORT_ERROR) from exc
49 with mf.Model() as model:
50 # Create variables for radius and center
51 r = model.variable("Radius", 1)
52 x = model.variable("Midpoint", [1, points.shape[1]])
54 # Number of points
55 k = points.shape[0]
57 # Repeat the radius and center variables for each point
58 r0 = mf.Var.repeat(r, k)
59 x0 = mf.Var.repeat(x, k)
61 # Create second-order cone constraints ensuring all points are within the circle
62 model.constraint(mf.Expr.hstack(r0, mf.Expr.sub(x0, points)), mf.Domain.inQCone())
64 # Set the objective to minimize the radius
65 model.objective("obj", mf.ObjectiveSense.Minimize, r)
67 # Solve the optimization problem
68 model.solve(**kwargs)
70 # Return the circle with the optimal center and radius
71 return Circle(radius=r.level(), center=x.level())