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

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. 

15 

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. 

20 

21Example: 

22 Concrete subclasses implement ``estimate`` and ``update``: 

23 

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 

34 

35""" 

36 

37from __future__ import annotations 

38 

39from abc import ABC, abstractmethod 

40from dataclasses import dataclass, field 

41from typing import Any 

42 

43import numpy as np 

44 

45from cvx.core.parameter import Parameter 

46from cvx.core.variable import Variable 

47 

48 

49@dataclass 

50class Model(ABC): 

51 """Abstract base class for parametric optimization models. 

52 

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. 

58 

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. 

63 

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 

75 

76 Parameters are :class:`~cvx.core.parameter.Parameter` instances: 

77 

78 >>> from cvx.core.parameter import Parameter 

79 >>> isinstance(model.parameter['chol'], Parameter) 

80 True 

81 

82 """ 

83 

84 parameter: dict[str, Parameter] = field(default_factory=dict) 

85 """Dictionary of named parameters.""" 

86 

87 @abstractmethod 

88 def estimate(self, weights: np.ndarray, **kwargs: Any) -> float: 

89 """Evaluate the model for the given input vector. 

90 

91 Args: 

92 weights: Input vector (e.g. portfolio weights or factor exposures). 

93 **kwargs: Additional keyword arguments for subclass-specific logic. 

94 

95 Returns: 

96 Scalar float result (e.g. risk, cost, or objective contribution). 

97 

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 

109 

110 """ 

111 

112 @abstractmethod 

113 def update(self, **kwargs: Any) -> None: 

114 """Update the parameter values from keyword arguments. 

115 

116 Updating parameters allows the same problem structure to be re-solved 

117 with new data without any symbolic re-compilation. 

118 

119 Args: 

120 **kwargs: New parameter values. The expected keys depend on the 

121 concrete subclass. 

122 

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 ... ) 

132 

133 """ 

134 

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. 

143 

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)