Skip to content

Proxable functions

Proxable functions are convex functions whose proximal operators can be used as steps, each supplying a numerical prox and the encoding needed by the verification problem. Use vp.prox(function, arg, gamma=1.0); support varies by backend, and unsupported operations raise explicitly. They all derive from the abstract base class below.

ProxableFunction

Abstract base class for proxable functions.

A function \(f : \mathbb{R}^n \to \mathbb{R} \cup \{+\infty\}\) is proxable if its proximal operator

\[ \operatorname{prox}_{\gamma f}(x) = \arg\!\min_{u} \left\{ f(u) + \frac{1}{2\gamma} \|u - x\|^2 \right\} \]

can be evaluated efficiently (in closed form or via a structured subproblem).

Subclasses provide the numerical interface (eval, prox, grad). The mapping from a proxable function to one or more steps lives in the step decomposer's _PROX_DECOMPOSERS registry — it is not a method on this class.

Source code in fomtools/proxable_functions/proximal_operator.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
class ProxableFunction:
    r"""
    Abstract base class for proxable functions.

    A function $f : \mathbb{R}^n \to \mathbb{R} \cup \{+\infty\}$ is proxable
    if its proximal operator

    $$
        \operatorname{prox}_{\gamma f}(x) = \arg\!\min_{u} \left\{ f(u)
        + \frac{1}{2\gamma} \|u - x\|^2 \right\}
    $$

    can be evaluated efficiently (in closed form or via a structured subproblem).

    Subclasses provide the numerical interface (``eval``, ``prox``, ``grad``).
    The mapping from a proxable function to one or more steps lives in the
    step decomposer's ``_PROX_DECOMPOSERS`` registry — it is *not* a method
    on this class.
    """

    def __init__(self):
        pass

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.

        Raises
        ------
        NotImplementedError
            Raised by the base class; must be implemented by subclasses.
        """
        raise NotImplementedError

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute the proximal point of the function.

        Solves

        $$
            \operatorname{prox}_{\gamma f}(\text{arg}) = \arg\!\min_{u} \left\{
            f(u) + \frac{1}{2\gamma} \|u - \text{arg}\|^2 \right\}.
        $$

        Parameters
        ----------
        arg : np.ndarray
            Input point at which to evaluate the proximal operator.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Raises
        ------
        NotImplementedError
            Raised by the base class; must be implemented by subclasses.
        """
        raise NotImplementedError

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute the gradient (or a subgradient) of the function at ``x``.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the (sub)gradient.

        Returns
        -------
        np.ndarray
            Gradient $\nabla f(x)$ or a subgradient when $f$ is non-smooth.

        Raises
        ------
        NotImplementedError
            Raised by the base class; must be implemented by subclasses.
        """
        raise NotImplementedError

eval

eval(x: ndarray) -> float

Evaluate \(f(x)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Raises:

Type Description
NotImplementedError

Raised by the base class; must be implemented by subclasses.

Source code in fomtools/proxable_functions/proximal_operator.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.

    Raises
    ------
    NotImplementedError
        Raised by the base class; must be implemented by subclasses.
    """
    raise NotImplementedError

grad

grad(x: ndarray) -> ndarray

Compute the gradient (or a subgradient) of the function at x.

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the (sub)gradient.

required

Returns:

Type Description
ndarray

Gradient \(\nabla f(x)\) or a subgradient when \(f\) is non-smooth.

Raises:

Type Description
NotImplementedError

Raised by the base class; must be implemented by subclasses.

Source code in fomtools/proxable_functions/proximal_operator.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute the gradient (or a subgradient) of the function at ``x``.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the (sub)gradient.

    Returns
    -------
    np.ndarray
        Gradient $\nabla f(x)$ or a subgradient when $f$ is non-smooth.

    Raises
    ------
    NotImplementedError
        Raised by the base class; must be implemented by subclasses.
    """
    raise NotImplementedError

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute the proximal point of the function.

Solves

\[ \operatorname{prox}_{\gamma f}(\text{arg}) = \arg\!\min_{u} \left\{ f(u) + \frac{1}{2\gamma} \|u - \text{arg}\|^2 \right\}. \]

Parameters:

Name Type Description Default
arg ndarray

Input point at which to evaluate the proximal operator.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Raises:

Type Description
NotImplementedError

Raised by the base class; must be implemented by subclasses.

Source code in fomtools/proxable_functions/proximal_operator.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute the proximal point of the function.

    Solves

    $$
        \operatorname{prox}_{\gamma f}(\text{arg}) = \arg\!\min_{u} \left\{
        f(u) + \frac{1}{2\gamma} \|u - \text{arg}\|^2 \right\}.
    $$

    Parameters
    ----------
    arg : np.ndarray
        Input point at which to evaluate the proximal operator.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Raises
    ------
    NotImplementedError
        Raised by the base class; must be implemented by subclasses.
    """
    raise NotImplementedError

ElasticNet

Bases: ProxableFunction

Elastic-net regularizer.

Represents

\[ f(x) = \alpha \|x\|_1 + \beta \|x\|_2^2. \]

Convex; strongly convex when \(\beta > 0\). Note that the \(\ell_2\) term has no leading \(1/2\) factor: \(\nabla(\beta \|x\|_2^2) = 2\beta x\).

Parameters:

Name Type Description Default
alpha float

Weight \(\alpha \ge 0\) of the \(\ell_1\) term.

required
beta float

Weight \(\beta \ge 0\) of the squared \(\ell_2\) term.

required

Raises:

Type Description
ValueError

If alpha or beta is negative.

See Also

NormL1 : The case \(\beta = 0\), identical to NormL1(lambd=alpha).

Notes

Proximal operator.

\[ \operatorname{prox}_{\gamma f}(v) = \frac{S_{\alpha\gamma}(v)}{1 + 2\beta\gamma}, \]

where \(S_\lambda(w) = \operatorname{sign}(w)\,\max(|w| - \lambda, 0)\) is element-wise soft thresholding. For \(\alpha = 0\) it reduces to the ridge shrinkage \(v / (1 + 2\beta\gamma)\).

Verification lowering. prox_elasticnet posts a single SoftThreshold step with threshold \(\alpha\gamma\) and scales its output by \(1 / (1 + 2\beta\gamma)\).

Source code in fomtools/proxable_functions/elastic_net.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
class ElasticNet(ProxableFunction):
    r"""
    Elastic-net regularizer.

    Represents

    $$
        f(x) = \alpha \|x\|_1 + \beta \|x\|_2^2.
    $$

    Convex; strongly convex when $\beta > 0$. Note that the $\ell_2$ term has
    no leading $1/2$ factor: $\nabla(\beta \|x\|_2^2) = 2\beta x$.

    Parameters
    ----------
    alpha : float
        Weight $\alpha \ge 0$ of the $\ell_1$ term.
    beta : float
        Weight $\beta \ge 0$ of the squared $\ell_2$ term.

    Raises
    ------
    ValueError
        If ``alpha`` or ``beta`` is negative.

    See Also
    --------
    NormL1 : The case $\beta = 0$, identical to ``NormL1(lambd=alpha)``.

    Notes
    -----
    **Proximal operator.**

    $$
        \operatorname{prox}_{\gamma f}(v) =
            \frac{S_{\alpha\gamma}(v)}{1 + 2\beta\gamma},
    $$

    where $S_\lambda(w) = \operatorname{sign}(w)\,\max(|w| - \lambda, 0)$ is
    element-wise soft thresholding. For $\alpha = 0$ it reduces to the ridge
    shrinkage $v / (1 + 2\beta\gamma)$.

    **Verification lowering.** ``prox_elasticnet`` posts a single
    ``SoftThreshold`` step with threshold $\alpha\gamma$ and scales its output
    by $1 / (1 + 2\beta\gamma)$.
    """

    def __init__(self, alpha: float, beta: float):
        super().__init__()
        if alpha < 0 or beta < 0:
            raise ValueError("alpha and beta must be non-negative")
        self.alpha = alpha
        self.beta = beta

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \alpha\|x\|_1 + \beta\|x\|_2^2$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.
        """
        return float(self.alpha * np.sum(np.abs(x)) + self.beta * np.sum(x * x))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Uses the closed form

        $$
            \operatorname{prox}_{\gamma f}(\text{arg}) =
                \frac{S_{\alpha\gamma}(\text{arg})}{1 + 2\beta\gamma}.
        $$

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        thresh = self.alpha * gamma
        soft = np.sign(arg) * np.maximum(np.abs(arg) - thresh, 0.0)
        return soft / (1.0 + 2.0 * self.beta * gamma)

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute a subgradient of $f$ at ``x``.

        Per coordinate,

        $$
            g_i = 2\beta x_i + \alpha \begin{cases}
                1   & x_i > 0 \\
                0   & x_i = 0 \\
                -1  & x_i < 0,
            \end{cases}
        $$

        where $0 \in [-1, 1]$ is the canonical choice at the kink $x_i = 0$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the subgradient.

        Returns
        -------
        np.ndarray
            A subgradient $g \in \partial f(x)$.
        """
        return 2.0 * self.beta * x + self.alpha * np.sign(x)

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \alpha\|x\|_1 + \beta\|x\|_2^2\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Source code in fomtools/proxable_functions/elastic_net.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \alpha\|x\|_1 + \beta\|x\|_2^2$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.
    """
    return float(self.alpha * np.sum(np.abs(x)) + self.beta * np.sum(x * x))

grad

grad(x: ndarray) -> ndarray

Compute a subgradient of \(f\) at x.

Per coordinate,

\[ g_i = 2\beta x_i + \alpha \begin{cases} 1 & x_i > 0 \\ 0 & x_i = 0 \\ -1 & x_i < 0, \end{cases} \]

where \(0 \in [-1, 1]\) is the canonical choice at the kink \(x_i = 0\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the subgradient.

required

Returns:

Type Description
ndarray

A subgradient \(g \in \partial f(x)\).

Source code in fomtools/proxable_functions/elastic_net.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute a subgradient of $f$ at ``x``.

    Per coordinate,

    $$
        g_i = 2\beta x_i + \alpha \begin{cases}
            1   & x_i > 0 \\
            0   & x_i = 0 \\
            -1  & x_i < 0,
        \end{cases}
    $$

    where $0 \in [-1, 1]$ is the canonical choice at the kink $x_i = 0$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the subgradient.

    Returns
    -------
    np.ndarray
        A subgradient $g \in \partial f(x)$.
    """
    return 2.0 * self.beta * x + self.alpha * np.sign(x)

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Uses the closed form

\[ \operatorname{prox}_{\gamma f}(\text{arg}) = \frac{S_{\alpha\gamma}(\text{arg})}{1 + 2\beta\gamma}. \]

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/elastic_net.py
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Uses the closed form

    $$
        \operatorname{prox}_{\gamma f}(\text{arg}) =
            \frac{S_{\alpha\gamma}(\text{arg})}{1 + 2\beta\gamma}.
    $$

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    thresh = self.alpha * gamma
    soft = np.sign(arg) * np.maximum(np.abs(arg) - thresh, 0.0)
    return soft / (1.0 + 2.0 * self.beta * gamma)

HingeLoss

Bases: ProxableFunction

Hinge loss (unlabeled).

Represents

\[ f(x) = \sum_i \max(0, 1 - x_i). \]

Convex and piecewise linear.

See Also

LabeledHingeLoss : Labeled variant \(\sum_i \max(0, 1 - y_i x_i)\).

Notes

Proximal operator. Element-wise separable; per coordinate,

\[ \operatorname{prox}_{\gamma f}(v) = \begin{cases} v + \gamma & v < 1 - \gamma \\ 1 & 1 - \gamma \le v \le 1 \\ v & v > 1, \end{cases} \]

which collapses to \(\operatorname{prox}_{\gamma f}(v) = v + \mathrm{satlin}(1 - v;\, 0,\, \gamma)\).

Verification lowering. prox_hingeloss posts a single SaturatedLinear clip of the margin residual \(1 - v\) to \([0, \gamma]\) and adds it back to the input.

Source code in fomtools/proxable_functions/hinge_loss.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
class HingeLoss(ProxableFunction):
    r"""
    Hinge loss (unlabeled).

    Represents

    $$
        f(x) = \sum_i \max(0, 1 - x_i).
    $$

    Convex and piecewise linear.

    See Also
    --------
    LabeledHingeLoss : Labeled variant $\sum_i \max(0, 1 - y_i x_i)$.

    Notes
    -----
    **Proximal operator.** Element-wise separable; per coordinate,

    $$
        \operatorname{prox}_{\gamma f}(v) = \begin{cases}
            v + \gamma & v < 1 - \gamma \\
            1          & 1 - \gamma \le v \le 1 \\
            v          & v > 1,
        \end{cases}
    $$

    which collapses to
    $\operatorname{prox}_{\gamma f}(v) = v + \mathrm{satlin}(1 - v;\, 0,\, \gamma)$.

    **Verification lowering.** ``prox_hingeloss`` posts a single
    ``SaturatedLinear`` clip of the margin residual $1 - v$ to $[0, \gamma]$
    and adds it back to the input.
    """

    def __init__(self):
        super().__init__()

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \sum_i \max(0, 1 - x_i)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.
        """
        return float(np.sum(np.maximum(0.0, 1.0 - x)))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Uses the closed form

        $$
            \operatorname{prox}_{\gamma f}(\text{arg})
            = \text{arg} + \mathrm{satlin}(1 - \text{arg};\, 0,\, \gamma).
        $$

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        return arg + np.minimum(np.maximum(1.0 - arg, 0.0), gamma)

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute a subgradient of $f$ at ``x``.

        Per coordinate,

        $$
            g_i = \begin{cases}
                -1  & x_i < 1 \\
                0   & x_i \geq 1,
            \end{cases}
        $$

        where $0 \in [-1, 0]$ is the canonical choice at the kink $x_i = 1$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the subgradient.

        Returns
        -------
        np.ndarray
            A subgradient $g \in \partial f(x)$.
        """
        return -(x < 1).astype(float)

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \sum_i \max(0, 1 - x_i)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Source code in fomtools/proxable_functions/hinge_loss.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \sum_i \max(0, 1 - x_i)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.
    """
    return float(np.sum(np.maximum(0.0, 1.0 - x)))

grad

grad(x: ndarray) -> ndarray

Compute a subgradient of \(f\) at x.

Per coordinate,

\[ g_i = \begin{cases} -1 & x_i < 1 \\ 0 & x_i \geq 1, \end{cases} \]

where \(0 \in [-1, 0]\) is the canonical choice at the kink \(x_i = 1\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the subgradient.

required

Returns:

Type Description
ndarray

A subgradient \(g \in \partial f(x)\).

Source code in fomtools/proxable_functions/hinge_loss.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute a subgradient of $f$ at ``x``.

    Per coordinate,

    $$
        g_i = \begin{cases}
            -1  & x_i < 1 \\
            0   & x_i \geq 1,
        \end{cases}
    $$

    where $0 \in [-1, 0]$ is the canonical choice at the kink $x_i = 1$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the subgradient.

    Returns
    -------
    np.ndarray
        A subgradient $g \in \partial f(x)$.
    """
    return -(x < 1).astype(float)

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Uses the closed form

\[ \operatorname{prox}_{\gamma f}(\text{arg}) = \text{arg} + \mathrm{satlin}(1 - \text{arg};\, 0,\, \gamma). \]

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/hinge_loss.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Uses the closed form

    $$
        \operatorname{prox}_{\gamma f}(\text{arg})
        = \text{arg} + \mathrm{satlin}(1 - \text{arg};\, 0,\, \gamma).
    $$

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    return arg + np.minimum(np.maximum(1.0 - arg, 0.0), gamma)

HuberLoss

Bases: ProxableFunction

Element-wise Huber loss.

Represents

\[ f(x) = \sum_i h(x_i), \qquad h(t) = \begin{cases} \tfrac{1}{2} t^2 & |t| \leq \delta \\ \delta |t| - \tfrac{1}{2}\delta^2 & |t| > \delta. \end{cases} \]

Convex and \(C^1\). This is the standard element-wise robust loss, not the multivariate norm-based "vector Huber" function.

Parameters:

Name Type Description Default
delta float

Threshold \(\delta \ge 0\) between the quadratic and linear regimes.

required

Raises:

Type Description
ValueError

If delta is negative.

Notes

Proximal operator. Element-wise separable; per coordinate,

\[ \operatorname{prox}_{\gamma f}(v)_i = \begin{cases} \dfrac{v_i}{1+\gamma} & |v_i| \leq \delta(1+\gamma), \\[6pt] v_i - \gamma\,\delta\,\operatorname{sign}(v_i) & |v_i| > \delta(1+\gamma). \end{cases} \]

The branches join continuously at \(|v_i| = \delta(1+\gamma)\). Equivalently, with element-wise soft thresholding \(S\),

\[ \operatorname{prox}_{\gamma f}(v) = \frac{v + \gamma\, S_{\delta(1+\gamma)}(v)}{1+\gamma}. \]

Verification lowering. prox_huberloss uses the soft-threshold form: a single SoftThreshold step with threshold \(\delta(1+\gamma)\) followed by affine arithmetic.

Source code in fomtools/proxable_functions/huber_loss.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
class HuberLoss(ProxableFunction):
    r"""
    Element-wise Huber loss.

    Represents

    $$
        f(x) = \sum_i h(x_i),
        \qquad
        h(t) = \begin{cases}
            \tfrac{1}{2} t^2                        & |t| \leq \delta \\
            \delta |t| - \tfrac{1}{2}\delta^2        & |t| > \delta.
        \end{cases}
    $$

    Convex and $C^1$. This is the standard element-wise robust loss, *not* the
    multivariate norm-based "vector Huber" function.

    Parameters
    ----------
    delta : float
        Threshold $\delta \ge 0$ between the quadratic and linear regimes.

    Raises
    ------
    ValueError
        If ``delta`` is negative.

    Notes
    -----
    **Proximal operator.** Element-wise separable; per coordinate,

    $$
        \operatorname{prox}_{\gamma f}(v)_i = \begin{cases}
            \dfrac{v_i}{1+\gamma}
                & |v_i| \leq \delta(1+\gamma), \\[6pt]
            v_i - \gamma\,\delta\,\operatorname{sign}(v_i)
                & |v_i| > \delta(1+\gamma).
        \end{cases}
    $$

    The branches join continuously at $|v_i| = \delta(1+\gamma)$. Equivalently,
    with element-wise soft thresholding $S$,

    $$
        \operatorname{prox}_{\gamma f}(v)
            = \frac{v + \gamma\, S_{\delta(1+\gamma)}(v)}{1+\gamma}.
    $$

    **Verification lowering.** ``prox_huberloss`` uses the soft-threshold form:
    a single ``SoftThreshold`` step with threshold $\delta(1+\gamma)$ followed
    by affine arithmetic.
    """

    def __init__(self, delta: float):
        super().__init__()
        if delta < 0:
            raise ValueError("delta must be non-negative")
        self.delta = delta

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \sum_i h(x_i)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.
        """
        ax = np.abs(x)
        per_coord = np.where(
            ax <= self.delta,
            0.5 * x * x,
            self.delta * ax - 0.5 * self.delta**2,
        )
        return float(np.sum(per_coord))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Uses the closed form, per coordinate,

        $$
            \operatorname{prox}_{\gamma f}(\text{arg})_i = \begin{cases}
                \text{arg}_i / (1+\gamma)
                    & |\text{arg}_i| \leq \delta(1+\gamma) \\
                \text{arg}_i - \gamma\,\delta\,\operatorname{sign}(\text{arg}_i)
                    & |\text{arg}_i| > \delta(1+\gamma).
            \end{cases}
        $$

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        theta = self.delta * (1.0 + gamma)
        return np.where(
            np.abs(arg) <= theta,
            arg / (1.0 + gamma),
            arg - gamma * self.delta * np.sign(arg),
        )

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute the gradient of $f$ at ``x``.

        Per coordinate,

        $$
            \nabla f(x)_i = h'(x_i) = \begin{cases}
                x_i                                     & |x_i| \leq \delta \\
                \delta\,\operatorname{sign}(x_i)         & |x_i| > \delta.
            \end{cases}
        $$

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the gradient.

        Returns
        -------
        np.ndarray
            The gradient $\nabla f(x)$.
        """
        return np.where(np.abs(x) <= self.delta, x, self.delta * np.sign(x))

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \sum_i h(x_i)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Source code in fomtools/proxable_functions/huber_loss.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \sum_i h(x_i)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.
    """
    ax = np.abs(x)
    per_coord = np.where(
        ax <= self.delta,
        0.5 * x * x,
        self.delta * ax - 0.5 * self.delta**2,
    )
    return float(np.sum(per_coord))

grad

grad(x: ndarray) -> ndarray

Compute the gradient of \(f\) at x.

Per coordinate,

\[ \nabla f(x)_i = h'(x_i) = \begin{cases} x_i & |x_i| \leq \delta \\ \delta\,\operatorname{sign}(x_i) & |x_i| > \delta. \end{cases} \]

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the gradient.

required

Returns:

Type Description
ndarray

The gradient \(\nabla f(x)\).

Source code in fomtools/proxable_functions/huber_loss.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute the gradient of $f$ at ``x``.

    Per coordinate,

    $$
        \nabla f(x)_i = h'(x_i) = \begin{cases}
            x_i                                     & |x_i| \leq \delta \\
            \delta\,\operatorname{sign}(x_i)         & |x_i| > \delta.
        \end{cases}
    $$

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the gradient.

    Returns
    -------
    np.ndarray
        The gradient $\nabla f(x)$.
    """
    return np.where(np.abs(x) <= self.delta, x, self.delta * np.sign(x))

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Uses the closed form, per coordinate,

\[ \operatorname{prox}_{\gamma f}(\text{arg})_i = \begin{cases} \text{arg}_i / (1+\gamma) & |\text{arg}_i| \leq \delta(1+\gamma) \\ \text{arg}_i - \gamma\,\delta\,\operatorname{sign}(\text{arg}_i) & |\text{arg}_i| > \delta(1+\gamma). \end{cases} \]

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/huber_loss.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Uses the closed form, per coordinate,

    $$
        \operatorname{prox}_{\gamma f}(\text{arg})_i = \begin{cases}
            \text{arg}_i / (1+\gamma)
                & |\text{arg}_i| \leq \delta(1+\gamma) \\
            \text{arg}_i - \gamma\,\delta\,\operatorname{sign}(\text{arg}_i)
                & |\text{arg}_i| > \delta(1+\gamma).
        \end{cases}
    $$

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    theta = self.delta * (1.0 + gamma)
    return np.where(
        np.abs(arg) <= theta,
        arg / (1.0 + gamma),
        arg - gamma * self.delta * np.sign(arg),
    )

IndAffine

Bases: ProxableFunction

Indicator of the affine set \(\{x : Ax = b\}\).

Represents

\[ f(x) = I_{\{Ax = b\}}(x) = \begin{cases} 0 & \text{if } Ax = b \\ +\infty & \text{otherwise.} \end{cases} \]

Parameters:

Name Type Description Default
A ndarray

Constraint matrix of shape (m, n), assumed to have full row rank so that \(AA^\top\) is invertible.

required
b float, np.ndarray, or LinearExpression

Right-hand side of shape (m,). A scalar is broadcast; a LinearExpression is treated as a parameter by the verification lowering.

required

Raises:

Type Description
ValueError

If A is not 2-D, or b does not match the row count of A.

See Also

IndPolyhedral : Inequality-constrained counterpart \(\{Ax \le b\}\).

Notes

Proximal operator. The Euclidean projection onto the affine set, independent of \(\gamma\). Its KKT conditions form the block system

\[ \begin{bmatrix} I_n & A^\top \\ A & 0_{m\times m} \end{bmatrix} \begin{bmatrix} u \\ \nu \end{bmatrix} = \begin{bmatrix} v \\ b \end{bmatrix}, \]

whose solution is \(u = v - A^\top (AA^\top)^{-1}(Av - b)\).

Verification lowering. prox_indaffine posts the two KKT rows as linear equalities on fresh iterates \(u\) and \(\nu\); it never forms \((AA^\top)^{-1}\) and needs no complementarity constraints.

Source code in fomtools/proxable_functions/ind_affine.py
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
class IndAffine(ProxableFunction):
    r"""
    Indicator of the affine set $\{x : Ax = b\}$.

    Represents

    $$
        f(x) = I_{\{Ax = b\}}(x) = \begin{cases}
            0       & \text{if } Ax = b \\
            +\infty & \text{otherwise.}
        \end{cases}
    $$

    Parameters
    ----------
    A : np.ndarray
        Constraint matrix of shape ``(m, n)``, assumed to have full row rank so
        that $AA^\top$ is invertible.
    b : float, np.ndarray, or LinearExpression
        Right-hand side of shape ``(m,)``. A scalar is broadcast; a
        ``LinearExpression`` is treated as a parameter by the verification
        lowering.

    Raises
    ------
    ValueError
        If ``A`` is not 2-D, or ``b`` does not match the row count of ``A``.

    See Also
    --------
    IndPolyhedral : Inequality-constrained counterpart $\{Ax \le b\}$.

    Notes
    -----
    **Proximal operator.** The Euclidean projection onto the affine set,
    independent of $\gamma$. Its KKT conditions form the block system

    $$
        \begin{bmatrix} I_n & A^\top \\ A & 0_{m\times m} \end{bmatrix}
        \begin{bmatrix} u \\ \nu \end{bmatrix}
        =
        \begin{bmatrix} v \\ b \end{bmatrix},
    $$

    whose solution is $u = v - A^\top (AA^\top)^{-1}(Av - b)$.

    **Verification lowering.** ``prox_indaffine`` posts the two KKT rows as
    linear equalities on fresh iterates $u$ and $\nu$; it never forms
    $(AA^\top)^{-1}$ and needs no complementarity constraints.
    """

    def __init__(
        self,
        A: np.ndarray,
        b: float | np.ndarray | LinearExpression,
    ):
        super().__init__()
        A = np.asarray(A, dtype=float)
        if A.ndim != 2:
            raise ValueError(f"A must be a 2-D matrix; got shape {A.shape}")
        m, n = A.shape
        if isinstance(b, np.ndarray):
            if b.shape != (m,):
                raise ValueError(
                    f"b must have shape ({m},) to match A's row count; got shape {b.shape}"
                )
        elif hasattr(b, "decomposition_dict"):  # LinearExpression
            if b.ndim != m:
                raise ValueError(f"b LinearExpression must have ndim {m}; got ndim {b.ndim}")
        # else: scalar — broadcasts in numpy arithmetic; LE-stacking lifts as a const.
        self.A = A
        self.b = b
        self.m = m
        self.n = n

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = I_{\{Ax = b\}}(x)$.

        Requires numeric ``b``; a ``LinearExpression`` ``b`` is supported only by
        the verification lowering.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            ``0.0`` if $Ax = b$ (within floating-point tolerance), ``+inf``
            otherwise.

        Raises
        ------
        TypeError
            If ``b`` is a ``LinearExpression``.
        """
        if hasattr(self.b, "decomposition_dict"):
            raise TypeError(
                "IndAffine.eval requires numeric b; got LinearExpression. "
                "Symbolic b is supported only by the verification-side lowering."
            )
        if np.allclose(self.A @ x, self.b):
            return 0.0
        return np.inf

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Project ``arg`` onto the affine set $\{x : Ax = b\}$.

        Solves the $m \times m$ system $AA^\top \nu = A\,\text{arg} - b$ and
        returns $\text{arg} - A^\top \nu$.

        Requires numeric ``b``; a ``LinearExpression`` ``b`` is supported only by
        the verification lowering.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size; unused, since the projection is independent of $\gamma$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Raises
        ------
        TypeError
            If ``b`` is a ``LinearExpression``.
        """
        if hasattr(self.b, "decomposition_dict"):
            raise TypeError(
                "IndAffine.prox requires numeric b; got LinearExpression. "
                "Symbolic b is supported only by the verification-side lowering."
            )
        nu = np.linalg.solve(self.A @ self.A.T, self.A @ arg - self.b)
        return arg - self.A.T @ nu

    def grad(self, x: np.ndarray) -> np.ndarray:
        """
        Not defined: indicator functions are not differentiable.

        Raises
        ------
        NotImplementedError
            Always.
        """
        raise NotImplementedError("Indicator functions are not differentiable.")

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = I_{\{Ax = b\}}(x)\).

Requires numeric b; a LinearExpression b is supported only by the verification lowering.

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

0.0 if \(Ax = b\) (within floating-point tolerance), +inf otherwise.

Raises:

Type Description
TypeError

If b is a LinearExpression.

Source code in fomtools/proxable_functions/ind_affine.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = I_{\{Ax = b\}}(x)$.

    Requires numeric ``b``; a ``LinearExpression`` ``b`` is supported only by
    the verification lowering.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        ``0.0`` if $Ax = b$ (within floating-point tolerance), ``+inf``
        otherwise.

    Raises
    ------
    TypeError
        If ``b`` is a ``LinearExpression``.
    """
    if hasattr(self.b, "decomposition_dict"):
        raise TypeError(
            "IndAffine.eval requires numeric b; got LinearExpression. "
            "Symbolic b is supported only by the verification-side lowering."
        )
    if np.allclose(self.A @ x, self.b):
        return 0.0
    return np.inf

grad

grad(x: ndarray) -> ndarray

Not defined: indicator functions are not differentiable.

Raises:

Type Description
NotImplementedError

Always.

Source code in fomtools/proxable_functions/ind_affine.py
149
150
151
152
153
154
155
156
157
158
def grad(self, x: np.ndarray) -> np.ndarray:
    """
    Not defined: indicator functions are not differentiable.

    Raises
    ------
    NotImplementedError
        Always.
    """
    raise NotImplementedError("Indicator functions are not differentiable.")

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Project arg onto the affine set \(\{x : Ax = b\}\).

Solves the \(m \times m\) system \(AA^\top \nu = A\,\text{arg} - b\) and returns \(\text{arg} - A^\top \nu\).

Requires numeric b; a LinearExpression b is supported only by the verification lowering.

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size; unused, since the projection is independent of \(\gamma\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Raises:

Type Description
TypeError

If b is a LinearExpression.

Source code in fomtools/proxable_functions/ind_affine.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Project ``arg`` onto the affine set $\{x : Ax = b\}$.

    Solves the $m \times m$ system $AA^\top \nu = A\,\text{arg} - b$ and
    returns $\text{arg} - A^\top \nu$.

    Requires numeric ``b``; a ``LinearExpression`` ``b`` is supported only by
    the verification lowering.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size; unused, since the projection is independent of $\gamma$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Raises
    ------
    TypeError
        If ``b`` is a ``LinearExpression``.
    """
    if hasattr(self.b, "decomposition_dict"):
        raise TypeError(
            "IndAffine.prox requires numeric b; got LinearExpression. "
            "Symbolic b is supported only by the verification-side lowering."
        )
    nu = np.linalg.solve(self.A @ self.A.T, self.A @ arg - self.b)
    return arg - self.A.T @ nu

IndBallL1

Bases: ProxableFunction

Indicator of the \(\ell_1\) ball \(\{x : \|x\|_1 \le r\}\).

Represents

\[ f(x) = I_{\{\|x\|_1 \le r\}}(x) = \begin{cases} 0 & \text{if } \|x\|_1 \le r \\ +\infty & \text{otherwise.} \end{cases} \]

Parameters:

Name Type Description Default
r float or LinearExpression

Ball radius \(r \ge 0\), or a dim-1 LinearExpression treated as a parameter by the verification lowering.

required
dual_ub float or None

Upper bound on the KKT multipliers used by the verification lowering. None auto-derives a valid bound from the input box.

None

Raises:

Type Description
ValueError

If r is negative, or a LinearExpression r is not dim-1.

See Also

IndBallLinf : The \(\ell_\infty\) ball. NormInf : Its prox is computed from this projection by Moreau decomposition.

Notes

Proximal operator. The Euclidean projection onto the ball,

\[ \operatorname{prox}_{\gamma f}(v) = \arg\min_{u}\ \tfrac{1}{2}\|u - v\|_2^2 \quad\text{s.t.}\quad \|u\|_1 \le r, \]

independent of \(\gamma\).

Verification lowering. prox_indballl1 encodes the KKT system of the lifted problem

\[ \min_{u, t}\ \tfrac{1}{2}\|u - v\|_2^2 \quad\text{s.t.}\quad -t \le u \le t,\ \mathbf{1}^\top t \le r \]

with complementarity constraints, bounding the multipliers by dual_ub.

Source code in fomtools/proxable_functions/ind_ball_l1.py
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
class IndBallL1(ProxableFunction):
    r"""
    Indicator of the $\ell_1$ ball $\{x : \|x\|_1 \le r\}$.

    Represents

    $$
        f(x) = I_{\{\|x\|_1 \le r\}}(x) = \begin{cases}
            0       & \text{if } \|x\|_1 \le r \\
            +\infty & \text{otherwise.}
        \end{cases}
    $$

    Parameters
    ----------
    r : float or LinearExpression
        Ball radius $r \ge 0$, or a dim-1 ``LinearExpression`` treated as a
        parameter by the verification lowering.
    dual_ub : float or None, default None
        Upper bound on the KKT multipliers used by the verification lowering.
        ``None`` auto-derives a valid bound from the input box.

    Raises
    ------
    ValueError
        If ``r`` is negative, or a ``LinearExpression`` ``r`` is not dim-1.

    See Also
    --------
    IndBallLinf : The $\ell_\infty$ ball.
    NormInf : Its prox is computed from this projection by Moreau decomposition.

    Notes
    -----
    **Proximal operator.** The Euclidean projection onto the ball,

    $$
        \operatorname{prox}_{\gamma f}(v)
        = \arg\min_{u}\ \tfrac{1}{2}\|u - v\|_2^2
        \quad\text{s.t.}\quad \|u\|_1 \le r,
    $$

    independent of $\gamma$.

    **Verification lowering.** ``prox_indballl1`` encodes the KKT system of the
    lifted problem

    $$
        \min_{u, t}\ \tfrac{1}{2}\|u - v\|_2^2 \quad\text{s.t.}\quad
        -t \le u \le t,\ \mathbf{1}^\top t \le r
    $$

    with complementarity constraints, bounding the multipliers by ``dual_ub``.
    """

    def __init__(
        self,
        r: float | LinearExpression,
        dual_ub: float | None = None,
    ):
        super().__init__()
        if hasattr(r, "decomposition_dict"):  # dim-1 LinearExpression parameter
            if r.ndim != 1:
                raise ValueError("r must be a scalar or a dim-1 LinearExpression")
        elif r < 0:
            raise ValueError("r must be non-negative")
        self.r = r
        self.dual_ub = dual_ub

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Project ``arg`` onto the $\ell_1$ ball of radius $r$.

        Solved numerically with CVXPY (Clarabel) as an independent reference; a
        ``LinearExpression`` ``r`` uses its constant value.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size; unused, since the projection is independent of $\gamma$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        arg = np.asarray(arg, dtype=float)
        # Numeric reference: a parameter ``r`` uses its stored constant value (cf. IndAffine).
        r = float(self.r.const[0]) if hasattr(self.r, "decomposition_dict") else float(self.r)
        u = cp.Variable(arg.shape[0])
        # Pin CLARABEL: deterministic and accurate (~1e-7) across platforms, unlike
        # cvxpy's auto-selected default (OSQP/SCS) which can return ~1e-3 solutions.
        cp.Problem(cp.Minimize(cp.sum_squares(u - arg)), [cp.norm1(u) <= r]).solve(
            solver=cp.CLARABEL
        )
        return np.asarray(u.value, dtype=float)

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Project arg onto the \(\ell_1\) ball of radius \(r\).

Solved numerically with CVXPY (Clarabel) as an independent reference; a LinearExpression r uses its constant value.

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size; unused, since the projection is independent of \(\gamma\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/ind_ball_l1.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Project ``arg`` onto the $\ell_1$ ball of radius $r$.

    Solved numerically with CVXPY (Clarabel) as an independent reference; a
    ``LinearExpression`` ``r`` uses its constant value.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size; unused, since the projection is independent of $\gamma$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    arg = np.asarray(arg, dtype=float)
    # Numeric reference: a parameter ``r`` uses its stored constant value (cf. IndAffine).
    r = float(self.r.const[0]) if hasattr(self.r, "decomposition_dict") else float(self.r)
    u = cp.Variable(arg.shape[0])
    # Pin CLARABEL: deterministic and accurate (~1e-7) across platforms, unlike
    # cvxpy's auto-selected default (OSQP/SCS) which can return ~1e-3 solutions.
    cp.Problem(cp.Minimize(cp.sum_squares(u - arg)), [cp.norm1(u) <= r]).solve(
        solver=cp.CLARABEL
    )
    return np.asarray(u.value, dtype=float)

IndBallLinf

Bases: ProxableFunction

Indicator of the \(\ell_\infty\) ball \(\{x : \|x\|_\infty \le r\}\).

Represents

\[ f(x) = I_{\{\|x\|_\infty \leq r\}}(x) = \begin{cases} 0 & \text{if } \|x\|_\infty \leq r \\ +\infty & \text{otherwise.} \end{cases} \]

Parameters:

Name Type Description Default
r float

Ball radius \(r \ge 0\).

required

Raises:

Type Description
ValueError

If r is negative.

See Also

IndBox : General box \([l, u]\). IndBallL1 : The \(\ell_1\) ball.

Notes

Proximal operator. The Euclidean projection onto the ball, an element-wise clip,

\[ \operatorname{prox}_{\gamma f}(v) = \operatorname{clip}(v, -r, r), \]

independent of \(\gamma\).

Verification lowering. prox_indballlinf posts a single SaturatedLinear step clipping to \([-r, r]\).

Source code in fomtools/proxable_functions/ind_ball_linf.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
class IndBallLinf(ProxableFunction):
    r"""
    Indicator of the $\ell_\infty$ ball $\{x : \|x\|_\infty \le r\}$.

    Represents

    $$
        f(x) = I_{\{\|x\|_\infty \leq r\}}(x) = \begin{cases}
            0       & \text{if } \|x\|_\infty \leq r \\
            +\infty & \text{otherwise.}
        \end{cases}
    $$

    Parameters
    ----------
    r : float
        Ball radius $r \ge 0$.

    Raises
    ------
    ValueError
        If ``r`` is negative.

    See Also
    --------
    IndBox : General box $[l, u]$.
    IndBallL1 : The $\ell_1$ ball.

    Notes
    -----
    **Proximal operator.** The Euclidean projection onto the ball, an
    element-wise clip,

    $$
        \operatorname{prox}_{\gamma f}(v) = \operatorname{clip}(v, -r, r),
    $$

    independent of $\gamma$.

    **Verification lowering.** ``prox_indballlinf`` posts a single
    ``SaturatedLinear`` step clipping to $[-r, r]$.
    """

    def __init__(self, r: float):
        super().__init__()
        if r < 0:
            raise ValueError("r must be non-negative")
        self.r = r

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = I_{\{\|x\|_\infty \leq r\}}(x)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            ``0.0`` if $\|x\|_\infty \leq r$, ``+inf`` otherwise.
        """
        if np.linalg.norm(x, np.inf) <= self.r:
            return 0.0
        return np.inf

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Project ``arg`` onto the $\ell_\infty$ ball of radius $r$.

        Computes $\operatorname{clip}(\text{arg}, -r, r)$ element-wise.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size; unused, since the projection is independent of $\gamma$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        return np.clip(arg, -self.r, self.r)

    def grad(self, x: np.ndarray) -> np.ndarray:
        """
        Not defined: indicator functions are not differentiable.

        Raises
        ------
        NotImplementedError
            Always.
        """
        raise NotImplementedError("Indicator functions are not differentiable.")

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = I_{\{\|x\|_\infty \leq r\}}(x)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

0.0 if \(\|x\|_\infty \leq r\), +inf otherwise.

Source code in fomtools/proxable_functions/ind_ball_linf.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = I_{\{\|x\|_\infty \leq r\}}(x)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        ``0.0`` if $\|x\|_\infty \leq r$, ``+inf`` otherwise.
    """
    if np.linalg.norm(x, np.inf) <= self.r:
        return 0.0
    return np.inf

grad

grad(x: ndarray) -> ndarray

Not defined: indicator functions are not differentiable.

Raises:

Type Description
NotImplementedError

Always.

Source code in fomtools/proxable_functions/ind_ball_linf.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
def grad(self, x: np.ndarray) -> np.ndarray:
    """
    Not defined: indicator functions are not differentiable.

    Raises
    ------
    NotImplementedError
        Always.
    """
    raise NotImplementedError("Indicator functions are not differentiable.")

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Project arg onto the \(\ell_\infty\) ball of radius \(r\).

Computes \(\operatorname{clip}(\text{arg}, -r, r)\) element-wise.

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size; unused, since the projection is independent of \(\gamma\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/ind_ball_linf.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Project ``arg`` onto the $\ell_\infty$ ball of radius $r$.

    Computes $\operatorname{clip}(\text{arg}, -r, r)$ element-wise.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size; unused, since the projection is independent of $\gamma$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    return np.clip(arg, -self.r, self.r)

IndBox

Bases: ProxableFunction

Indicator of the box \([l, u]\).

Represents

\[ f(x) = I_{[l, u]}(x) = \begin{cases} 0 & \text{if } l \leq x \leq u \text{ (element-wise)} \\ +\infty & \text{otherwise.} \end{cases} \]

Parameters:

Name Type Description Default
l float, np.ndarray, or LinearExpression

Lower bound. A scalar is broadcast; a LinearExpression is treated as a parameter by the verification lowering.

required
u float, np.ndarray, or LinearExpression

Upper bound, with the same conventions as l.

required
See Also

IndBallLinf : Symmetric box \([-r, r]\). IndNonNegative : One-sided box \([0, +\infty)\).

Notes

Proximal operator. The Euclidean projection onto the box,

\[ \operatorname{prox}_{\gamma f}(v) = \min(\max(v, l), u) \quad \text{(element-wise)}, \]

independent of \(\gamma\).

Verification lowering. prox_indbox posts a single SaturatedLinear step clipping to \([l, u]\). LinearExpression bounds enter through their interval bounds during bound propagation.

Source code in fomtools/proxable_functions/ind_box.py
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
class IndBox(ProxableFunction):
    r"""
    Indicator of the box $[l, u]$.

    Represents

    $$
        f(x) = I_{[l, u]}(x) = \begin{cases}
            0       & \text{if } l \leq x \leq u \text{ (element-wise)} \\
            +\infty & \text{otherwise.}
        \end{cases}
    $$

    Parameters
    ----------
    l : float, np.ndarray, or LinearExpression
        Lower bound. A scalar is broadcast; a ``LinearExpression`` is treated as
        a parameter by the verification lowering.
    u : float, np.ndarray, or LinearExpression
        Upper bound, with the same conventions as ``l``.

    See Also
    --------
    IndBallLinf : Symmetric box $[-r, r]$.
    IndNonNegative : One-sided box $[0, +\infty)$.

    Notes
    -----
    **Proximal operator.** The Euclidean projection onto the box,

    $$
        \operatorname{prox}_{\gamma f}(v) = \min(\max(v, l), u)
        \quad \text{(element-wise)},
    $$

    independent of $\gamma$.

    **Verification lowering.** ``prox_indbox`` posts a single
    ``SaturatedLinear`` step clipping to $[l, u]$. ``LinearExpression`` bounds
    enter through their interval bounds during bound propagation.
    """

    def __init__(
        self,
        l: float | np.ndarray | LinearExpression,
        u: float | np.ndarray | LinearExpression,
    ):
        super().__init__()
        self.l = l
        self.u = u

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = I_{[l, u]}(x)$.

        Requires numeric ``l`` and ``u``; ``LinearExpression`` bounds are
        supported only by the verification lowering.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            ``0.0`` if $l \leq x \leq u$ element-wise, ``+inf`` otherwise.

        Raises
        ------
        TypeError
            If ``l`` or ``u`` is a ``LinearExpression``.
        """
        if hasattr(self.l, "decomposition_dict") or hasattr(self.u, "decomposition_dict"):
            raise TypeError(
                "IndBox.eval requires numeric l and u; got LinearExpression. "
                "Symbolic bounds are supported only by the verification-side lowering."
            )
        if np.all((x >= self.l) & (x <= self.u)):
            return 0.0
        return np.inf

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Project ``arg`` onto the box $[l, u]$.

        Computes $\min(\max(\text{arg}, l), u)$ element-wise.

        Requires numeric ``l`` and ``u``; ``LinearExpression`` bounds are
        supported only by the verification lowering.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size; unused, since the projection is independent of $\gamma$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Raises
        ------
        TypeError
            If ``l`` or ``u`` is a ``LinearExpression``.
        """
        if hasattr(self.l, "decomposition_dict") or hasattr(self.u, "decomposition_dict"):
            raise TypeError(
                "IndBox.prox requires numeric l and u; got LinearExpression. "
                "Symbolic bounds are supported only by the verification-side lowering."
            )
        return np.minimum(np.maximum(arg, self.l), self.u)

    def grad(self, x: np.ndarray) -> np.ndarray:
        """
        Not defined: indicator functions are not differentiable.

        Raises
        ------
        NotImplementedError
            Always.
        """
        raise NotImplementedError("Indicator functions are not differentiable.")

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = I_{[l, u]}(x)\).

Requires numeric l and u; LinearExpression bounds are supported only by the verification lowering.

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

0.0 if \(l \leq x \leq u\) element-wise, +inf otherwise.

Raises:

Type Description
TypeError

If l or u is a LinearExpression.

Source code in fomtools/proxable_functions/ind_box.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = I_{[l, u]}(x)$.

    Requires numeric ``l`` and ``u``; ``LinearExpression`` bounds are
    supported only by the verification lowering.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        ``0.0`` if $l \leq x \leq u$ element-wise, ``+inf`` otherwise.

    Raises
    ------
    TypeError
        If ``l`` or ``u`` is a ``LinearExpression``.
    """
    if hasattr(self.l, "decomposition_dict") or hasattr(self.u, "decomposition_dict"):
        raise TypeError(
            "IndBox.eval requires numeric l and u; got LinearExpression. "
            "Symbolic bounds are supported only by the verification-side lowering."
        )
    if np.all((x >= self.l) & (x <= self.u)):
        return 0.0
    return np.inf

grad

grad(x: ndarray) -> ndarray

Not defined: indicator functions are not differentiable.

Raises:

Type Description
NotImplementedError

Always.

Source code in fomtools/proxable_functions/ind_box.py
122
123
124
125
126
127
128
129
130
131
def grad(self, x: np.ndarray) -> np.ndarray:
    """
    Not defined: indicator functions are not differentiable.

    Raises
    ------
    NotImplementedError
        Always.
    """
    raise NotImplementedError("Indicator functions are not differentiable.")

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Project arg onto the box \([l, u]\).

Computes \(\min(\max(\text{arg}, l), u)\) element-wise.

Requires numeric l and u; LinearExpression bounds are supported only by the verification lowering.

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size; unused, since the projection is independent of \(\gamma\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Raises:

Type Description
TypeError

If l or u is a LinearExpression.

Source code in fomtools/proxable_functions/ind_box.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Project ``arg`` onto the box $[l, u]$.

    Computes $\min(\max(\text{arg}, l), u)$ element-wise.

    Requires numeric ``l`` and ``u``; ``LinearExpression`` bounds are
    supported only by the verification lowering.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size; unused, since the projection is independent of $\gamma$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Raises
    ------
    TypeError
        If ``l`` or ``u`` is a ``LinearExpression``.
    """
    if hasattr(self.l, "decomposition_dict") or hasattr(self.u, "decomposition_dict"):
        raise TypeError(
            "IndBox.prox requires numeric l and u; got LinearExpression. "
            "Symbolic bounds are supported only by the verification-side lowering."
        )
    return np.minimum(np.maximum(arg, self.l), self.u)

IndHalfSpace

Bases: ProxableFunction

Indicator of the closed half-space \(\{x : a^\top x \le b\}\).

Represents

\[ f(x) = I_{\{a^\top x \le b\}}(x) = \begin{cases} 0 & \text{if } a^\top x \le b \\ +\infty & \text{otherwise.} \end{cases} \]

Parameters:

Name Type Description Default
a ndarray

Nonzero normal vector of shape (n,). (For \(a = 0\) the set would be all of \(\mathbb{R}^n\) or empty, and the projection is undefined.)

required
b float

Offset.

required

Raises:

Type Description
ValueError

If a is the zero vector.

See Also

IndPolyhedral : Intersection of several half-spaces \(\{Ax \le b\}\).

Notes

Proximal operator. The Euclidean projection onto the half-space,

\[ \operatorname{prox}_{\gamma f}(v) = v - \frac{\max(0,\, a^\top v - b)}{\|a\|_2^2}\, a, \]

independent of \(\gamma\).

Verification lowering. prox_indhalfspace posts a single scalar Relu step on \((a^\top v - b)/\|a\|_2^2\) and lifts it back along \(a\).

Source code in fomtools/proxable_functions/ind_half_space.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
class IndHalfSpace(ProxableFunction):
    r"""
    Indicator of the closed half-space $\{x : a^\top x \le b\}$.

    Represents

    $$
        f(x) = I_{\{a^\top x \le b\}}(x) = \begin{cases}
            0       & \text{if } a^\top x \le b \\
            +\infty & \text{otherwise.}
        \end{cases}
    $$

    Parameters
    ----------
    a : np.ndarray
        Nonzero normal vector of shape ``(n,)``. (For $a = 0$ the set would be
        all of $\mathbb{R}^n$ or empty, and the projection is undefined.)
    b : float
        Offset.

    Raises
    ------
    ValueError
        If ``a`` is the zero vector.

    See Also
    --------
    IndPolyhedral : Intersection of several half-spaces $\{Ax \le b\}$.

    Notes
    -----
    **Proximal operator.** The Euclidean projection onto the half-space,

    $$
        \operatorname{prox}_{\gamma f}(v)
        = v - \frac{\max(0,\, a^\top v - b)}{\|a\|_2^2}\, a,
    $$

    independent of $\gamma$.

    **Verification lowering.** ``prox_indhalfspace`` posts a single scalar
    ``Relu`` step on $(a^\top v - b)/\|a\|_2^2$ and lifts it back along $a$.
    """

    def __init__(self, a: np.ndarray, b: float):
        super().__init__()
        a = np.asarray(a, dtype=float)
        norm_a_sq = float(np.dot(a, a))
        if norm_a_sq == 0.0:
            raise ValueError("a must be nonzero")
        self.a = a
        self.b = float(b)
        self._norm_a_sq = norm_a_sq

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = I_{\{a^\top x \le b\}}(x)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            ``0.0`` if $a^\top x \le b$, ``+inf`` otherwise.
        """
        if float(np.dot(self.a, x)) <= self.b:
            return 0.0
        return np.inf

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Project ``arg`` onto the half-space $\{x : a^\top x \le b\}$.

        Computes $\text{arg} - \max(0,\, a^\top \text{arg} - b)\, a / \|a\|_2^2$.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size; unused, since the projection is independent of $\gamma$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        excess = max(0.0, float(np.dot(self.a, arg)) - self.b)
        return arg - (excess / self._norm_a_sq) * self.a

    def grad(self, x: np.ndarray) -> np.ndarray:
        """
        Not defined: indicator functions are not differentiable.

        Raises
        ------
        NotImplementedError
            Always.
        """
        raise NotImplementedError("Indicator functions are not differentiable.")

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = I_{\{a^\top x \le b\}}(x)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

0.0 if \(a^\top x \le b\), +inf otherwise.

Source code in fomtools/proxable_functions/ind_half_space.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = I_{\{a^\top x \le b\}}(x)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        ``0.0`` if $a^\top x \le b$, ``+inf`` otherwise.
    """
    if float(np.dot(self.a, x)) <= self.b:
        return 0.0
    return np.inf

grad

grad(x: ndarray) -> ndarray

Not defined: indicator functions are not differentiable.

Raises:

Type Description
NotImplementedError

Always.

Source code in fomtools/proxable_functions/ind_half_space.py
100
101
102
103
104
105
106
107
108
109
def grad(self, x: np.ndarray) -> np.ndarray:
    """
    Not defined: indicator functions are not differentiable.

    Raises
    ------
    NotImplementedError
        Always.
    """
    raise NotImplementedError("Indicator functions are not differentiable.")

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Project arg onto the half-space \(\{x : a^\top x \le b\}\).

Computes \(\text{arg} - \max(0,\, a^\top \text{arg} - b)\, a / \|a\|_2^2\).

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size; unused, since the projection is independent of \(\gamma\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/ind_half_space.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Project ``arg`` onto the half-space $\{x : a^\top x \le b\}$.

    Computes $\text{arg} - \max(0,\, a^\top \text{arg} - b)\, a / \|a\|_2^2$.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size; unused, since the projection is independent of $\gamma$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    excess = max(0.0, float(np.dot(self.a, arg)) - self.b)
    return arg - (excess / self._norm_a_sq) * self.a

IndNonNegative

Bases: ProxableFunction

Indicator of the non-negative orthant.

Represents

\[ f(x) = I_{\{x \geq 0\}}(x) = \begin{cases} 0 & \text{if } x \geq 0 \text{ (element-wise)} \\ +\infty & \text{otherwise.} \end{cases} \]
See Also

IndNonPositive : The non-positive orthant. IndBox : General box \([l, u]\). SoftNonNegative : Smooth quadratic relaxation.

Notes

Proximal operator. The Euclidean projection onto the orthant,

\[ \operatorname{prox}_{\gamma f}(v) = \max(v, 0) \quad \text{(element-wise)}, \]

independent of \(\gamma\).

Verification lowering. prox_indnonnegative posts a single Relu step.

Source code in fomtools/proxable_functions/ind_nonnegative.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class IndNonNegative(ProxableFunction):
    r"""
    Indicator of the non-negative orthant.

    Represents

    $$
        f(x) = I_{\{x \geq 0\}}(x) = \begin{cases}
            0       & \text{if } x \geq 0 \text{ (element-wise)} \\
            +\infty & \text{otherwise.}
        \end{cases}
    $$

    See Also
    --------
    IndNonPositive : The non-positive orthant.
    IndBox : General box $[l, u]$.
    SoftNonNegative : Smooth quadratic relaxation.

    Notes
    -----
    **Proximal operator.** The Euclidean projection onto the orthant,

    $$
        \operatorname{prox}_{\gamma f}(v) = \max(v, 0) \quad \text{(element-wise)},
    $$

    independent of $\gamma$.

    **Verification lowering.** ``prox_indnonnegative`` posts a single ``Relu``
    step.
    """

    def __init__(self):
        super().__init__()

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = I_{\{x \geq 0\}}(x)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            ``0.0`` if $x \geq 0$ element-wise, ``+inf`` otherwise.
        """
        if np.all(x >= 0):
            return 0.0
        return np.inf

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Project ``arg`` onto the non-negative orthant.

        Computes $\max(\text{arg}, 0)$ element-wise.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size; unused, since the projection is independent of $\gamma$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        return np.maximum(arg, 0)

    def grad(self, x: np.ndarray) -> np.ndarray:
        """
        Not defined: indicator functions are not differentiable.

        Raises
        ------
        NotImplementedError
            Always.
        """
        raise NotImplementedError("Indicator functions are not differentiable.")

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = I_{\{x \geq 0\}}(x)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

0.0 if \(x \geq 0\) element-wise, +inf otherwise.

Source code in fomtools/proxable_functions/ind_nonnegative.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = I_{\{x \geq 0\}}(x)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        ``0.0`` if $x \geq 0$ element-wise, ``+inf`` otherwise.
    """
    if np.all(x >= 0):
        return 0.0
    return np.inf

grad

grad(x: ndarray) -> ndarray

Not defined: indicator functions are not differentiable.

Raises:

Type Description
NotImplementedError

Always.

Source code in fomtools/proxable_functions/ind_nonnegative.py
80
81
82
83
84
85
86
87
88
89
def grad(self, x: np.ndarray) -> np.ndarray:
    """
    Not defined: indicator functions are not differentiable.

    Raises
    ------
    NotImplementedError
        Always.
    """
    raise NotImplementedError("Indicator functions are not differentiable.")

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Project arg onto the non-negative orthant.

Computes \(\max(\text{arg}, 0)\) element-wise.

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size; unused, since the projection is independent of \(\gamma\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/ind_nonnegative.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Project ``arg`` onto the non-negative orthant.

    Computes $\max(\text{arg}, 0)$ element-wise.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size; unused, since the projection is independent of $\gamma$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    return np.maximum(arg, 0)

IndNonPositive

Bases: ProxableFunction

Indicator of the non-positive orthant.

Represents

\[ f(x) = I_{\{x \leq 0\}}(x) = \begin{cases} 0 & \text{if } x \leq 0 \text{ (element-wise)} \\ +\infty & \text{otherwise.} \end{cases} \]
See Also

IndNonNegative : The non-negative orthant. IndBox : General box \([l, u]\).

Notes

Proximal operator. The Euclidean projection onto the orthant,

\[ \operatorname{prox}_{\gamma f}(v) = \min(v, 0) \quad \text{(element-wise)}, \]

independent of \(\gamma\).

Verification lowering. prox_indnonpositive posts a single Relu step via \(\min(v, 0) = -\operatorname{ReLU}(-v)\).

Source code in fomtools/proxable_functions/ind_nonpositive.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class IndNonPositive(ProxableFunction):
    r"""
    Indicator of the non-positive orthant.

    Represents

    $$
        f(x) = I_{\{x \leq 0\}}(x) = \begin{cases}
            0       & \text{if } x \leq 0 \text{ (element-wise)} \\
            +\infty & \text{otherwise.}
        \end{cases}
    $$

    See Also
    --------
    IndNonNegative : The non-negative orthant.
    IndBox : General box $[l, u]$.

    Notes
    -----
    **Proximal operator.** The Euclidean projection onto the orthant,

    $$
        \operatorname{prox}_{\gamma f}(v) = \min(v, 0) \quad \text{(element-wise)},
    $$

    independent of $\gamma$.

    **Verification lowering.** ``prox_indnonpositive`` posts a single ``Relu``
    step via $\min(v, 0) = -\operatorname{ReLU}(-v)$.
    """

    def __init__(self):
        super().__init__()

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = I_{\{x \leq 0\}}(x)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            ``0.0`` if $x \leq 0$ element-wise, ``+inf`` otherwise.
        """
        if np.all(x <= 0):
            return 0.0
        return np.inf

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Project ``arg`` onto the non-positive orthant.

        Computes $\min(\text{arg}, 0)$ element-wise.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size; unused, since the projection is independent of $\gamma$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        return np.minimum(arg, 0)

    def grad(self, x: np.ndarray) -> np.ndarray:
        """
        Not defined: indicator functions are not differentiable.

        Raises
        ------
        NotImplementedError
            Always.
        """
        raise NotImplementedError("Indicator functions are not differentiable.")

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = I_{\{x \leq 0\}}(x)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

0.0 if \(x \leq 0\) element-wise, +inf otherwise.

Source code in fomtools/proxable_functions/ind_nonpositive.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = I_{\{x \leq 0\}}(x)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        ``0.0`` if $x \leq 0$ element-wise, ``+inf`` otherwise.
    """
    if np.all(x <= 0):
        return 0.0
    return np.inf

grad

grad(x: ndarray) -> ndarray

Not defined: indicator functions are not differentiable.

Raises:

Type Description
NotImplementedError

Always.

Source code in fomtools/proxable_functions/ind_nonpositive.py
79
80
81
82
83
84
85
86
87
88
def grad(self, x: np.ndarray) -> np.ndarray:
    """
    Not defined: indicator functions are not differentiable.

    Raises
    ------
    NotImplementedError
        Always.
    """
    raise NotImplementedError("Indicator functions are not differentiable.")

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Project arg onto the non-positive orthant.

Computes \(\min(\text{arg}, 0)\) element-wise.

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size; unused, since the projection is independent of \(\gamma\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/ind_nonpositive.py
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Project ``arg`` onto the non-positive orthant.

    Computes $\min(\text{arg}, 0)$ element-wise.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size; unused, since the projection is independent of $\gamma$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    return np.minimum(arg, 0)

IndPoint

Bases: ProxableFunction

Indicator of the singleton \(\{p\}\).

Represents

\[ f(x) = I_{\{p\}}(x) = \begin{cases} 0 & \text{if } x = p \\ +\infty & \text{otherwise.} \end{cases} \]

Parameters:

Name Type Description Default
p float or ndarray

The point. A scalar is broadcast against the input.

required
See Also

IndBox : The box \([l, u]\); \(\{p\}\) is the case \(l = u = p\).

Notes

Proximal operator. The constant map

\[ \operatorname{prox}_{\gamma f}(v) = p, \]

independent of \(\gamma\).

Verification lowering. prox_indpoint posts a degenerate SaturatedLinear step with \(l = u = p\).

Source code in fomtools/proxable_functions/ind_point.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class IndPoint(ProxableFunction):
    r"""
    Indicator of the singleton $\{p\}$.

    Represents

    $$
        f(x) = I_{\{p\}}(x) = \begin{cases}
            0       & \text{if } x = p \\
            +\infty & \text{otherwise.}
        \end{cases}
    $$

    Parameters
    ----------
    p : float or np.ndarray
        The point. A scalar is broadcast against the input.

    See Also
    --------
    IndBox : The box $[l, u]$; $\{p\}$ is the case $l = u = p$.

    Notes
    -----
    **Proximal operator.** The constant map

    $$
        \operatorname{prox}_{\gamma f}(v) = p,
    $$

    independent of $\gamma$.

    **Verification lowering.** ``prox_indpoint`` posts a degenerate
    ``SaturatedLinear`` step with $l = u = p$.
    """

    def __init__(self, p: float | np.ndarray):
        super().__init__()
        self.p = p

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = I_{\{p\}}(x)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            ``0.0`` if $x = p$ (within floating-point tolerance), ``+inf``
            otherwise.
        """
        if np.allclose(x, self.p):
            return 0.0
        return np.inf

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Project ``arg`` onto $\{p\}$.

        Returns $p$ broadcast to the shape of ``arg``; ``arg`` is used only for
        its shape.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size; unused, since the projection is independent of $\gamma$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        return np.broadcast_to(self.p, arg.shape).copy()

    def grad(self, x: np.ndarray) -> np.ndarray:
        """
        Not defined: indicator functions are not differentiable.

        Raises
        ------
        NotImplementedError
            Always.
        """
        raise NotImplementedError("Indicator functions are not differentiable.")

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = I_{\{p\}}(x)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

0.0 if \(x = p\) (within floating-point tolerance), +inf otherwise.

Source code in fomtools/proxable_functions/ind_point.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = I_{\{p\}}(x)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        ``0.0`` if $x = p$ (within floating-point tolerance), ``+inf``
        otherwise.
    """
    if np.allclose(x, self.p):
        return 0.0
    return np.inf

grad

grad(x: ndarray) -> ndarray

Not defined: indicator functions are not differentiable.

Raises:

Type Description
NotImplementedError

Always.

Source code in fomtools/proxable_functions/ind_point.py
86
87
88
89
90
91
92
93
94
95
def grad(self, x: np.ndarray) -> np.ndarray:
    """
    Not defined: indicator functions are not differentiable.

    Raises
    ------
    NotImplementedError
        Always.
    """
    raise NotImplementedError("Indicator functions are not differentiable.")

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Project arg onto \(\{p\}\).

Returns \(p\) broadcast to the shape of arg; arg is used only for its shape.

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size; unused, since the projection is independent of \(\gamma\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/ind_point.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Project ``arg`` onto $\{p\}$.

    Returns $p$ broadcast to the shape of ``arg``; ``arg`` is used only for
    its shape.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size; unused, since the projection is independent of $\gamma$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    return np.broadcast_to(self.p, arg.shape).copy()

IndPolyhedral

Bases: ProxableFunction

Indicator of the polyhedron \(\{x : Ax \le b\}\).

Represents

\[ f(x) = I_{\{Ax \le b\}}(x) = \begin{cases} 0 & \text{if } Ax \le b \\ +\infty & \text{otherwise.} \end{cases} \]

Parameters:

Name Type Description Default
A ndarray

Constraint matrix of shape (m, n).

required
b float, np.ndarray, or LinearExpression

Right-hand side. A scalar or array is broadcast to shape (m,); a dim-m LinearExpression is treated as a parameter by the verification lowering.

required
dual_ub float, np.ndarray, or None

Upper bound(s) on the multipliers \(\lambda \ge 0\). Required for verification: the multipliers of a general polyhedron cannot be bounded automatically, so the verification lowering raises ValueError when this is None. Not used by prox.

None

Raises:

Type Description
ValueError

If A is not 2-D, or a LinearExpression b does not have dimension m.

See Also

IndHalfSpace : Single half-space, with a closed-form projection. IndAffine : Equality-constrained counterpart \(\{Ax = b\}\).

Notes

Proximal operator. The Euclidean projection onto the polyhedron,

\[ \operatorname{prox}_{\gamma f}(v) = \arg\min_{u}\ \tfrac{1}{2}\|u - v\|_2^2 \quad\text{s.t.}\quad Au \le b, \]

independent of \(\gamma\).

Verification lowering. prox_indpolyhedral encodes the KKT system

\[ u = v - A^\top \lambda,\qquad 0 \le \lambda \perp (b - Au) \ge 0 \]

with complementarity constraints. The MIP big-M encoding needs the finite bound dual_ub on \(\lambda\); OBBT tightens it further when enabled.

Source code in fomtools/proxable_functions/ind_polyhedral.py
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
class IndPolyhedral(ProxableFunction):
    r"""
    Indicator of the polyhedron $\{x : Ax \le b\}$.

    Represents

    $$
        f(x) = I_{\{Ax \le b\}}(x) = \begin{cases}
            0       & \text{if } Ax \le b \\
            +\infty & \text{otherwise.}
        \end{cases}
    $$

    Parameters
    ----------
    A : np.ndarray
        Constraint matrix of shape ``(m, n)``.
    b : float, np.ndarray, or LinearExpression
        Right-hand side. A scalar or array is broadcast to shape ``(m,)``; a
        dim-``m`` ``LinearExpression`` is treated as a parameter by the
        verification lowering.
    dual_ub : float, np.ndarray, or None, default None
        Upper bound(s) on the multipliers $\lambda \ge 0$. Required for
        verification: the multipliers of a general polyhedron cannot be bounded
        automatically, so the verification lowering raises ``ValueError`` when
        this is ``None``. Not used by ``prox``.

    Raises
    ------
    ValueError
        If ``A`` is not 2-D, or a ``LinearExpression`` ``b`` does not have
        dimension ``m``.

    See Also
    --------
    IndHalfSpace : Single half-space, with a closed-form projection.
    IndAffine : Equality-constrained counterpart $\{Ax = b\}$.

    Notes
    -----
    **Proximal operator.** The Euclidean projection onto the polyhedron,

    $$
        \operatorname{prox}_{\gamma f}(v)
        = \arg\min_{u}\ \tfrac{1}{2}\|u - v\|_2^2
        \quad\text{s.t.}\quad Au \le b,
    $$

    independent of $\gamma$.

    **Verification lowering.** ``prox_indpolyhedral`` encodes the KKT system

    $$
        u = v - A^\top \lambda,\qquad 0 \le \lambda \perp (b - Au) \ge 0
    $$

    with complementarity constraints. The MIP big-M encoding needs the finite
    bound ``dual_ub`` on $\lambda$; OBBT tightens it further when enabled.
    """

    def __init__(
        self,
        A: np.ndarray,
        b: float | np.ndarray | LinearExpression,
        dual_ub: float | np.ndarray | None = None,
    ):
        super().__init__()
        A = np.asarray(A, dtype=float)
        if A.ndim != 2:
            raise ValueError(f"A must be a 2-D matrix; got shape {A.shape}")
        m, n = A.shape

        if hasattr(b, "decomposition_dict"):  # LinearExpression parameter
            if b.ndim != m:
                raise ValueError(f"b dim {b.ndim} must match A rows {m}")
        else:
            b = np.broadcast_to(np.asarray(b, dtype=float), (m,)).copy()

        self.A = A
        self.b = b
        self.m = m
        self.n = n
        self.dual_ub = dual_ub

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Project ``arg`` onto the polyhedron $\{x : Ax \le b\}$.

        Solved numerically with CVXPY (Clarabel) as an independent reference; a
        ``LinearExpression`` ``b`` uses its constant value.

        Parameters
        ----------
        arg : np.ndarray
            Input point of dimension ``n``.
        gamma : float, default 1.0
            Step size; unused, since the projection is independent of $\gamma$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        arg = np.asarray(arg, dtype=float)
        b = self.b if isinstance(self.b, np.ndarray) else np.asarray(self.b.const, dtype=float)
        u = cp.Variable(self.n)
        # Pin CLARABEL: deterministic and accurate (~1e-7) across platforms, unlike
        # cvxpy's auto-selected default (OSQP/SCS) which can return ~1e-3 solutions.
        cp.Problem(cp.Minimize(cp.sum_squares(u - arg)), [self.A @ u <= b]).solve(
            solver=cp.CLARABEL
        )
        return np.asarray(u.value, dtype=float)

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Project arg onto the polyhedron \(\{x : Ax \le b\}\).

Solved numerically with CVXPY (Clarabel) as an independent reference; a LinearExpression b uses its constant value.

Parameters:

Name Type Description Default
arg ndarray

Input point of dimension n.

required
gamma float

Step size; unused, since the projection is independent of \(\gamma\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/ind_polyhedral.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Project ``arg`` onto the polyhedron $\{x : Ax \le b\}$.

    Solved numerically with CVXPY (Clarabel) as an independent reference; a
    ``LinearExpression`` ``b`` uses its constant value.

    Parameters
    ----------
    arg : np.ndarray
        Input point of dimension ``n``.
    gamma : float, default 1.0
        Step size; unused, since the projection is independent of $\gamma$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    arg = np.asarray(arg, dtype=float)
    b = self.b if isinstance(self.b, np.ndarray) else np.asarray(self.b.const, dtype=float)
    u = cp.Variable(self.n)
    # Pin CLARABEL: deterministic and accurate (~1e-7) across platforms, unlike
    # cvxpy's auto-selected default (OSQP/SCS) which can return ~1e-3 solutions.
    cp.Problem(cp.Minimize(cp.sum_squares(u - arg)), [self.A @ u <= b]).solve(
        solver=cp.CLARABEL
    )
    return np.asarray(u.value, dtype=float)

IndSimplex

Bases: ProxableFunction

Indicator of the scaled simplex \(\{x : x \ge 0,\ \mathbf{1}^\top x = a\}\).

Represents

\[ f(x) = I_{\{x \ge 0,\ \mathbf{1}^\top x = a\}}(x) = \begin{cases} 0 & \text{if } x \ge 0 \text{ and } \mathbf{1}^\top x = a \\ +\infty & \text{otherwise.} \end{cases} \]

For \(a = 1\) this is the probability simplex.

Parameters:

Name Type Description Default
a float or LinearExpression

Simplex sum \(a > 0\), or a dim-1 LinearExpression treated as a parameter by the verification lowering.

1.0

Raises:

Type Description
ValueError

If a is not positive, or a LinearExpression a is not dim-1.

See Also

IndPolyhedral : General polyhedron \(\{Ax \le b\}\).

Notes

Proximal operator. The Euclidean projection onto the simplex,

\[ \operatorname{prox}_{\gamma f}(v) = \arg\min_{u}\ \tfrac{1}{2}\|u - v\|_2^2 \quad\text{s.t.}\quad u \ge 0,\ \mathbf{1}^\top u = a, \]

independent of \(\gamma\).

Verification lowering. prox_indsimplex encodes the KKT system of this projection with complementarity constraints. The simplex is bounded, so the multiplier bounds are derived from the input box and no dual_ub is needed.

Source code in fomtools/proxable_functions/ind_simplex.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class IndSimplex(ProxableFunction):
    r"""
    Indicator of the scaled simplex $\{x : x \ge 0,\ \mathbf{1}^\top x = a\}$.

    Represents

    $$
        f(x) = I_{\{x \ge 0,\ \mathbf{1}^\top x = a\}}(x) = \begin{cases}
            0       & \text{if } x \ge 0 \text{ and } \mathbf{1}^\top x = a \\
            +\infty & \text{otherwise.}
        \end{cases}
    $$

    For $a = 1$ this is the probability simplex.

    Parameters
    ----------
    a : float or LinearExpression, default 1.0
        Simplex sum $a > 0$, or a dim-1 ``LinearExpression`` treated as a
        parameter by the verification lowering.

    Raises
    ------
    ValueError
        If ``a`` is not positive, or a ``LinearExpression`` ``a`` is not dim-1.

    See Also
    --------
    IndPolyhedral : General polyhedron $\{Ax \le b\}$.

    Notes
    -----
    **Proximal operator.** The Euclidean projection onto the simplex,

    $$
        \operatorname{prox}_{\gamma f}(v)
        = \arg\min_{u}\ \tfrac{1}{2}\|u - v\|_2^2
        \quad\text{s.t.}\quad u \ge 0,\ \mathbf{1}^\top u = a,
    $$

    independent of $\gamma$.

    **Verification lowering.** ``prox_indsimplex`` encodes the KKT system of
    this projection with complementarity constraints. The simplex is bounded,
    so the multiplier bounds are derived from the input box and no ``dual_ub``
    is needed.
    """

    def __init__(self, a: float | LinearExpression = 1.0):
        super().__init__()
        if hasattr(a, "decomposition_dict"):  # dim-1 LinearExpression parameter
            if a.ndim != 1:
                raise ValueError("a must be a scalar or a dim-1 LinearExpression")
        elif a <= 0:
            raise ValueError("a must be positive")
        self.a = a

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Project ``arg`` onto the simplex $\{x : x \ge 0,\ \mathbf{1}^\top x = a\}$.

        Solved numerically with CVXPY (Clarabel) as an independent reference; a
        ``LinearExpression`` ``a`` uses its constant value.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size; unused, since the projection is independent of $\gamma$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        arg = np.asarray(arg, dtype=float)
        a = float(self.a.const[0]) if hasattr(self.a, "decomposition_dict") else float(self.a)
        u = cp.Variable(arg.shape[0])
        # Pin CLARABEL: deterministic and accurate (~1e-7) across platforms, unlike
        # cvxpy's auto-selected default (OSQP/SCS) which can return ~1e-3 solutions.
        cp.Problem(cp.Minimize(cp.sum_squares(u - arg)), [u >= 0, cp.sum(u) == a]).solve(
            solver=cp.CLARABEL
        )
        return np.asarray(u.value, dtype=float)

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Project arg onto the simplex \(\{x : x \ge 0,\ \mathbf{1}^\top x = a\}\).

Solved numerically with CVXPY (Clarabel) as an independent reference; a LinearExpression a uses its constant value.

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size; unused, since the projection is independent of \(\gamma\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/ind_simplex.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Project ``arg`` onto the simplex $\{x : x \ge 0,\ \mathbf{1}^\top x = a\}$.

    Solved numerically with CVXPY (Clarabel) as an independent reference; a
    ``LinearExpression`` ``a`` uses its constant value.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size; unused, since the projection is independent of $\gamma$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    arg = np.asarray(arg, dtype=float)
    a = float(self.a.const[0]) if hasattr(self.a, "decomposition_dict") else float(self.a)
    u = cp.Variable(arg.shape[0])
    # Pin CLARABEL: deterministic and accurate (~1e-7) across platforms, unlike
    # cvxpy's auto-selected default (OSQP/SCS) which can return ~1e-3 solutions.
    cp.Problem(cp.Minimize(cp.sum_squares(u - arg)), [u >= 0, cp.sum(u) == a]).solve(
        solver=cp.CLARABEL
    )
    return np.asarray(u.value, dtype=float)

LabeledHingeLoss

Bases: ProxableFunction

Labeled hinge loss for binary classification.

Represents

\[ f(x) = \sum_i \max(0, 1 - y_i x_i), \]

with labels \(y \in \{-1, +1\}^n\) fixed at construction. Convex and piecewise linear; for \(y = \mathbf{1}\) it reduces to HingeLoss.

Parameters:

Name Type Description Default
y ndarray

1-D array of labels, each exactly \(\pm 1\) (\(\{0, 1\}\)-encoded labels are rejected).

required

Raises:

Type Description
ValueError

If y is not 1-D or has an entry other than \(\pm 1\).

See Also

HingeLoss : Unlabeled case \(y = \mathbf{1}\).

Notes

Proximal operator. The substitution \(w = y u\), \(z = y v\) (using \(y_i^2 = 1\)) reduces the labeled prox to the unlabeled one; back-substituting,

\[ \operatorname{prox}_{\gamma f}(v) = v + y \odot \mathrm{satlin}(1 - y \odot v;\, 0,\, \gamma). \]

Verification lowering. prox_labeledhingeloss posts a single SaturatedLinear clip of \(1 - y \odot v\) to \([0, \gamma]\); the element-wise scaling by \(y\) is a sparse diagonal matrix.

Source code in fomtools/proxable_functions/labeled_hinge_loss.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
class LabeledHingeLoss(ProxableFunction):
    r"""
    Labeled hinge loss for binary classification.

    Represents

    $$
        f(x) = \sum_i \max(0, 1 - y_i x_i),
    $$

    with labels $y \in \{-1, +1\}^n$ fixed at construction. Convex and
    piecewise linear; for $y = \mathbf{1}$ it reduces to ``HingeLoss``.

    Parameters
    ----------
    y : np.ndarray
        1-D array of labels, each exactly $\pm 1$ ($\{0, 1\}$-encoded labels
        are rejected).

    Raises
    ------
    ValueError
        If ``y`` is not 1-D or has an entry other than $\pm 1$.

    See Also
    --------
    HingeLoss : Unlabeled case $y = \mathbf{1}$.

    Notes
    -----
    **Proximal operator.** The substitution $w = y u$, $z = y v$ (using
    $y_i^2 = 1$) reduces the labeled prox to the unlabeled one; back-substituting,

    $$
        \operatorname{prox}_{\gamma f}(v)
            = v + y \odot \mathrm{satlin}(1 - y \odot v;\, 0,\, \gamma).
    $$

    **Verification lowering.** ``prox_labeledhingeloss`` posts a single
    ``SaturatedLinear`` clip of $1 - y \odot v$ to $[0, \gamma]$; the
    element-wise scaling by $y$ is a sparse diagonal matrix.
    """

    def __init__(self, y: np.ndarray):
        super().__init__()
        y = np.asarray(y, dtype=float)
        if y.ndim != 1:
            raise ValueError(f"y must be 1-D; got shape {y.shape}")
        if not np.all((y == 1.0) | (y == -1.0)):
            raise ValueError("y entries must be ±1")
        self.y = y

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \sum_i \max(0, 1 - y_i x_i)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.
        """
        return float(np.sum(np.maximum(0.0, 1.0 - self.y * x)))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Uses the closed form

        $$
            \operatorname{prox}_{\gamma f}(\text{arg})
            = \text{arg} + y \odot \mathrm{satlin}(1 - y \odot \text{arg};\, 0,\, \gamma).
        $$

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        return arg + self.y * np.minimum(np.maximum(1.0 - self.y * arg, 0.0), gamma)

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute a subgradient of $f$ at ``x``.

        Per coordinate,

        $$
            g_i = \begin{cases}
                -y_i    & y_i x_i < 1 \\
                0       & y_i x_i \geq 1,
            \end{cases}
        $$

        where $0$ is the canonical choice at the kink $y_i x_i = 1$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the subgradient.

        Returns
        -------
        np.ndarray
            A subgradient $g \in \partial f(x)$.
        """
        return -self.y * (self.y * x < 1).astype(float)

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \sum_i \max(0, 1 - y_i x_i)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Source code in fomtools/proxable_functions/labeled_hinge_loss.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \sum_i \max(0, 1 - y_i x_i)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.
    """
    return float(np.sum(np.maximum(0.0, 1.0 - self.y * x)))

grad

grad(x: ndarray) -> ndarray

Compute a subgradient of \(f\) at x.

Per coordinate,

\[ g_i = \begin{cases} -y_i & y_i x_i < 1 \\ 0 & y_i x_i \geq 1, \end{cases} \]

where \(0\) is the canonical choice at the kink \(y_i x_i = 1\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the subgradient.

required

Returns:

Type Description
ndarray

A subgradient \(g \in \partial f(x)\).

Source code in fomtools/proxable_functions/labeled_hinge_loss.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute a subgradient of $f$ at ``x``.

    Per coordinate,

    $$
        g_i = \begin{cases}
            -y_i    & y_i x_i < 1 \\
            0       & y_i x_i \geq 1,
        \end{cases}
    $$

    where $0$ is the canonical choice at the kink $y_i x_i = 1$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the subgradient.

    Returns
    -------
    np.ndarray
        A subgradient $g \in \partial f(x)$.
    """
    return -self.y * (self.y * x < 1).astype(float)

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Uses the closed form

\[ \operatorname{prox}_{\gamma f}(\text{arg}) = \text{arg} + y \odot \mathrm{satlin}(1 - y \odot \text{arg};\, 0,\, \gamma). \]

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/labeled_hinge_loss.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Uses the closed form

    $$
        \operatorname{prox}_{\gamma f}(\text{arg})
        = \text{arg} + y \odot \mathrm{satlin}(1 - y \odot \text{arg};\, 0,\, \gamma).
    $$

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    return arg + self.y * np.minimum(np.maximum(1.0 - self.y * arg, 0.0), gamma)

LeastSquares

Bases: Quadratic

Least-squares loss.

Represents

\[ f(x) = \tfrac{1}{2}\|Ax - b\|_2^2 = \tfrac{1}{2}\,x^\top (A^\top A)\, x - (A^\top b)^\top x + \tfrac{1}{2}\|b\|_2^2. \]

Convex and smooth. This is a Quadratic with \(Q = A^\top A\) and \(q = -A^\top b\) plus a constant, so prox and grad are inherited; only eval is overridden to add back \(\tfrac{1}{2}\|b\|_2^2\).

Parameters:

Name Type Description Default
A np.ndarray or scipy.sparse matrix

Data matrix of shape (m, n). Sparse input is kept sparse (CSR).

required
b float, np.ndarray, or LinearExpression

Target of shape (m,). A LinearExpression is treated as a parameter by the verification lowering.

required

Raises:

Type Description
ValueError

If A is not 2-D, or an array b has the wrong shape.

See Also

Quadratic : Parent class.

Notes

Proximal operator.

\[ (I + \gamma A^\top A)\, u = v + \gamma A^\top b. \]

Verification lowering. Registered to prox_quadratic: a single linear equality on a fresh iterate, as for Quadratic.

Source code in fomtools/proxable_functions/least_squares.py
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
class LeastSquares(Quadratic):
    r"""
    Least-squares loss.

    Represents

    $$
        f(x) = \tfrac{1}{2}\|Ax - b\|_2^2
             = \tfrac{1}{2}\,x^\top (A^\top A)\, x - (A^\top b)^\top x
               + \tfrac{1}{2}\|b\|_2^2.
    $$

    Convex and smooth. This is a ``Quadratic`` with $Q = A^\top A$ and
    $q = -A^\top b$ plus a constant, so ``prox`` and ``grad`` are inherited;
    only ``eval`` is overridden to add back $\tfrac{1}{2}\|b\|_2^2$.

    Parameters
    ----------
    A : np.ndarray or scipy.sparse matrix
        Data matrix of shape ``(m, n)``. Sparse input is kept sparse (CSR).
    b : float, np.ndarray, or LinearExpression
        Target of shape ``(m,)``. A ``LinearExpression`` is treated as a
        parameter by the verification lowering.

    Raises
    ------
    ValueError
        If ``A`` is not 2-D, or an array ``b`` has the wrong shape.

    See Also
    --------
    Quadratic : Parent class.

    Notes
    -----
    **Proximal operator.**

    $$
        (I + \gamma A^\top A)\, u = v + \gamma A^\top b.
    $$

    **Verification lowering.** Registered to ``prox_quadratic``: a single linear
    equality on a fresh iterate, as for ``Quadratic``.
    """

    def __init__(
        self,
        A: np.ndarray,
        b: float | np.ndarray | LinearExpression,
    ):
        # ``A`` may be dense or scipy.sparse; normalize sparse to CSR so ``Q = A^T A``
        # stays sparse and flows into the (sparse-aware) Quadratic base.
        if spa.issparse(A):
            A = A.tocsr()
        else:
            A = np.asarray(A, dtype=float)
        if A.ndim != 2:
            raise ValueError(f"A must be a 2-D matrix; got shape {A.shape}")
        m = A.shape[0]
        if isinstance(b, np.ndarray) and b.shape != (m,):
            raise ValueError(
                f"b must have shape ({m},) to match A's row count; got shape {b.shape}"
            )
        Q = A.T @ A  # symmetric PSD; sparse A -> sparse Q (Quadratic normalizes to CSR)
        q = -A.T @ b  # numeric b -> 1-D ndarray; LinearExpression b -> __rmatmul__
        super().__init__(Q=Q, q=q)
        self.A = A
        self.b = b

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \tfrac{1}{2}\|Ax - b\|_2^2$.

        Requires numeric ``b``; a ``LinearExpression`` ``b`` is supported only by
        the verification lowering.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.

        Raises
        ------
        TypeError
            If ``b`` is a ``LinearExpression``.
        """
        if hasattr(self.b, "decomposition_dict"):
            raise TypeError(
                "LeastSquares.eval requires numeric b; got LinearExpression. "
                "Symbolic b is supported only by the verification-side lowering."
            )
        r = self.A @ x - self.b
        return 0.5 * float(r @ r)

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \tfrac{1}{2}\|Ax - b\|_2^2\).

Requires numeric b; a LinearExpression b is supported only by the verification lowering.

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Raises:

Type Description
TypeError

If b is a LinearExpression.

Source code in fomtools/proxable_functions/least_squares.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \tfrac{1}{2}\|Ax - b\|_2^2$.

    Requires numeric ``b``; a ``LinearExpression`` ``b`` is supported only by
    the verification lowering.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.

    Raises
    ------
    TypeError
        If ``b`` is a ``LinearExpression``.
    """
    if hasattr(self.b, "decomposition_dict"):
        raise TypeError(
            "LeastSquares.eval requires numeric b; got LinearExpression. "
            "Symbolic b is supported only by the verification-side lowering."
        )
    r = self.A @ x - self.b
    return 0.5 * float(r @ r)

Linear

Bases: ProxableFunction

Linear cost.

Represents

\[ f(x) = c^\top x. \]

Convex and smooth.

Parameters:

Name Type Description Default
c float, np.ndarray, or LinearExpression

Cost vector. A scalar is broadcast; a LinearExpression is treated as a parameter by the verification lowering.

required
Notes

Proximal operator.

\[ \operatorname{prox}_{\gamma f}(v) = v - \gamma\, c. \]

Verification lowering. prox_linear is a pure affine shift; no step and no fresh leaf variables are introduced.

Source code in fomtools/proxable_functions/linear.py
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
class Linear(ProxableFunction):
    r"""
    Linear cost.

    Represents

    $$
        f(x) = c^\top x.
    $$

    Convex and smooth.

    Parameters
    ----------
    c : float, np.ndarray, or LinearExpression
        Cost vector. A scalar is broadcast; a ``LinearExpression`` is treated as
        a parameter by the verification lowering.

    Notes
    -----
    **Proximal operator.**

    $$
        \operatorname{prox}_{\gamma f}(v) = v - \gamma\, c.
    $$

    **Verification lowering.** ``prox_linear`` is a pure affine shift; no step
    and no fresh leaf variables are introduced.
    """

    def __init__(self, c: float | np.ndarray | LinearExpression):
        super().__init__()
        self.c = c

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = c^\top x$.

        Requires numeric ``c``; a ``LinearExpression`` ``c`` is supported only by
        the verification lowering.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.
        """
        return float(np.dot(self.c, x))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Uses the closed form
        $\operatorname{prox}_{\gamma f}(\text{arg}) = \text{arg} - \gamma c$.

        Requires numeric ``c``; a ``LinearExpression`` ``c`` is supported only by
        the verification lowering.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        return arg - gamma * self.c

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute the gradient of $f$ at ``x``.

        $\nabla f(x) = c$, broadcast to the shape of ``x``.

        Requires numeric ``c``; a ``LinearExpression`` ``c`` is supported only by
        the verification lowering.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the gradient.

        Returns
        -------
        np.ndarray
            The gradient $\nabla f(x)$.
        """
        return np.broadcast_to(self.c, x.shape).copy()

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = c^\top x\).

Requires numeric c; a LinearExpression c is supported only by the verification lowering.

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Source code in fomtools/proxable_functions/linear.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = c^\top x$.

    Requires numeric ``c``; a ``LinearExpression`` ``c`` is supported only by
    the verification lowering.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.
    """
    return float(np.dot(self.c, x))

grad

grad(x: ndarray) -> ndarray

Compute the gradient of \(f\) at x.

\(\nabla f(x) = c\), broadcast to the shape of x.

Requires numeric c; a LinearExpression c is supported only by the verification lowering.

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the gradient.

required

Returns:

Type Description
ndarray

The gradient \(\nabla f(x)\).

Source code in fomtools/proxable_functions/linear.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute the gradient of $f$ at ``x``.

    $\nabla f(x) = c$, broadcast to the shape of ``x``.

    Requires numeric ``c``; a ``LinearExpression`` ``c`` is supported only by
    the verification lowering.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the gradient.

    Returns
    -------
    np.ndarray
        The gradient $\nabla f(x)$.
    """
    return np.broadcast_to(self.c, x.shape).copy()

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Uses the closed form \(\operatorname{prox}_{\gamma f}(\text{arg}) = \text{arg} - \gamma c\).

Requires numeric c; a LinearExpression c is supported only by the verification lowering.

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/linear.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Uses the closed form
    $\operatorname{prox}_{\gamma f}(\text{arg}) = \text{arg} - \gamma c$.

    Requires numeric ``c``; a ``LinearExpression`` ``c`` is supported only by
    the verification lowering.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    return arg - gamma * self.c

NormInf

Bases: ProxableFunction

Scaled \(\ell_\infty\) norm.

Represents

\[ f(x) = \lambda \|x\|_\infty = \lambda \max_i |x_i|. \]

Convex and piecewise linear.

Parameters:

Name Type Description Default
lambd float or LinearExpression

Weight \(\lambda \ge 0\) (a single scalar, since the \(\ell_\infty\) norm is not separable), or a dim-1 LinearExpression treated as a parameter by the verification lowering.

1.0
dual_ub float or None

Upper bound forwarded to the underlying IndBallL1 projection. None auto-derives a valid bound from the input box; supply a value when that bound is loose (e.g. large asymmetric inputs).

None

Raises:

Type Description
ValueError

If lambd is negative, or a LinearExpression lambd is not dim-1.

See Also

IndBallL1 : Projection used by the Moreau decomposition below. NormL1 : The \(\ell_1\) norm.

Notes

Proximal operator. By Moreau decomposition, the prox is the residual of the projection onto the dual-norm ball,

\[ \operatorname{prox}_{\gamma f}(v) = v - \operatorname{proj}_{\{\|u\|_1 \le \gamma\lambda\}}(v). \]

Verification lowering. prox_norminf applies this identity, reusing prox_indballl1 with radius \(\gamma\lambda\) and the given dual_ub.

Source code in fomtools/proxable_functions/norm_inf.py
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
class NormInf(ProxableFunction):
    r"""
    Scaled $\ell_\infty$ norm.

    Represents

    $$
        f(x) = \lambda \|x\|_\infty = \lambda \max_i |x_i|.
    $$

    Convex and piecewise linear.

    Parameters
    ----------
    lambd : float or LinearExpression, default 1.0
        Weight $\lambda \ge 0$ (a single scalar, since the $\ell_\infty$ norm is
        not separable), or a dim-1 ``LinearExpression`` treated as a parameter
        by the verification lowering.
    dual_ub : float or None, default None
        Upper bound forwarded to the underlying ``IndBallL1`` projection.
        ``None`` auto-derives a valid bound from the input box; supply a value
        when that bound is loose (e.g. large asymmetric inputs).

    Raises
    ------
    ValueError
        If ``lambd`` is negative, or a ``LinearExpression`` ``lambd`` is not
        dim-1.

    See Also
    --------
    IndBallL1 : Projection used by the Moreau decomposition below.
    NormL1 : The $\ell_1$ norm.

    Notes
    -----
    **Proximal operator.** By Moreau decomposition, the prox is the residual of
    the projection onto the dual-norm ball,

    $$
        \operatorname{prox}_{\gamma f}(v)
        = v - \operatorname{proj}_{\{\|u\|_1 \le \gamma\lambda\}}(v).
    $$

    **Verification lowering.** ``prox_norminf`` applies this identity, reusing
    ``prox_indballl1`` with radius $\gamma\lambda$ and the given ``dual_ub``.
    """

    def __init__(
        self,
        lambd: float | LinearExpression = 1.0,
        dual_ub: float | None = None,
    ):
        super().__init__()
        if hasattr(lambd, "decomposition_dict"):  # dim-1 LinearExpression parameter
            if lambd.ndim != 1:
                raise ValueError("lambd must be a scalar or a dim-1 LinearExpression")
        elif lambd < 0:
            raise ValueError("lambd must be non-negative")
        self.lambd = lambd
        self.dual_ub = dual_ub

    def _lambd_value(self) -> float:
        """Concrete scalar weight (a parameter uses its stored constant value)."""
        if hasattr(self.lambd, "decomposition_dict"):
            return float(self.lambd.const[0])
        return float(self.lambd)

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \lambda \|x\|_\infty$.

        A ``LinearExpression`` ``lambd`` uses its constant value.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.
        """
        return self._lambd_value() * float(np.max(np.abs(np.asarray(x, dtype=float))))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Solved numerically with CVXPY (Clarabel) from the defining problem, as
        a reference independent of the Moreau identity used by the verification
        lowering; a ``LinearExpression`` ``lambd`` uses its constant value.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        arg = np.asarray(arg, dtype=float)
        u = cp.Variable(arg.shape[0])
        obj = 0.5 * cp.sum_squares(u - arg) + gamma * self._lambd_value() * cp.norm_inf(u)
        # Pin CLARABEL: deterministic and accurate (~1e-7) across platforms, unlike
        # cvxpy's auto-selected default (OSQP/SCS) which can return ~1e-3 solutions.
        cp.Problem(cp.Minimize(obj)).solve(solver=cp.CLARABEL)
        return np.asarray(u.value, dtype=float)

_lambd_value

_lambd_value() -> float

Concrete scalar weight (a parameter uses its stored constant value).

Source code in fomtools/proxable_functions/norm_inf.py
70
71
72
73
74
def _lambd_value(self) -> float:
    """Concrete scalar weight (a parameter uses its stored constant value)."""
    if hasattr(self.lambd, "decomposition_dict"):
        return float(self.lambd.const[0])
    return float(self.lambd)

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \lambda \|x\|_\infty\).

A LinearExpression lambd uses its constant value.

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Source code in fomtools/proxable_functions/norm_inf.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \lambda \|x\|_\infty$.

    A ``LinearExpression`` ``lambd`` uses its constant value.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.
    """
    return self._lambd_value() * float(np.max(np.abs(np.asarray(x, dtype=float))))

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Solved numerically with CVXPY (Clarabel) from the defining problem, as a reference independent of the Moreau identity used by the verification lowering; a LinearExpression lambd uses its constant value.

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/norm_inf.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Solved numerically with CVXPY (Clarabel) from the defining problem, as
    a reference independent of the Moreau identity used by the verification
    lowering; a ``LinearExpression`` ``lambd`` uses its constant value.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    arg = np.asarray(arg, dtype=float)
    u = cp.Variable(arg.shape[0])
    obj = 0.5 * cp.sum_squares(u - arg) + gamma * self._lambd_value() * cp.norm_inf(u)
    # Pin CLARABEL: deterministic and accurate (~1e-7) across platforms, unlike
    # cvxpy's auto-selected default (OSQP/SCS) which can return ~1e-3 solutions.
    cp.Problem(cp.Minimize(obj)).solve(solver=cp.CLARABEL)
    return np.asarray(u.value, dtype=float)

NormL1

Bases: ProxableFunction

Weighted \(\ell_1\) norm.

Represents

\[ f(x) = \sum_i \lambda_i |x_i|, \]

which is \(\lambda \|x\|_1\) when lambd is a scalar. Convex and piecewise linear.

Parameters:

Name Type Description Default
lambd float or ndarray

Non-negative weight: a scalar, or a 1-D array of per-coordinate weights.

1.0

Raises:

Type Description
ValueError

If any weight is negative, or lambd has more than one dimension.

See Also

ElasticNet : Adds a squared \(\ell_2\) term. NormInf : The \(\ell_\infty\) norm.

Notes

Proximal operator. Element-wise soft thresholding,

\[ \operatorname{prox}_{\gamma f}(v)_i = \operatorname{sign}(v_i)\,\max(|v_i| - \gamma\lambda_i,\, 0). \]

Verification lowering. prox_norml1 posts a single SoftThreshold step with threshold \(\gamma\lambda\).

Source code in fomtools/proxable_functions/norm_l1.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
class NormL1(ProxableFunction):
    r"""
    Weighted $\ell_1$ norm.

    Represents

    $$
        f(x) = \sum_i \lambda_i |x_i|,
    $$

    which is $\lambda \|x\|_1$ when ``lambd`` is a scalar. Convex and piecewise
    linear.

    Parameters
    ----------
    lambd : float or np.ndarray, default 1.0
        Non-negative weight: a scalar, or a 1-D array of per-coordinate weights.

    Raises
    ------
    ValueError
        If any weight is negative, or ``lambd`` has more than one dimension.

    See Also
    --------
    ElasticNet : Adds a squared $\ell_2$ term.
    NormInf : The $\ell_\infty$ norm.

    Notes
    -----
    **Proximal operator.** Element-wise soft thresholding,

    $$
        \operatorname{prox}_{\gamma f}(v)_i
        = \operatorname{sign}(v_i)\,\max(|v_i| - \gamma\lambda_i,\, 0).
    $$

    **Verification lowering.** ``prox_norml1`` posts a single ``SoftThreshold``
    step with threshold $\gamma\lambda$.
    """

    def __init__(self, lambd: float | np.ndarray = 1.0):
        super().__init__()
        if np.any(lambd < 0):
            raise ValueError("Lambda must be positive")
        if isinstance(lambd, np.ndarray) and lambd.ndim > 1:
            raise ValueError("Lambda cannot have dimension larger than 1")
        self.lambd = np.array(lambd)

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \sum_i \lambda_i |x_i|$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.

        Raises
        ------
        ValueError
            If ``x`` is not 1-D, or its length does not match an array ``lambd``.
        """
        if x.ndim >= 2:
            raise ValueError("Input should be one-dimensional")
        if self.lambd.ndim == 1 and x.shape[0] != self.lambd.shape[0]:
            raise ValueError("Input should be of the same size as Lambda")
        return np.sum(np.multiply(self.lambd, np.abs(x)))

    def prox(self, arg: np.ndarray, gamma: float | np.ndarray = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Applies element-wise soft thresholding by $\gamma \lambda$. With an
        array ``gamma``, this is the prox in the scaled metric

        $$
            \arg\!\min_{u} \left\{ f(u)
            + \frac{1}{2} \sum_i \frac{(u_i - \text{arg}_i)^2}{\gamma_i} \right\}.
        $$

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float or np.ndarray, default 1.0
            Step size $\gamma > 0$: a scalar, or an array broadcastable to the
            shape of ``arg``.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Raises
        ------
        ValueError
            If ``arg`` is not 1-D, or its shape does not match an array ``lambd``.
        """
        if arg.ndim >= 2:
            raise ValueError("Input should be one-dimensional")
        if self.lambd.ndim == 1 and arg.shape != self.lambd.shape:
            raise ValueError("Input should be of the same size as Lambda")
        gl = gamma * self.lambd
        return arg + np.where(arg <= -gl, gl, np.where(arg >= gl, -gl, -arg))

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute a subgradient of $f$ at ``x``.

        Per coordinate,

        $$
            g_i = \begin{cases}
                1   & x_i > 0 \\
                0   & x_i = 0 \\
                -1  & x_i < 0,
            \end{cases}
        $$

        where $0$ is the canonical choice at the kink $x_i = 0$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the subgradient.

        Returns
        -------
        np.ndarray
            A subgradient $g \in \partial f(x)$.
        """
        return np.sign(x)

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \sum_i \lambda_i |x_i|\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Raises:

Type Description
ValueError

If x is not 1-D, or its length does not match an array lambd.

Source code in fomtools/proxable_functions/norm_l1.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \sum_i \lambda_i |x_i|$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.

    Raises
    ------
    ValueError
        If ``x`` is not 1-D, or its length does not match an array ``lambd``.
    """
    if x.ndim >= 2:
        raise ValueError("Input should be one-dimensional")
    if self.lambd.ndim == 1 and x.shape[0] != self.lambd.shape[0]:
        raise ValueError("Input should be of the same size as Lambda")
    return np.sum(np.multiply(self.lambd, np.abs(x)))

grad

grad(x: ndarray) -> ndarray

Compute a subgradient of \(f\) at x.

Per coordinate,

\[ g_i = \begin{cases} 1 & x_i > 0 \\ 0 & x_i = 0 \\ -1 & x_i < 0, \end{cases} \]

where \(0\) is the canonical choice at the kink \(x_i = 0\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the subgradient.

required

Returns:

Type Description
ndarray

A subgradient \(g \in \partial f(x)\).

Source code in fomtools/proxable_functions/norm_l1.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute a subgradient of $f$ at ``x``.

    Per coordinate,

    $$
        g_i = \begin{cases}
            1   & x_i > 0 \\
            0   & x_i = 0 \\
            -1  & x_i < 0,
        \end{cases}
    $$

    where $0$ is the canonical choice at the kink $x_i = 0$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the subgradient.

    Returns
    -------
    np.ndarray
        A subgradient $g \in \partial f(x)$.
    """
    return np.sign(x)

prox

prox(arg: ndarray, gamma: float | ndarray = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Applies element-wise soft thresholding by \(\gamma \lambda\). With an array gamma, this is the prox in the scaled metric

\[ \arg\!\min_{u} \left\{ f(u) + \frac{1}{2} \sum_i \frac{(u_i - \text{arg}_i)^2}{\gamma_i} \right\}. \]

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float or ndarray

Step size \(\gamma > 0\): a scalar, or an array broadcastable to the shape of arg.

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Raises:

Type Description
ValueError

If arg is not 1-D, or its shape does not match an array lambd.

Source code in fomtools/proxable_functions/norm_l1.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def prox(self, arg: np.ndarray, gamma: float | np.ndarray = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Applies element-wise soft thresholding by $\gamma \lambda$. With an
    array ``gamma``, this is the prox in the scaled metric

    $$
        \arg\!\min_{u} \left\{ f(u)
        + \frac{1}{2} \sum_i \frac{(u_i - \text{arg}_i)^2}{\gamma_i} \right\}.
    $$

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float or np.ndarray, default 1.0
        Step size $\gamma > 0$: a scalar, or an array broadcastable to the
        shape of ``arg``.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Raises
    ------
    ValueError
        If ``arg`` is not 1-D, or its shape does not match an array ``lambd``.
    """
    if arg.ndim >= 2:
        raise ValueError("Input should be one-dimensional")
    if self.lambd.ndim == 1 and arg.shape != self.lambd.shape:
        raise ValueError("Input should be of the same size as Lambda")
    gl = gamma * self.lambd
    return arg + np.where(arg <= -gl, gl, np.where(arg >= gl, -gl, -arg))

Quadratic

Bases: ProxableFunction

Convex quadratic.

Represents

\[ f(x) = \tfrac{1}{2}\,x^\top Q x + q^\top x. \]

Convex and smooth.

Parameters:

Name Type Description Default
Q np.ndarray or scipy.sparse matrix

Symmetric positive semidefinite matrix of shape (n, n). Sparse input is kept sparse (CSR).

required
q float, np.ndarray, or LinearExpression

Linear term of shape (n,). A LinearExpression is treated as a parameter by the verification lowering.

required

Raises:

Type Description
ValueError

If Q is not square, not symmetric, or not positive semidefinite (up to a numerical tolerance), or an array q has the wrong shape.

See Also

LeastSquares : The case \(Q = A^\top A\), \(q = -A^\top b\). SqL2Norm : The case \(Q = I\), \(q = 0\).

Notes

Proximal operator. The unique solution \(u\) of the linear system

\[ (I + \gamma Q)\, u = v - \gamma q. \]

Verification lowering. prox_quadratic posts this single linear equality on a fresh iterate \(u\); no inverse is formed and no duals or complementarity constraints are introduced.

Source code in fomtools/proxable_functions/quadratic.py
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
class Quadratic(ProxableFunction):
    r"""
    Convex quadratic.

    Represents

    $$
        f(x) = \tfrac{1}{2}\,x^\top Q x + q^\top x.
    $$

    Convex and smooth.

    Parameters
    ----------
    Q : np.ndarray or scipy.sparse matrix
        Symmetric positive semidefinite matrix of shape ``(n, n)``. Sparse input
        is kept sparse (CSR).
    q : float, np.ndarray, or LinearExpression
        Linear term of shape ``(n,)``. A ``LinearExpression`` is treated as a
        parameter by the verification lowering.

    Raises
    ------
    ValueError
        If ``Q`` is not square, not symmetric, or not positive semidefinite
        (up to a numerical tolerance), or an array ``q`` has the wrong shape.

    See Also
    --------
    LeastSquares : The case $Q = A^\top A$, $q = -A^\top b$.
    SqL2Norm : The case $Q = I$, $q = 0$.

    Notes
    -----
    **Proximal operator.** The unique solution $u$ of the linear system

    $$
        (I + \gamma Q)\, u = v - \gamma q.
    $$

    **Verification lowering.** ``prox_quadratic`` posts this single linear
    equality on a fresh iterate $u$; no inverse is formed and no duals or
    complementarity constraints are introduced.
    """

    def __init__(
        self,
        Q: np.ndarray,
        q: float | np.ndarray | LinearExpression,
    ):
        super().__init__()
        # ``Q`` may be dense or scipy.sparse; normalize sparse to CSR (supports T,
        # arithmetic, and ``LinearExpression.__rmatmul__``), mirroring QuadraticProgram.
        if spa.issparse(Q):
            Q = Q.tocsr()
        else:
            Q = np.asarray(Q, dtype=float)
        if Q.ndim != 2 or Q.shape[0] != Q.shape[1]:
            raise ValueError(f"Q must be a square 2-D matrix; got shape {Q.shape}")
        n = Q.shape[0]
        # The prox (I + gamma*Q) x = ... is well posed only for symmetric PSD Q.
        sym_err = _sym_error(Q)
        if sym_err > _PSD_TOL:
            raise ValueError(f"Q must be symmetric; max|Q - Q^T| = {sym_err:.3e}")
        min_eig = _min_eig(Q)
        if min_eig < -_PSD_TOL:
            raise ValueError(
                f"Q must be positive semidefinite; smallest eigenvalue {min_eig:.3e} < -{_PSD_TOL:g}"
            )
        if isinstance(q, np.ndarray) and q.shape != (n,):
            raise ValueError(f"q must have shape ({n},) to match Q; got shape {q.shape}")
        self.Q = Q
        self.q = q
        self.n = n

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \tfrac{1}{2}\,x^\top Q x + q^\top x$.

        Requires numeric ``q``; a ``LinearExpression`` ``q`` is supported only by
        the verification lowering.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.

        Raises
        ------
        TypeError
            If ``q`` is a ``LinearExpression``.
        """
        if hasattr(self.q, "decomposition_dict"):
            raise TypeError(
                "Quadratic.eval requires numeric q; got LinearExpression. "
                "Symbolic q is supported only by the verification-side lowering."
            )
        return 0.5 * float(x @ self.Q @ x) + float(np.dot(self.q, x))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Solves $(I + \gamma Q)\, u = \text{arg} - \gamma q$ directly (sparse
        solve when ``Q`` is sparse).

        Requires numeric ``q``; a ``LinearExpression`` ``q`` is supported only by
        the verification lowering.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        rhs = arg - gamma * self.q
        if spa.issparse(self.Q):
            # Keep (I + gamma*Q) sparse and solve directly — a dense ``np.eye(n) +
            # gamma*Q`` would silently produce a numpy.matrix of the wrong shape.
            M = spa.eye(self.n, format="csr") + gamma * self.Q
            return spa.linalg.spsolve(M.tocsc(), rhs)
        M = np.eye(self.n) + gamma * self.Q
        return np.linalg.solve(M, rhs)

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute the gradient of $f$ at ``x``.

        $\nabla f(x) = Qx + q$.

        Requires numeric ``q``; a ``LinearExpression`` ``q`` is supported only by
        the verification lowering.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the gradient.

        Returns
        -------
        np.ndarray
            The gradient $\nabla f(x)$.

        Raises
        ------
        TypeError
            If ``q`` is a ``LinearExpression``.
        """
        if hasattr(self.q, "decomposition_dict"):
            raise TypeError(
                "Quadratic.grad requires numeric q; got LinearExpression. "
                "Symbolic q is supported only by the verification-side lowering."
            )
        return self.Q @ x + self.q

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \tfrac{1}{2}\,x^\top Q x + q^\top x\).

Requires numeric q; a LinearExpression q is supported only by the verification lowering.

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Raises:

Type Description
TypeError

If q is a LinearExpression.

Source code in fomtools/proxable_functions/quadratic.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \tfrac{1}{2}\,x^\top Q x + q^\top x$.

    Requires numeric ``q``; a ``LinearExpression`` ``q`` is supported only by
    the verification lowering.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.

    Raises
    ------
    TypeError
        If ``q`` is a ``LinearExpression``.
    """
    if hasattr(self.q, "decomposition_dict"):
        raise TypeError(
            "Quadratic.eval requires numeric q; got LinearExpression. "
            "Symbolic q is supported only by the verification-side lowering."
        )
    return 0.5 * float(x @ self.Q @ x) + float(np.dot(self.q, x))

grad

grad(x: ndarray) -> ndarray

Compute the gradient of \(f\) at x.

\(\nabla f(x) = Qx + q\).

Requires numeric q; a LinearExpression q is supported only by the verification lowering.

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the gradient.

required

Returns:

Type Description
ndarray

The gradient \(\nabla f(x)\).

Raises:

Type Description
TypeError

If q is a LinearExpression.

Source code in fomtools/proxable_functions/quadratic.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute the gradient of $f$ at ``x``.

    $\nabla f(x) = Qx + q$.

    Requires numeric ``q``; a ``LinearExpression`` ``q`` is supported only by
    the verification lowering.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the gradient.

    Returns
    -------
    np.ndarray
        The gradient $\nabla f(x)$.

    Raises
    ------
    TypeError
        If ``q`` is a ``LinearExpression``.
    """
    if hasattr(self.q, "decomposition_dict"):
        raise TypeError(
            "Quadratic.grad requires numeric q; got LinearExpression. "
            "Symbolic q is supported only by the verification-side lowering."
        )
    return self.Q @ x + self.q

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Solves \((I + \gamma Q)\, u = \text{arg} - \gamma q\) directly (sparse solve when Q is sparse).

Requires numeric q; a LinearExpression q is supported only by the verification lowering.

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/quadratic.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Solves $(I + \gamma Q)\, u = \text{arg} - \gamma q$ directly (sparse
    solve when ``Q`` is sparse).

    Requires numeric ``q``; a ``LinearExpression`` ``q`` is supported only by
    the verification lowering.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    rhs = arg - gamma * self.q
    if spa.issparse(self.Q):
        # Keep (I + gamma*Q) sparse and solve directly — a dense ``np.eye(n) +
        # gamma*Q`` would silently produce a numpy.matrix of the wrong shape.
        M = spa.eye(self.n, format="csr") + gamma * self.Q
        return spa.linalg.spsolve(M.tocsc(), rhs)
    M = np.eye(self.n) + gamma * self.Q
    return np.linalg.solve(M, rhs)

SoftNonNegative

Bases: ProxableFunction

One-sided quadratic penalty on negative coordinates.

Represents

\[ f(x) = \frac{1 - \alpha}{2\,\alpha}\, \sum_i \bigl(\min(x_i,\, 0)\bigr)^2. \]

Convex, smooth, and zero on the non-negative orthant. Its prox is a scaled LeakyReLU, and the slope \(\alpha\) interpolates between two familiar cases:

  • \(\alpha = 1\): \(f \equiv 0\), so the prox is the identity.
  • \(\alpha \to 0^+\): \(f\) tends to the indicator \(I_{\mathbb{R}_+^n}\), and the prox tends to ReLU.

Parameters:

Name Type Description Default
alpha float

LeakyReLU slope \(\alpha \in (0, 1]\).

required

Raises:

Type Description
ValueError

If alpha is not in \((0, 1]\).

See Also

IndNonNegative : The limit \(\alpha \to 0^+\).

Notes

Proximal operator.

\[ \operatorname{prox}_{\gamma f}(v) = \tilde\alpha(\gamma)\, v + \bigl(1 - \tilde\alpha(\gamma)\bigr)\operatorname{ReLU}(v), \qquad \tilde\alpha(\gamma) = \frac{\alpha}{\alpha + \gamma(1 - \alpha)}, \]

an element-wise LeakyReLU with effective slope \(\tilde\alpha(\gamma)\); at \(\gamma = 1\) the slope is \(\alpha\).

Verification lowering. prox_softnonnegative posts a single Relu step and combines it affinely with the input.

Source code in fomtools/proxable_functions/soft_nonnegative.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
class SoftNonNegative(ProxableFunction):
    r"""
    One-sided quadratic penalty on negative coordinates.

    Represents

    $$
        f(x) = \frac{1 - \alpha}{2\,\alpha}\,
               \sum_i \bigl(\min(x_i,\, 0)\bigr)^2.
    $$

    Convex, smooth, and zero on the non-negative orthant. Its prox is a scaled
    LeakyReLU, and the slope $\alpha$ interpolates between two familiar cases:

    - $\alpha = 1$: $f \equiv 0$, so the prox is the identity.
    - $\alpha \to 0^+$: $f$ tends to the indicator $I_{\mathbb{R}_+^n}$, and
      the prox tends to ReLU.

    Parameters
    ----------
    alpha : float
        LeakyReLU slope $\alpha \in (0, 1]$.

    Raises
    ------
    ValueError
        If ``alpha`` is not in $(0, 1]$.

    See Also
    --------
    IndNonNegative : The limit $\alpha \to 0^+$.

    Notes
    -----
    **Proximal operator.**

    $$
        \operatorname{prox}_{\gamma f}(v)
            = \tilde\alpha(\gamma)\, v
              + \bigl(1 - \tilde\alpha(\gamma)\bigr)\operatorname{ReLU}(v),
        \qquad
        \tilde\alpha(\gamma) = \frac{\alpha}{\alpha + \gamma(1 - \alpha)},
    $$

    an element-wise LeakyReLU with effective slope $\tilde\alpha(\gamma)$; at
    $\gamma = 1$ the slope is $\alpha$.

    **Verification lowering.** ``prox_softnonnegative`` posts a single ``Relu``
    step and combines it affinely with the input.
    """

    def __init__(self, alpha: float):
        super().__init__()
        if not (0.0 < alpha <= 1.0):
            raise ValueError(f"alpha must be in (0, 1]; got {alpha}")
        self.alpha = float(alpha)

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \frac{1-\alpha}{2\alpha}\sum_i \min(x_i, 0)^2$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.
        """
        coeff = (1.0 - self.alpha) / (2.0 * self.alpha)
        neg = np.maximum(0.0, -x)
        return float(coeff * np.sum(neg * neg))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Applies a LeakyReLU with effective slope
        $\tilde\alpha(\gamma) = \alpha / (\alpha + \gamma(1-\alpha))$.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        alpha_tilde = self.alpha / (self.alpha + gamma * (1.0 - self.alpha))
        return alpha_tilde * arg + (1.0 - alpha_tilde) * np.maximum(arg, 0.0)

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute the gradient of $f$ at ``x``.

        $\nabla f(x)_i = \frac{1-\alpha}{\alpha}\,\min(x_i, 0)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the gradient.

        Returns
        -------
        np.ndarray
            The gradient $\nabla f(x)$.
        """
        coeff = (1.0 - self.alpha) / self.alpha
        return coeff * np.minimum(x, 0.0)

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \frac{1-\alpha}{2\alpha}\sum_i \min(x_i, 0)^2\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Source code in fomtools/proxable_functions/soft_nonnegative.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \frac{1-\alpha}{2\alpha}\sum_i \min(x_i, 0)^2$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.
    """
    coeff = (1.0 - self.alpha) / (2.0 * self.alpha)
    neg = np.maximum(0.0, -x)
    return float(coeff * np.sum(neg * neg))

grad

grad(x: ndarray) -> ndarray

Compute the gradient of \(f\) at x.

\(\nabla f(x)_i = \frac{1-\alpha}{\alpha}\,\min(x_i, 0)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the gradient.

required

Returns:

Type Description
ndarray

The gradient \(\nabla f(x)\).

Source code in fomtools/proxable_functions/soft_nonnegative.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute the gradient of $f$ at ``x``.

    $\nabla f(x)_i = \frac{1-\alpha}{\alpha}\,\min(x_i, 0)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the gradient.

    Returns
    -------
    np.ndarray
        The gradient $\nabla f(x)$.
    """
    coeff = (1.0 - self.alpha) / self.alpha
    return coeff * np.minimum(x, 0.0)

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Applies a LeakyReLU with effective slope \(\tilde\alpha(\gamma) = \alpha / (\alpha + \gamma(1-\alpha))\).

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/soft_nonnegative.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Applies a LeakyReLU with effective slope
    $\tilde\alpha(\gamma) = \alpha / (\alpha + \gamma(1-\alpha))$.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    alpha_tilde = self.alpha / (self.alpha + gamma * (1.0 - self.alpha))
    return alpha_tilde * arg + (1.0 - alpha_tilde) * np.maximum(arg, 0.0)

SqL2Norm

Bases: ProxableFunction

Squared \(\ell_2\) norm.

Represents

\[ f(x) = \tfrac{1}{2}\|x\|_2^2 = \tfrac{1}{2}\sum_i x_i^2. \]

Convex and smooth.

See Also

Quadratic : General convex quadratic \(\tfrac{1}{2} x^\top Q x + q^\top x\).

Notes

Proximal operator.

\[ \operatorname{prox}_{\gamma f}(v) = \frac{v}{1 + \gamma}. \]

Verification lowering. prox_sql2norm is a pure scalar shrinkage; no step and no fresh leaf variables are introduced.

Source code in fomtools/proxable_functions/sq_l2_norm.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
class SqL2Norm(ProxableFunction):
    r"""
    Squared $\ell_2$ norm.

    Represents

    $$
        f(x) = \tfrac{1}{2}\|x\|_2^2 = \tfrac{1}{2}\sum_i x_i^2.
    $$

    Convex and smooth.

    See Also
    --------
    Quadratic : General convex quadratic $\tfrac{1}{2} x^\top Q x + q^\top x$.

    Notes
    -----
    **Proximal operator.**

    $$
        \operatorname{prox}_{\gamma f}(v) = \frac{v}{1 + \gamma}.
    $$

    **Verification lowering.** ``prox_sql2norm`` is a pure scalar shrinkage; no
    step and no fresh leaf variables are introduced.
    """

    def __init__(self):
        super().__init__()

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \tfrac{1}{2}\|x\|_2^2$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.
        """
        return 0.5 * float(np.sum(x * x))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Uses the closed form
        $\operatorname{prox}_{\gamma f}(\text{arg}) = \text{arg}/(1 + \gamma)$.

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        return arg / (1.0 + gamma)

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute the gradient of $f$ at ``x``.

        $\nabla f(x) = x$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the gradient.

        Returns
        -------
        np.ndarray
            The gradient $\nabla f(x)$.
        """
        return x.copy()

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \tfrac{1}{2}\|x\|_2^2\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Source code in fomtools/proxable_functions/sq_l2_norm.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \tfrac{1}{2}\|x\|_2^2$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.
    """
    return 0.5 * float(np.sum(x * x))

grad

grad(x: ndarray) -> ndarray

Compute the gradient of \(f\) at x.

\(\nabla f(x) = x\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the gradient.

required

Returns:

Type Description
ndarray

The gradient \(\nabla f(x)\).

Source code in fomtools/proxable_functions/sq_l2_norm.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute the gradient of $f$ at ``x``.

    $\nabla f(x) = x$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the gradient.

    Returns
    -------
    np.ndarray
        The gradient $\nabla f(x)$.
    """
    return x.copy()

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Uses the closed form \(\operatorname{prox}_{\gamma f}(\text{arg}) = \text{arg}/(1 + \gamma)\).

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/sq_l2_norm.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Uses the closed form
    $\operatorname{prox}_{\gamma f}(\text{arg}) = \text{arg}/(1 + \gamma)$.

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    return arg / (1.0 + gamma)

SumNegative

Bases: ProxableFunction

Sum of negative parts.

Represents

\[ f(x) = \sum_i \max(0, -x_i). \]

Convex and piecewise linear. Penalizes only the negative coordinates and leaves non-negative ones untouched.

See Also

SumPositive : Mirror image under \(x \mapsto -x\). NormL1 : Two-sided counterpart \(\lambda \|x\|_1\).

Notes

Proximal operator. Element-wise separable; per coordinate,

\[ \operatorname{prox}_{\gamma f}(v) = \begin{cases} v & v > 0 \\ 0 & -\gamma \le v \le 0 \\ v + \gamma & v < -\gamma, \end{cases} \]

which collapses to \(\operatorname{prox}_{\gamma f}(v) = v - \mathrm{satlin}(v;\, -\gamma,\, 0)\).

Verification lowering. prox_sumnegative posts a single SaturatedLinear clip to \([-\gamma, 0]\) and subtracts it from the input.

Source code in fomtools/proxable_functions/sum_negative.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
class SumNegative(ProxableFunction):
    r"""
    Sum of negative parts.

    Represents

    $$
        f(x) = \sum_i \max(0, -x_i).
    $$

    Convex and piecewise linear. Penalizes only the negative coordinates and
    leaves non-negative ones untouched.

    See Also
    --------
    SumPositive : Mirror image under $x \mapsto -x$.
    NormL1 : Two-sided counterpart $\lambda \|x\|_1$.

    Notes
    -----
    **Proximal operator.** Element-wise separable; per coordinate,

    $$
        \operatorname{prox}_{\gamma f}(v) = \begin{cases}
            v          & v > 0 \\
            0          & -\gamma \le v \le 0 \\
            v + \gamma & v < -\gamma,
        \end{cases}
    $$

    which collapses to
    $\operatorname{prox}_{\gamma f}(v) = v - \mathrm{satlin}(v;\, -\gamma,\, 0)$.

    **Verification lowering.** ``prox_sumnegative`` posts a single
    ``SaturatedLinear`` clip to $[-\gamma, 0]$ and subtracts it from the input.
    """

    def __init__(self):
        super().__init__()

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \sum_i \max(0, -x_i)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.
        """
        return float(np.sum(np.maximum(0.0, -x)))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Uses the closed form

        $$
            \operatorname{prox}_{\gamma f}(\text{arg})
            = \text{arg} - \mathrm{satlin}(\text{arg};\, -\gamma,\, 0).
        $$

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        return arg - np.minimum(np.maximum(arg, -gamma), 0.0)

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute a subgradient of $f$ at ``x``.

        Per coordinate,

        $$
            g_i = \begin{cases}
                -1  & x_i < 0 \\
                0   & x_i \geq 0,
            \end{cases}
        $$

        where $0 \in [-1, 0]$ is the canonical choice at the kink $x_i = 0$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the subgradient.

        Returns
        -------
        np.ndarray
            A subgradient $g \in \partial f(x)$.
        """
        return -(x < 0).astype(float)

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \sum_i \max(0, -x_i)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Source code in fomtools/proxable_functions/sum_negative.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \sum_i \max(0, -x_i)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.
    """
    return float(np.sum(np.maximum(0.0, -x)))

grad

grad(x: ndarray) -> ndarray

Compute a subgradient of \(f\) at x.

Per coordinate,

\[ g_i = \begin{cases} -1 & x_i < 0 \\ 0 & x_i \geq 0, \end{cases} \]

where \(0 \in [-1, 0]\) is the canonical choice at the kink \(x_i = 0\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the subgradient.

required

Returns:

Type Description
ndarray

A subgradient \(g \in \partial f(x)\).

Source code in fomtools/proxable_functions/sum_negative.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute a subgradient of $f$ at ``x``.

    Per coordinate,

    $$
        g_i = \begin{cases}
            -1  & x_i < 0 \\
            0   & x_i \geq 0,
        \end{cases}
    $$

    where $0 \in [-1, 0]$ is the canonical choice at the kink $x_i = 0$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the subgradient.

    Returns
    -------
    np.ndarray
        A subgradient $g \in \partial f(x)$.
    """
    return -(x < 0).astype(float)

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Uses the closed form

\[ \operatorname{prox}_{\gamma f}(\text{arg}) = \text{arg} - \mathrm{satlin}(\text{arg};\, -\gamma,\, 0). \]

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/sum_negative.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Uses the closed form

    $$
        \operatorname{prox}_{\gamma f}(\text{arg})
        = \text{arg} - \mathrm{satlin}(\text{arg};\, -\gamma,\, 0).
    $$

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    return arg - np.minimum(np.maximum(arg, -gamma), 0.0)

SumPositive

Bases: ProxableFunction

Sum of positive parts.

Represents

\[ f(x) = \sum_i \max(0, x_i). \]

Convex and piecewise linear. A one-sided variant of \(\|x\|_1\) that penalizes only the positive coordinates and leaves negative ones untouched.

See Also

SumNegative : Mirror image under \(x \mapsto -x\). NormL1 : Two-sided counterpart \(\lambda \|x\|_1\).

Notes

Proximal operator. Element-wise separable; per coordinate,

\[ \operatorname{prox}_{\gamma f}(v) = \begin{cases} v - \gamma & v > \gamma \\ 0 & 0 \le v \le \gamma \\ v & v < 0, \end{cases} \]

which collapses to \(\operatorname{prox}_{\gamma f}(v) = v - \mathrm{satlin}(v;\, 0,\, \gamma)\).

Verification lowering. prox_sumpositive posts a single SaturatedLinear clip to \([0, \gamma]\) and subtracts it from the input.

Source code in fomtools/proxable_functions/sum_positive.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
class SumPositive(ProxableFunction):
    r"""
    Sum of positive parts.

    Represents

    $$
        f(x) = \sum_i \max(0, x_i).
    $$

    Convex and piecewise linear. A one-sided variant of $\|x\|_1$ that
    penalizes only the positive coordinates and leaves negative ones
    untouched.

    See Also
    --------
    SumNegative : Mirror image under $x \mapsto -x$.
    NormL1 : Two-sided counterpart $\lambda \|x\|_1$.

    Notes
    -----
    **Proximal operator.** Element-wise separable; per coordinate,

    $$
        \operatorname{prox}_{\gamma f}(v) = \begin{cases}
            v - \gamma & v > \gamma \\
            0          & 0 \le v \le \gamma \\
            v          & v < 0,
        \end{cases}
    $$

    which collapses to
    $\operatorname{prox}_{\gamma f}(v) = v - \mathrm{satlin}(v;\, 0,\, \gamma)$.

    **Verification lowering.** ``prox_sumpositive`` posts a single
    ``SaturatedLinear`` clip to $[0, \gamma]$ and subtracts it from the input.
    """

    def __init__(self):
        super().__init__()

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \sum_i \max(0, x_i)$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.
        """
        return float(np.sum(np.maximum(0.0, x)))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Uses the closed form

        $$
            \operatorname{prox}_{\gamma f}(\text{arg})
            = \text{arg} - \mathrm{satlin}(\text{arg};\, 0,\, \gamma).
        $$

        Parameters
        ----------
        arg : np.ndarray
            Input point.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
        """
        return arg - np.minimum(np.maximum(arg, 0.0), gamma)

    def grad(self, x: np.ndarray) -> np.ndarray:
        r"""
        Compute a subgradient of $f$ at ``x``.

        Per coordinate,

        $$
            g_i = \begin{cases}
                1   & x_i > 0 \\
                0   & x_i \leq 0,
            \end{cases}
        $$

        where $0 \in [0, 1]$ is the canonical choice at the kink $x_i = 0$.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the subgradient.

        Returns
        -------
        np.ndarray
            A subgradient $g \in \partial f(x)$.
        """
        return (x > 0).astype(float)

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \sum_i \max(0, x_i)\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Source code in fomtools/proxable_functions/sum_positive.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \sum_i \max(0, x_i)$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.
    """
    return float(np.sum(np.maximum(0.0, x)))

grad

grad(x: ndarray) -> ndarray

Compute a subgradient of \(f\) at x.

Per coordinate,

\[ g_i = \begin{cases} 1 & x_i > 0 \\ 0 & x_i \leq 0, \end{cases} \]

where \(0 \in [0, 1]\) is the canonical choice at the kink \(x_i = 0\).

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the subgradient.

required

Returns:

Type Description
ndarray

A subgradient \(g \in \partial f(x)\).

Source code in fomtools/proxable_functions/sum_positive.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def grad(self, x: np.ndarray) -> np.ndarray:
    r"""
    Compute a subgradient of $f$ at ``x``.

    Per coordinate,

    $$
        g_i = \begin{cases}
            1   & x_i > 0 \\
            0   & x_i \leq 0,
        \end{cases}
    $$

    where $0 \in [0, 1]$ is the canonical choice at the kink $x_i = 0$.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the subgradient.

    Returns
    -------
    np.ndarray
        A subgradient $g \in \partial f(x)$.
    """
    return (x > 0).astype(float)

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Uses the closed form

\[ \operatorname{prox}_{\gamma f}(\text{arg}) = \text{arg} - \mathrm{satlin}(\text{arg};\, 0,\, \gamma). \]

Parameters:

Name Type Description Default
arg ndarray

Input point.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Source code in fomtools/proxable_functions/sum_positive.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Uses the closed form

    $$
        \operatorname{prox}_{\gamma f}(\text{arg})
        = \text{arg} - \mathrm{satlin}(\text{arg};\, 0,\, \gamma).
    $$

    Parameters
    ----------
    arg : np.ndarray
        Input point.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.
    """
    return arg - np.minimum(np.maximum(arg, 0.0), gamma)

TotalVariation1D

Bases: ProxableFunction

1-D total variation.

Represents

\[ f(x) = \lambda \|Dx\|_1 = \lambda \sum_i |x_{i+1} - x_i|, \]

where \(D\) is the first-difference operator (see diff_matrix). Convex and piecewise linear.

Parameters:

Name Type Description Default
lambd float or LinearExpression

Weight \(\lambda \ge 0\), or a dim-1 LinearExpression treated as a parameter by the verification lowering.

1.0

Raises:

Type Description
ValueError

If lambd is negative, or a LinearExpression lambd is not dim-1.

See Also

NormL1 : The case \(D = I\).

Notes

Proximal operator. The 1-D fused-lasso signal approximator,

\[ \operatorname{prox}_{\gamma f}(v) = \arg\min_x\ \tfrac{1}{2}\|x - v\|_2^2 + \gamma\lambda\|Dx\|_1. \]

For a signal of length \(n < 2\), \(f \equiv 0\) and the prox is the identity.

Verification lowering. prox_totalvariation1d encodes the KKT system

\[ x = v - D^\top u,\quad Dx = z^+ - z^-,\quad 0 \le (\gamma\lambda - u) \perp z^+ \ge 0,\quad 0 \le (\gamma\lambda + u) \perp z^- \ge 0 \]

with complementarity constraints. All variable bounds are derived from the data, so no dual_ub is needed.

Source code in fomtools/proxable_functions/total_variation_1d.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
class TotalVariation1D(ProxableFunction):
    r"""
    1-D total variation.

    Represents

    $$
        f(x) = \lambda \|Dx\|_1 = \lambda \sum_i |x_{i+1} - x_i|,
    $$

    where $D$ is the first-difference operator (see ``diff_matrix``). Convex
    and piecewise linear.

    Parameters
    ----------
    lambd : float or LinearExpression, default 1.0
        Weight $\lambda \ge 0$, or a dim-1 ``LinearExpression`` treated as a
        parameter by the verification lowering.

    Raises
    ------
    ValueError
        If ``lambd`` is negative, or a ``LinearExpression`` ``lambd`` is not
        dim-1.

    See Also
    --------
    NormL1 : The case $D = I$.

    Notes
    -----
    **Proximal operator.** The 1-D fused-lasso signal approximator,

    $$
        \operatorname{prox}_{\gamma f}(v)
        = \arg\min_x\ \tfrac{1}{2}\|x - v\|_2^2 + \gamma\lambda\|Dx\|_1.
    $$

    For a signal of length $n < 2$, $f \equiv 0$ and the prox is the identity.

    **Verification lowering.** ``prox_totalvariation1d`` encodes the KKT system

    $$
        x = v - D^\top u,\quad Dx = z^+ - z^-,\quad
        0 \le (\gamma\lambda - u) \perp z^+ \ge 0,\quad
        0 \le (\gamma\lambda + u) \perp z^- \ge 0
    $$

    with complementarity constraints. All variable bounds are derived from the
    data, so no ``dual_ub`` is needed.
    """

    def __init__(self, lambd: float | LinearExpression = 1.0):
        super().__init__()
        if hasattr(lambd, "decomposition_dict"):  # dim-1 LinearExpression parameter
            if lambd.ndim != 1:
                raise ValueError("lambd must be a scalar or a dim-1 LinearExpression")
        elif lambd < 0:
            raise ValueError("lambd must be non-negative")
        self.lambd = lambd

    def _lambd_value(self) -> float:
        if hasattr(self.lambd, "decomposition_dict"):
            return float(self.lambd.const[0])
        return float(self.lambd)

    def eval(self, x: np.ndarray) -> float:
        r"""
        Evaluate $f(x) = \lambda \sum_i |x_{i+1} - x_i|$.

        A ``LinearExpression`` ``lambd`` uses its constant value.

        Parameters
        ----------
        x : np.ndarray
            Point at which to evaluate the function.

        Returns
        -------
        float
            The function value $f(x)$.
        """
        x = np.asarray(x, dtype=float)
        return self._lambd_value() * float(np.sum(np.abs(np.diff(x))))

    def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
        r"""
        Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Solved numerically with CVXPY (Clarabel) as an independent reference; a
        ``LinearExpression`` ``lambd`` uses its constant value.

        Parameters
        ----------
        arg : np.ndarray
            Input signal.
        gamma : float, default 1.0
            Step size $\gamma > 0$.

        Returns
        -------
        np.ndarray
            The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.

        Warns
        -----
        UserWarning
            If ``arg`` has fewer than 2 entries; ``arg`` is then returned
            unchanged.
        """
        arg = np.asarray(arg, dtype=float)
        n = arg.shape[0]
        if n < 2:
            warnings.warn(_SHORT_SIGNAL_MSG.format(n=n), UserWarning, stacklevel=2)
            return arg.copy()  # TV of a scalar is 0 -> identity
        x = cp.Variable(n)
        tau = gamma * self._lambd_value()
        obj = 0.5 * cp.sum_squares(x - arg) + tau * cp.norm1(diff_matrix(n) @ x)
        # Pin CLARABEL: deterministic and accurate (~1e-7) across platforms, unlike
        # cvxpy's auto-selected default (OSQP/SCS) which can return ~1e-3 solutions.
        cp.Problem(cp.Minimize(obj)).solve(solver=cp.CLARABEL)
        return np.asarray(x.value, dtype=float)

eval

eval(x: ndarray) -> float

Evaluate \(f(x) = \lambda \sum_i |x_{i+1} - x_i|\).

A LinearExpression lambd uses its constant value.

Parameters:

Name Type Description Default
x ndarray

Point at which to evaluate the function.

required

Returns:

Type Description
float

The function value \(f(x)\).

Source code in fomtools/proxable_functions/total_variation_1d.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def eval(self, x: np.ndarray) -> float:
    r"""
    Evaluate $f(x) = \lambda \sum_i |x_{i+1} - x_i|$.

    A ``LinearExpression`` ``lambd`` uses its constant value.

    Parameters
    ----------
    x : np.ndarray
        Point at which to evaluate the function.

    Returns
    -------
    float
        The function value $f(x)$.
    """
    x = np.asarray(x, dtype=float)
    return self._lambd_value() * float(np.sum(np.abs(np.diff(x))))

prox

prox(arg: ndarray, gamma: float = 1.0) -> ndarray

Compute \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Solved numerically with CVXPY (Clarabel) as an independent reference; a LinearExpression lambd uses its constant value.

Parameters:

Name Type Description Default
arg ndarray

Input signal.

required
gamma float

Step size \(\gamma > 0\).

1.0

Returns:

Type Description
ndarray

The proximal point \(\operatorname{prox}_{\gamma f}(\text{arg})\).

Warns:

Type Description
UserWarning

If arg has fewer than 2 entries; arg is then returned unchanged.

Source code in fomtools/proxable_functions/total_variation_1d.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def prox(self, arg: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    r"""
    Compute $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Solved numerically with CVXPY (Clarabel) as an independent reference; a
    ``LinearExpression`` ``lambd`` uses its constant value.

    Parameters
    ----------
    arg : np.ndarray
        Input signal.
    gamma : float, default 1.0
        Step size $\gamma > 0$.

    Returns
    -------
    np.ndarray
        The proximal point $\operatorname{prox}_{\gamma f}(\text{arg})$.

    Warns
    -----
    UserWarning
        If ``arg`` has fewer than 2 entries; ``arg`` is then returned
        unchanged.
    """
    arg = np.asarray(arg, dtype=float)
    n = arg.shape[0]
    if n < 2:
        warnings.warn(_SHORT_SIGNAL_MSG.format(n=n), UserWarning, stacklevel=2)
        return arg.copy()  # TV of a scalar is 0 -> identity
    x = cp.Variable(n)
    tau = gamma * self._lambd_value()
    obj = 0.5 * cp.sum_squares(x - arg) + tau * cp.norm1(diff_matrix(n) @ x)
    # Pin CLARABEL: deterministic and accurate (~1e-7) across platforms, unlike
    # cvxpy's auto-selected default (OSQP/SCS) which can return ~1e-3 solutions.
    cp.Problem(cp.Minimize(obj)).solve(solver=cp.CLARABEL)
    return np.asarray(x.value, dtype=float)

Sets and projections

Use vp.project(set_instance, arg).

Set

Base class for convex sets onto which we can project.

A Set is the user-facing, math-named counterpart of an indicator ProxableFunction: for a closed convex set \(C\), the Euclidean projection onto \(C\) is exactly the proximal operator of its indicator \(I_C\),

\[ \Pi_C(x) = \operatorname{prox}_{I_C}(x) = \arg\min_{u \in C} \tfrac12 \lVert u - x \rVert_2^2 . \]

Each concrete set therefore only needs to know its indicator (indicator); projection and membership reuse the indicator's existing numerical prox/eval. The verifier-level fomtools.verifier.Verifier.project is a thin wrapper that calls prox on this indicator, so Set adds no new lowering logic.

Subclasses form an inheritance tree mirroring the mathematics (e.g. NonNegative is a Polyhedron), but each concrete set overrides indicator to return its most specific indicator so the efficient backend lowering is preserved.

Source code in fomtools/sets/set.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
class Set:
    r"""
    Base class for convex sets onto which we can project.

    A `Set` is the user-facing, math-named counterpart of an *indicator*
    `ProxableFunction`: for a
    closed convex set $C$, the Euclidean projection onto $C$ is exactly the proximal
    operator of its indicator $I_C$,

    $$
        \Pi_C(x) = \operatorname{prox}_{I_C}(x)
                 = \arg\min_{u \in C} \tfrac12 \lVert u - x \rVert_2^2 .
    $$

    Each concrete set therefore only needs to know its indicator
    (`indicator`); projection and membership reuse the indicator's existing
    numerical ``prox``/``eval``. The verifier-level `fomtools.verifier.Verifier.project`
    is a thin wrapper that calls ``prox`` on this indicator, so `Set` adds no
    new lowering logic.

    Subclasses form an inheritance tree mirroring the mathematics (e.g.
    `NonNegative` is a `Polyhedron`),
    but each concrete set overrides `indicator` to return its *most specific*
    indicator so the efficient backend lowering is preserved.
    """

    def indicator(self) -> ProxableFunction:
        """
        Return the indicator `ProxableFunction` whose prox is the Euclidean
        projection onto this set.

        Raises
        ------
        NotImplementedError
            Raised by the base class; concrete sets must implement this.
        """
        raise NotImplementedError

    def project(self, x: np.ndarray) -> np.ndarray:
        r"""
        Euclidean projection of the point ``x`` onto this set, $\Pi_C(x)$.

        Delegates to the indicator's numerical ``prox`` (projection is
        ``prox`` of the indicator).

        Notes
        -----
        A fresh indicator object is constructed on each call (construction is cheap —
        it only stores the set's parameters). If you are projecting/checking membership
        in a tight loop, build the indicator once and reuse it:
        ``ind = my_set.indicator(); ind.prox(x1); ind.prox(x2); ...``.
        """
        return self.indicator().prox(np.asarray(x, dtype=float))

    def contains(self, x: np.ndarray, tol: float = 1e-6, rtol: float = 1e-5) -> bool:
        r"""
        Return ``True`` if ``x`` lies in the set (within tolerance).

        Tested via projection idempotence: for a closed convex set,
        $x \in C \iff \Pi_C(x) = x$. This relies only on the indicator's ``prox``
        (which every indicator implements), so it works uniformly across all sets.

        The comparison uses `numpy.allclose` semantics,
        $\lVert \Pi_C(x) - x \rVert \le \text{tol} + \text{rtol}\,\lvert x \rvert$
        (elementwise): ``tol`` is the absolute tolerance and ``rtol`` adds a relative,
        **scale-aware** slack so large-magnitude points are not held to an unrealistic
        absolute bound. Pass ``rtol=0.0`` for a pure absolute check. For cvxpy-solved
        sets (Simplex, L1Ball, Polyhedron) the round-trip inherits the solver's
        precision (~``PROX_ATOL``), so ``tol`` may need to be loosened accordingly.

        Parameters
        ----------
        x : np.ndarray
            Point to test.
        tol : float, default 1e-6
            Absolute tolerance.
        rtol : float, default 1e-5
            Relative tolerance (scale-aware term).

        Notes
        -----
        Calls `project`, which constructs a fresh indicator each call; for tight
        membership-checking loops reuse a single indicator (see `project`).
        """
        x = np.asarray(x, dtype=float)
        return bool(np.allclose(self.project(x), x, atol=tol, rtol=rtol))

    def _repr_fields(self) -> str:
        """Constructor-argument string for `__repr__`; overridden per set."""
        return ""

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self._repr_fields()})"

contains

contains(x: ndarray, tol: float = 1e-06, rtol: float = 1e-05) -> bool

Return True if x lies in the set (within tolerance).

Tested via projection idempotence: for a closed convex set, \(x \in C \iff \Pi_C(x) = x\). This relies only on the indicator's prox (which every indicator implements), so it works uniformly across all sets.

The comparison uses numpy.allclose semantics, \(\lVert \Pi_C(x) - x \rVert \le \text{tol} + \text{rtol}\,\lvert x \rvert\) (elementwise): tol is the absolute tolerance and rtol adds a relative, scale-aware slack so large-magnitude points are not held to an unrealistic absolute bound. Pass rtol=0.0 for a pure absolute check. For cvxpy-solved sets (Simplex, L1Ball, Polyhedron) the round-trip inherits the solver's precision (~PROX_ATOL), so tol may need to be loosened accordingly.

Parameters:

Name Type Description Default
x ndarray

Point to test.

required
tol float

Absolute tolerance.

1e-6
rtol float

Relative tolerance (scale-aware term).

1e-5
Notes

Calls project, which constructs a fresh indicator each call; for tight membership-checking loops reuse a single indicator (see project).

Source code in fomtools/sets/set.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def contains(self, x: np.ndarray, tol: float = 1e-6, rtol: float = 1e-5) -> bool:
    r"""
    Return ``True`` if ``x`` lies in the set (within tolerance).

    Tested via projection idempotence: for a closed convex set,
    $x \in C \iff \Pi_C(x) = x$. This relies only on the indicator's ``prox``
    (which every indicator implements), so it works uniformly across all sets.

    The comparison uses `numpy.allclose` semantics,
    $\lVert \Pi_C(x) - x \rVert \le \text{tol} + \text{rtol}\,\lvert x \rvert$
    (elementwise): ``tol`` is the absolute tolerance and ``rtol`` adds a relative,
    **scale-aware** slack so large-magnitude points are not held to an unrealistic
    absolute bound. Pass ``rtol=0.0`` for a pure absolute check. For cvxpy-solved
    sets (Simplex, L1Ball, Polyhedron) the round-trip inherits the solver's
    precision (~``PROX_ATOL``), so ``tol`` may need to be loosened accordingly.

    Parameters
    ----------
    x : np.ndarray
        Point to test.
    tol : float, default 1e-6
        Absolute tolerance.
    rtol : float, default 1e-5
        Relative tolerance (scale-aware term).

    Notes
    -----
    Calls `project`, which constructs a fresh indicator each call; for tight
    membership-checking loops reuse a single indicator (see `project`).
    """
    x = np.asarray(x, dtype=float)
    return bool(np.allclose(self.project(x), x, atol=tol, rtol=rtol))

indicator

indicator() -> ProxableFunction

Return the indicator ProxableFunction whose prox is the Euclidean projection onto this set.

Raises:

Type Description
NotImplementedError

Raised by the base class; concrete sets must implement this.

Source code in fomtools/sets/set.py
46
47
48
49
50
51
52
53
54
55
56
def indicator(self) -> ProxableFunction:
    """
    Return the indicator `ProxableFunction` whose prox is the Euclidean
    projection onto this set.

    Raises
    ------
    NotImplementedError
        Raised by the base class; concrete sets must implement this.
    """
    raise NotImplementedError

project

project(x: ndarray) -> ndarray

Euclidean projection of the point x onto this set, \(\Pi_C(x)\).

Delegates to the indicator's numerical prox (projection is prox of the indicator).

Notes

A fresh indicator object is constructed on each call (construction is cheap — it only stores the set's parameters). If you are projecting/checking membership in a tight loop, build the indicator once and reuse it: ind = my_set.indicator(); ind.prox(x1); ind.prox(x2); ....

Source code in fomtools/sets/set.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def project(self, x: np.ndarray) -> np.ndarray:
    r"""
    Euclidean projection of the point ``x`` onto this set, $\Pi_C(x)$.

    Delegates to the indicator's numerical ``prox`` (projection is
    ``prox`` of the indicator).

    Notes
    -----
    A fresh indicator object is constructed on each call (construction is cheap —
    it only stores the set's parameters). If you are projecting/checking membership
    in a tight loop, build the indicator once and reuse it:
    ``ind = my_set.indicator(); ind.prox(x1); ind.prox(x2); ...``.
    """
    return self.indicator().prox(np.asarray(x, dtype=float))

Polyhedron

Bases: Set

The polyhedron \(\{x : A x \le b\}\).

Backed by IndPolyhedral, whose prox solves the projection via a KKT system. dual_ub bounds the inequality multipliers and is required by the MIP/SDP lowering.

Parameters:

Name Type Description Default
A ndarray

Constraint matrix (m, n).

None
b ndarray or LinearExpression

Right-hand side (dim m).

None
dual_ub float

Upper bound on the inequality dual variables (needed by the backend).

None
Source code in fomtools/sets/polyhedron.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Polyhedron(Set):
    r"""
    The polyhedron $\{x : A x \le b\}$.

    Backed by `IndPolyhedral`,
    whose prox solves the projection via a KKT system. ``dual_ub`` bounds the
    inequality multipliers and is required by the MIP/SDP lowering.

    Parameters
    ----------
    A : np.ndarray
        Constraint matrix ``(m, n)``.
    b : np.ndarray or LinearExpression
        Right-hand side (dim ``m``).
    dual_ub : float, optional
        Upper bound on the inequality dual variables (needed by the backend).
    """

    def __init__(self, A=None, b=None, dual_ub=None):
        self.A = A
        self.b = b
        self.dual_ub = dual_ub

    def indicator(self) -> IndPolyhedral:
        return IndPolyhedral(self.A, self.b, dual_ub=self.dual_ub)

    def _repr_fields(self) -> str:
        return f"A={_fmt(self.A)}, b={_fmt(self.b)}, dual_ub={self.dual_ub!r}"

HalfSpace

Bases: Polyhedron

The half-space \(\{x : a^\top x \le b\}\) (a single linear inequality).

A Polyhedron with a single row: A = a (shape (1, n)) and RHS vector b = [b]. The inherited self.A/self.b therefore carry the usual polyhedral meaning (self.b is the length-1 RHS vector, not a bare scalar); the scalar normal is kept on self.a. Backed by IndHalfSpace, which projects in closed form (no dual bound needed).

Parameters:

Name Type Description Default
a ndarray

Normal vector (nonzero).

required
b float

Offset (the scalar right-hand side of a^T x <= b).

required
Source code in fomtools/sets/polyhedron.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class HalfSpace(Polyhedron):
    r"""
    The half-space $\{x : a^\top x \le b\}$ (a single linear inequality).

    A `Polyhedron` with a single row: ``A = a`` (shape ``(1, n)``) and RHS vector
    ``b = [b]``. The inherited ``self.A``/``self.b`` therefore carry the usual polyhedral
    meaning (``self.b`` is the length-1 RHS vector, *not* a bare scalar); the scalar normal
    is kept on ``self.a``. Backed by
    `IndHalfSpace`, which projects in
    closed form (no dual bound needed).

    Parameters
    ----------
    a : np.ndarray
        Normal vector (nonzero).
    b : float
        Offset (the scalar right-hand side of ``a^T x <= b``).
    """

    def __init__(self, a, b):
        a = np.asarray(a, dtype=float)
        # A half-space is a polyhedron with one row; store A/b with the inherited meaning.
        super().__init__(A=a.reshape(1, -1), b=np.array([float(b)]))
        self.a = a

    def indicator(self) -> IndHalfSpace:
        return IndHalfSpace(self.a, float(self.b[0]))

    def _repr_fields(self) -> str:
        return f"a={_fmt(self.a)}, b={float(self.b[0])!r}"

Simplex

Bases: Polyhedron

The scaled probability simplex \(\{x : x \ge 0,\ \mathbf{1}^\top x = a\}\).

Backed by IndSimplex.

Parameters:

Name Type Description Default
a float or LinearExpression

Total mass (a > 0).

1.0
Source code in fomtools/sets/polyhedron.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
class Simplex(Polyhedron):
    r"""
    The scaled probability simplex $\{x : x \ge 0,\ \mathbf{1}^\top x = a\}$.

    Backed by `IndSimplex`.

    Parameters
    ----------
    a : float or LinearExpression, default 1.0
        Total mass (``a > 0``).
    """

    def __init__(self, a=1.0):
        super().__init__()
        self.a = a

    def indicator(self) -> IndSimplex:
        return IndSimplex(self.a)

    def _repr_fields(self) -> str:
        return f"a={self.a!r}"

Box

Bases: Polyhedron

The axis-aligned box \(\{x : l \le x \le u\}\) (element-wise).

A Polyhedron specialization that projects by element-wise clipping; backed by IndBox.

Parameters:

Name Type Description Default
l float, np.ndarray, or LinearExpression

Lower and upper bounds (scalars broadcast to the dimension of the input).

required
u float, np.ndarray, or LinearExpression

Lower and upper bounds (scalars broadcast to the dimension of the input).

required
Source code in fomtools/sets/box.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Box(Polyhedron):
    r"""
    The axis-aligned box $\{x : l \le x \le u\}$ (element-wise).

    A `Polyhedron` specialization that projects by
    element-wise clipping; backed by
    `IndBox`.

    Parameters
    ----------
    l, u : float, np.ndarray, or LinearExpression
        Lower and upper bounds (scalars broadcast to the dimension of the input).
    """

    def __init__(self, l, u):
        super().__init__()
        self.l = l
        self.u = u

    def indicator(self) -> IndBox:
        return IndBox(self.l, self.u)

    def _repr_fields(self) -> str:
        return f"l={_fmt(self.l)}, u={_fmt(self.u)}"

NonNegative

Bases: Box

The non-negative orthant \(\{x : x \ge 0\}\), i.e. the box \([0, +\infty)\).

A parameterless Box specialization (l = 0, u = +\infty); backed by IndNonNegative, which projects via a single ReLU (more efficient than the generic box/polyhedron path). The box bounds are stored for the inherited Box/Polyhedron contract but are not used by indicator.

Source code in fomtools/sets/box.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class NonNegative(Box):
    r"""
    The non-negative orthant $\{x : x \ge 0\}$, i.e. the box $[0, +\infty)$.

    A parameterless `Box` specialization (``l = 0``, ``u = +\infty``); backed by
    `IndNonNegative`, which projects
    via a single ReLU (more efficient than the generic box/polyhedron path). The box
    bounds are stored for the inherited `Box`/`Polyhedron` contract but are
    not used by `indicator`.
    """

    def __init__(self):
        super().__init__(0.0, np.inf)

    def indicator(self) -> IndNonNegative:
        return IndNonNegative()

    def _repr_fields(self) -> str:
        return ""  # parameterless constructor

NonPositive

Bases: Box

The non-positive orthant \(\{x : x \le 0\}\), i.e. the box \((-\infty, 0]\).

A parameterless Box specialization (l = -\infty, u = 0); backed by IndNonPositive. The box bounds are stored for the inherited contract but are not used by indicator.

Source code in fomtools/sets/box.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class NonPositive(Box):
    r"""
    The non-positive orthant $\{x : x \le 0\}$, i.e. the box $(-\infty, 0]$.

    A parameterless `Box` specialization (``l = -\infty``, ``u = 0``); backed by
    `IndNonPositive`. The box bounds
    are stored for the inherited contract but are not used by `indicator`.
    """

    def __init__(self):
        super().__init__(-np.inf, 0.0)

    def indicator(self) -> IndNonPositive:
        return IndNonPositive()

    def _repr_fields(self) -> str:
        return ""  # parameterless constructor

Point

Bases: Box

The singleton \(\{p\}\) (a degenerate box with \(l = u = p\)).

Backed by IndPoint; projection is the constant map \(x \mapsto p\).

Parameters:

Name Type Description Default
p float or ndarray

The single point (scalars broadcast to the dimension of the input).

required
Source code in fomtools/sets/box.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class Point(Box):
    r"""
    The singleton $\{p\}$ (a degenerate box with $l = u = p$).

    Backed by `IndPoint`; projection
    is the constant map $x \mapsto p$.

    Parameters
    ----------
    p : float or np.ndarray
        The single point (scalars broadcast to the dimension of the input).
    """

    def __init__(self, p):
        super().__init__(p, p)  # degenerate box l = u = p
        self.p = p

    def indicator(self) -> IndPoint:
        return IndPoint(self.p)

    def _repr_fields(self) -> str:
        return f"p={_fmt(self.p)}"

L1Ball

Bases: Set

The \(\ell_1\)-ball \(\{x : \lVert x \rVert_1 \le r\}\).

Backed by IndBallL1, whose projection lifts auxiliary variables and solves a KKT system. dual_ub bounds the inequality multipliers for the MIP/SDP lowering. Sits directly under Set (the lifting does not fit the plain Ax <= b mold).

Parameters:

Name Type Description Default
r float or LinearExpression

Radius (r >= 0).

required
dual_ub float

Upper bound on the inequality dual variables (needed by the backend).

None
Source code in fomtools/sets/ball.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class L1Ball(Set):
    r"""
    The $\ell_1$-ball $\{x : \lVert x \rVert_1 \le r\}$.

    Backed by `IndBallL1`, whose
    projection lifts auxiliary variables and solves a KKT system. ``dual_ub`` bounds
    the inequality multipliers for the MIP/SDP lowering. Sits directly under
    `Set` (the lifting does not fit the plain ``Ax <= b`` mold).

    Parameters
    ----------
    r : float or LinearExpression
        Radius (``r >= 0``).
    dual_ub : float, optional
        Upper bound on the inequality dual variables (needed by the backend).
    """

    def __init__(self, r, dual_ub=None):
        self.r = r
        self.dual_ub = dual_ub

    def indicator(self) -> IndBallL1:
        return IndBallL1(self.r, dual_ub=self.dual_ub)

    def _repr_fields(self) -> str:
        return f"r={self.r!r}, dual_ub={self.dual_ub!r}"

LinfBall

Bases: Box

The \(\ell_\infty\)-ball \(\{x : \lVert x \rVert_\infty \le r\}\), i.e. the box \([-r, r]\).

A Box specialization; backed by IndBallLinf, which projects by clipping to \([-r, r]\).

Parameters:

Name Type Description Default
r float

Radius (r >= 0).

required
Source code in fomtools/sets/ball.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class LinfBall(Box):
    r"""
    The $\ell_\infty$-ball $\{x : \lVert x \rVert_\infty \le r\}$, i.e. the box
    $[-r, r]$.

    A `Box` specialization; backed by
    `IndBallLinf`, which projects
    by clipping to $[-r, r]$.

    Parameters
    ----------
    r : float
        Radius (``r >= 0``).
    """

    def __init__(self, r):
        super().__init__(-r, r)  # the box [-r, r]
        self.r = r

    def indicator(self) -> IndBallLinf:
        return IndBallLinf(self.r)

    def _repr_fields(self) -> str:
        return f"r={self.r!r}"

AffineSubspace

Bases: Set

The affine subspace \(\{x : A x = b\}\).

Backed by IndAffine, which projects onto the equality set in closed form. Named AffineSubspace to avoid colliding with the Affine step. Sits directly under Set (it is an equality, not an Ax <= b inequality polyhedron).

Parameters:

Name Type Description Default
A ndarray

Constraint matrix (m, n) (full row rank).

required
b float, np.ndarray, or LinearExpression

Right-hand side (dim m).

required
Source code in fomtools/sets/affine_subspace.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class AffineSubspace(Set):
    r"""
    The affine subspace $\{x : A x = b\}$.

    Backed by `IndAffine`, which
    projects onto the equality set in closed form. Named ``AffineSubspace`` to avoid
    colliding with the `Affine` *step*. Sits directly
    under `Set` (it is an equality, not an ``Ax <= b``
    inequality polyhedron).

    Parameters
    ----------
    A : np.ndarray
        Constraint matrix ``(m, n)`` (full row rank).
    b : float, np.ndarray, or LinearExpression
        Right-hand side (dim ``m``).
    """

    def __init__(self, A, b):
        self.A = A
        self.b = b

    def indicator(self) -> IndAffine:
        return IndAffine(self.A, self.b)

    def _repr_fields(self) -> str:
        return f"A={_fmt(self.A)}, b={_fmt(self.b)}"