-
Notifications
You must be signed in to change notification settings - Fork 4
/
constraints.py
152 lines (109 loc) · 4.59 KB
/
constraints.py
1
2
3
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
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
"""
Implementation of various constraints, whose violations must be tested by the metrics.
"""
from typing import Iterable
from abc import ABC, abstractmethod
import numpy as np
from numpy.core.multiarray import array as array
class Constraint(ABC):
@abstractmethod
def violation(self, samples: np.array, scaling: float) -> np.array:
"""
Function to calculate a combined metric that considers a region of interest and constraints.
This method should be invariant to the number of variables in the samples.
Also, this method should take advantage of the scaling parameter to ensure that it is scale invariant.
Note: If we multiply all numbers in a task by 2, the scaling parameter will be divided by 2.
Parameters:
----------
samples: np.array
The forecast values. (n_samples, variable dimensions)
scaling: float
Scaling factor for both the samples, and the constraint values.
Returns:
--------
violation: np.array
By how much the constraint is violated in each sample.
If 0, it is satisfied. Always >= 0. (n_samples)
"""
pass
def __call__(self, samples: np.array, scaling: float) -> np.array:
return self.violation(samples, scaling)
@abstractmethod
def __repr__(self) -> str:
pass
class ListConstraint(Constraint):
"""
A list of constraints, each of which having to be satisfied.
"""
def __init__(self, constraints: Iterable[Constraint]) -> None:
super().__init__()
self.constraints = list(constraints)
def __getitem__(self, index) -> Constraint:
return self.constraints[index]
def __len__(self) -> int:
return len(self.constraints)
def violation(self, samples: np.array, scaling: float) -> np.array:
return sum(
constraint.violation(samples, scaling) for constraint in self.constraints
)
def __repr__(self) -> str:
sub_constraints = ", ".join(str(constraint) for constraint in self.constraints)
return f"ListConstraint([{sub_constraints}])"
class MaxConstraint(Constraint):
"""
Constraint of the form: x_i <= threshold
"""
def __init__(self, threshold: float) -> None:
super().__init__()
self.threshold = threshold
def violation(self, samples: np.array, scaling: float) -> float:
scaled_samples = scaling * samples
scaled_threshold = scaling * self.threshold
return (scaled_samples - scaled_threshold).clip(min=0).mean(axis=1)
def __repr__(self) -> str:
return f"MaxConstraint(max={self.threshold})"
class MinConstraint(Constraint):
"""
Constraint of the form: x_i >= threshold
"""
def __init__(self, threshold: float) -> None:
super().__init__()
self.threshold = threshold
def violation(self, samples: np.array, scaling: float) -> float:
scaled_samples = scaling * samples
scaled_threshold = scaling * self.threshold
return (scaled_threshold - scaled_samples).clip(min=0).mean(axis=1)
def __repr__(self) -> str:
return f"MinConstraint(min={self.threshold})"
class VariableMaxConstraint(Constraint):
"""
Constraint of the form: x_i <= threshold_i for i in S
Where S doesn't have to be the full range
"""
def __init__(self, indices: np.array, thresholds: np.array) -> None:
super().__init__()
assert len(indices) == len(
thresholds
), f"Unequal dimensions for indices and thresholds: {len(indices)} != {len(thresholds)}"
self.indices = indices
self.thresholds = thresholds
def violation(self, samples: np.array, scaling: float) -> float:
indexed_samples = samples[:, self.indices]
scaled_samples = scaling * indexed_samples
scaled_thresholds = scaling * self.thresholds
return (scaled_samples - scaled_thresholds[None, :]).clip(min=0).mean(axis=1)
def __repr__(self) -> str:
return f"VariableMaxConstraint(indices={list(self.indices)}, thresholds={list(self.thresholds)})"
class MeanEqualityConstraint(Constraint):
"""
Constraint of the form: mean(x) == value
"""
def __init__(self, value: float) -> None:
super().__init__()
self.value = value
def violation(self, samples: np.array, scaling: float) -> float:
scaled_samples = scaling * samples
scaled_value = scaling * self.value
return abs(scaled_value - scaled_samples.mean(axis=1))
def __repr__(self) -> str:
return f"MeanEqualityConstraint(mean={self.value})"