forked from sabbadino-ca/production-allocation
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinputvalidations.py
More file actions
190 lines (174 loc) · 7.58 KB
/
inputvalidations.py
File metadata and controls
190 lines (174 loc) · 7.58 KB
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
from typing import Iterable, List, Sequence
from datatypes import ObjectiveSpec
def ObjectiveSpecValidation(objectives: Iterable[ObjectiveSpec]) -> None:
"""Validate that each ObjectiveSpec has integer values only.
Parameters
----------
objectives : Iterable[ObjectiveSpec]
Collection of additive objective specifications to validate.
Raises
------
TypeError
If any element is not an ObjectiveSpec or values is not a list-like of integers.
ValueError
If any value inside ObjectiveSpec.values is not an integer.
"""
for spec in objectives:
if not isinstance(spec, ObjectiveSpec):
raise TypeError("All objectives must be instances of ObjectiveSpec.")
vals = spec.values
# Allow any iterable but prefer list/tuple checks for performance
if not isinstance(vals, (list, tuple)):
try:
vals = list(vals)
except Exception as exc:
raise TypeError("ObjectiveSpec.values must be an iterable of integers.") from exc
# Check integer-ness strictly
for idx, v in enumerate(vals):
if not isinstance(v, int):
raise ValueError(f"Objective '{spec.name}' has a non-integer value at index {idx}: {v!r}")
def validate_input(
*,
item_names: List[str],
model_names: List[str],
item_quantities: List[int],
order_ids: List[str],
plant_names: List[str],
plant_quantity_capacities: List[int],
allowed_model_names_per_plant: Sequence[Iterable[str]],
additive_objectives: Iterable[ObjectiveSpec],
min_allowed_qty_of_items_same_model_name_in_a_plant: int,
soft_min_qty_of_items_same_model_name_in_a_plant: int,
w_soft_min_qty_of_items_same_model_name_in_a_plant: float = 0.0,
# New: solver configuration
time_limit_s: float | int | None = None,
) -> None:
"""Validate all inputs for the CP-SAT optimizer.
Parameters
----------
item_names : List[str]
Unique identifiers for items (must be unique and match lengths).
model_names : List[str]
Model name per item, same length as item_names.
item_quantities : List[int]
Non-negative integer quantity per item, same length as item_names.
order_ids : List[str]
Order identifier per item (string), same length as item_names/model_names.
plant_names : List[str]
Plant labels (must be unique, aligned with capacities and allowed sets).
plant_quantity_capacities : List[int]
Strictly positive integer capacity per plant.
allowed_model_names_per_plant : Sequence[Iterable[str]]
For each plant, the set/iterable of model names it can produce.
additive_objectives : Iterable[ObjectiveSpec]
Collection of additive objective specs; each must have integer values.
min_allowed_qty_of_items_same_model_name_in_a_plant : int
Global hard minimum quantity per (model_name, plant); 0 disables.
soft_min_qty_of_items_same_model_name_in_a_plant : int
Global soft minimum quantity per (model_name, plant); 0 disables.
w_soft_min_qty_of_items_same_model_name_in_a_plant : float, optional
Penalty weight for the soft minimum; 0 disables. Used only when combined with
soft_min_qty_of_items_same_model_name_in_a_plant > 0. Defaults to 0.0.
time_limit_s : float | int | None, optional
CP-SAT time limit in seconds. When provided, it must be a finite number strictly > 0.
Raises
------
AssertionError
If core shape/type constraints fail (e.g., mismatched lengths, nonpositive capacities).
ValueError
If names are not unique or objective values are not integers.
"""
# Basic shape validations
n = len(model_names)
if not (len(item_names) == n and len(item_quantities) == n and len(order_ids) == n):
raise AssertionError("item_names/model_names/item_quantities/order_ids mismatch")
if len(set(item_names)) != n:
raise ValueError("item_names must be unique.")
# order_ids basic typing
if not all(isinstance(oid, str) for oid in order_ids):
raise AssertionError("order_ids must be a list of strings with length matching items")
P = len(plant_quantity_capacities)
if not (P > 0 and len(allowed_model_names_per_plant) == P and len(plant_names) == P):
raise AssertionError("plant arrays must align")
if len(set(plant_names)) != P:
raise ValueError("plant_names must be unique.")
# Types and ranges
if not all(isinstance(c, int) and c > 0 for c in plant_quantity_capacities):
raise AssertionError("plant capacities must be positive ints")
if not all(isinstance(q, int) and q >= 0 for q in item_quantities):
raise AssertionError("item quantities must be nonnegative ints")
if not (isinstance(min_allowed_qty_of_items_same_model_name_in_a_plant, int) and min_allowed_qty_of_items_same_model_name_in_a_plant >= 0):
raise AssertionError(
"min_allowed_qty_of_items_same_model_name_in_a_plant must be a non-negative int"
)
if not (isinstance(soft_min_qty_of_items_same_model_name_in_a_plant, int) and soft_min_qty_of_items_same_model_name_in_a_plant >= 0):
raise AssertionError(
"soft_min_qty_of_items_same_model_name_in_a_plant must be a non-negative int"
)
# Weight must be a finite, non-negative number
try:
w_soft = float(w_soft_min_qty_of_items_same_model_name_in_a_plant)
except Exception as exc:
raise AssertionError(
"w_soft_min_qty_of_items_same_model_name_in_a_plant must be a number"
) from exc
if not (w_soft >= 0.0):
raise AssertionError("w_soft_min_qty_of_items_same_model_name_in_a_plant must be non-negative")
# Mutual exclusivity: HARD min vs SOFT min must not be enabled together
if min_allowed_qty_of_items_same_model_name_in_a_plant > 0:
if soft_min_qty_of_items_same_model_name_in_a_plant != 0 or w_soft != 0.0:
raise AssertionError(
"Mutual exclusivity violated: when hard min > 0, both soft_min_qty_of_items_same_model_name_in_a_plant and "
"w_soft_min_qty_of_items_same_model_name_in_a_plant must be 0."
)
# Vice versa: if either soft min threshold or its weight is enabled, hard min must be 0
if soft_min_qty_of_items_same_model_name_in_a_plant > 0 or w_soft > 0.0:
if min_allowed_qty_of_items_same_model_name_in_a_plant != 0:
raise AssertionError(
"Mutual exclusivity violated: when soft min (threshold or weight) is enabled, "
"min_allowed_qty_of_items_same_model_name_in_a_plant must be 0."
)
# Objectives validation (ensures integer values)
ObjectiveSpecValidation(additive_objectives)
# time_limit_s validation (strictly positive if provided)
if time_limit_s is None:
raise AssertionError("time_limit_s must be provided")
try:
limit = float(time_limit_s)
except Exception as exc:
raise AssertionError("time_limit_s must be a number if provided") from exc
if not (limit > 0.0):
raise AssertionError("time_limit_s must be > 0 seconds")
# Validate allowed models per plant
for p_idx, models in enumerate(allowed_model_names_per_plant):
# Disallow passing a single string directly
if isinstance(models, (str, bytes)):
raise TypeError(
f"allowed_model_names_per_plant[{p_idx}] must be an iterable of strings, not a string."
)
try:
lst = list(models)
except Exception as exc:
raise TypeError(
f"allowed_model_names_per_plant[{p_idx}] must be an iterable of strings."
) from exc
# Non-empty requirement
if len(lst) == 0:
raise ValueError(
f"allowed models for plant '{plant_names[p_idx]}' must be non-empty."
)
# All entries must be strings and non-blank
for j, m in enumerate(lst):
if not isinstance(m, str):
raise TypeError(
f"allowed model at plant '{plant_names[p_idx]}' index {j} must be str, got {type(m).__name__}."
)
if m.strip() == "":
raise ValueError(
f"allowed model at plant '{plant_names[p_idx]}' index {j} must not be empty/blank."
)
# Duplicates check (preserve case-sensitivity as-is)
if len(set(lst)) != len(lst):
raise ValueError(
f"allowed models for plant '{plant_names[p_idx]}' contain duplicates: {lst}"
)