Coverage for src / cvx / core / model.py: 100%
18 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-13 06:46 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-13 06:46 +0000
1# Copyright 2023 Stanford University Convex Optimization Group
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Abstract parametric model with named numpy-array parameters.
16This module provides the :class:`Model` abstract base class for any
17parametric optimization model whose data can be stored as named
18:class:`~cvx.core.parameter.Parameter` objects and updated independently
19of the problem structure.
21Example:
22 Concrete subclasses implement ``estimate`` and ``update``:
24 >>> import numpy as np
25 >>> from cvx.risk.sample import SampleCovariance
26 >>> model = SampleCovariance(num=3)
27 >>> model.update(
28 ... cov=np.eye(3),
29 ... lower_assets=np.zeros(3),
30 ... upper_assets=np.ones(3)
31 ... )
32 >>> isinstance(model.estimate(np.ones(3) / 3), float)
33 True
35"""
37from __future__ import annotations
39from abc import ABC, abstractmethod
40from dataclasses import dataclass, field
41from typing import Any
43import numpy as np
45from cvx.core.parameter import Parameter
46from cvx.core.variable import Variable
49@dataclass
50class Model(ABC):
51 """Abstract base class for parametric optimization models.
53 A ``Model`` holds a dictionary of named :class:`~cvx.core.parameter.Parameter`
54 objects (numpy arrays) that can be updated between solver calls without
55 reconstructing the optimization problem structure. Subclasses implement
56 :meth:`estimate` to evaluate the model output and :meth:`update` to refresh
57 the parameter values.
59 Attributes:
60 parameter: Dictionary of named :class:`~cvx.core.parameter.Parameter`
61 objects. Parameters can be updated independently of the problem
62 structure, making it cheap to solve a sequence of related problems.
64 Example:
65 >>> import numpy as np
66 >>> from cvx.risk.sample import SampleCovariance
67 >>> model = SampleCovariance(num=2)
68 >>> model.update(
69 ... cov=np.array([[1.0, 0.5], [0.5, 2.0]]),
70 ... lower_assets=np.zeros(2),
71 ... upper_assets=np.ones(2)
72 ... )
73 >>> 'chol' in model.parameter
74 True
76 Parameters are :class:`~cvx.core.parameter.Parameter` instances:
78 >>> from cvx.core.parameter import Parameter
79 >>> isinstance(model.parameter['chol'], Parameter)
80 True
82 """
84 parameter: dict[str, Parameter] = field(default_factory=dict)
85 """Dictionary of named parameters."""
87 @abstractmethod
88 def estimate(self, weights: np.ndarray, **kwargs: Any) -> float:
89 """Evaluate the model for the given input vector.
91 Args:
92 weights: Input vector (e.g. portfolio weights or factor exposures).
93 **kwargs: Additional keyword arguments for subclass-specific logic.
95 Returns:
96 Scalar float result (e.g. risk, cost, or objective contribution).
98 Example:
99 >>> import numpy as np
100 >>> from cvx.risk.sample import SampleCovariance
101 >>> model = SampleCovariance(num=2)
102 >>> model.update(
103 ... cov=np.array([[1.0, 0.0], [0.0, 1.0]]),
104 ... lower_assets=np.zeros(2),
105 ... upper_assets=np.ones(2)
106 ... )
107 >>> isinstance(model.estimate(np.array([0.5, 0.5])), float)
108 True
110 """
112 @abstractmethod
113 def update(self, **kwargs: Any) -> None:
114 """Update the parameter values from keyword arguments.
116 Updating parameters allows the same problem structure to be re-solved
117 with new data without any symbolic re-compilation.
119 Args:
120 **kwargs: New parameter values. The expected keys depend on the
121 concrete subclass.
123 Example:
124 >>> import numpy as np
125 >>> from cvx.risk.sample import SampleCovariance
126 >>> model = SampleCovariance(num=3)
127 >>> model.update(
128 ... cov=np.eye(3),
129 ... lower_assets=np.zeros(3),
130 ... upper_assets=np.ones(3)
131 ... )
133 """
135 def solve_minrisk(
136 self,
137 weights: Variable,
138 base: np.ndarray,
139 extra_constraints: list[tuple[np.ndarray, float | None, float | None]],
140 y_var: Variable | None = None,
141 ) -> tuple[float | None, float | None, str]:
142 """Solve the minimum-risk problem for this model.
144 Subclasses that support direct Clarabel solving override this method.
145 """
146 msg = f"{type(self).__name__} does not implement solve_minrisk"
147 raise NotImplementedError(msg)