python-constraint2 2.0.0__cp313-cp313-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,786 @@
1
+ """Module containing the code for constraint definitions."""
2
+
3
+ from constraint.domain import Unassigned
4
+ from typing import Callable, Union, Optional
5
+ from collections.abc import Sequence
6
+
7
+ class Constraint:
8
+ """Abstract base class for constraints."""
9
+
10
+ def __call__(self, variables: Sequence, domains: dict, assignments: dict, forwardcheck=False):
11
+ """Perform the constraint checking.
12
+
13
+ If the forwardcheck parameter is not false, besides telling if
14
+ the constraint is currently broken or not, the constraint
15
+ implementation may choose to hide values from the domains of
16
+ unassigned variables to prevent them from being used, and thus
17
+ prune the search space.
18
+
19
+ Args:
20
+ variables (sequence): :py:class:`Variables` affected by that constraint,
21
+ in the same order provided by the user
22
+ domains (dict): Dictionary mapping variables to their
23
+ domains
24
+ assignments (dict): Dictionary mapping assigned variables to
25
+ their current assumed value
26
+ forwardcheck: Boolean value stating whether forward checking
27
+ should be performed or not
28
+
29
+ Returns:
30
+ bool: Boolean value stating if this constraint is currently
31
+ broken or not
32
+ """
33
+ return True
34
+
35
+ def preProcess(self, variables: Sequence, domains: dict, constraints: list[tuple], vconstraints: dict):
36
+ """Preprocess variable domains.
37
+
38
+ This method is called before starting to look for solutions,
39
+ and is used to prune domains with specific constraint logic
40
+ when possible. For instance, any constraints with a single
41
+ variable may be applied on all possible values and removed,
42
+ since they may act on individual values even without further
43
+ knowledge about other assignments.
44
+
45
+ Args:
46
+ variables (sequence): Variables affected by that constraint,
47
+ in the same order provided by the user
48
+ domains (dict): Dictionary mapping variables to their
49
+ domains
50
+ constraints (list): List of pairs of (constraint, variables)
51
+ vconstraints (dict): Dictionary mapping variables to a list
52
+ of constraints affecting the given variables.
53
+ """
54
+ if len(variables) == 1:
55
+ variable = variables[0]
56
+ domain = domains[variable]
57
+ for value in domain[:]:
58
+ if not self(variables, domains, {variable: value}):
59
+ domain.remove(value)
60
+ constraints.remove((self, variables))
61
+ vconstraints[variable].remove((self, variables))
62
+
63
+ def forwardCheck(self, variables: Sequence, domains: dict, assignments: dict, _unassigned=Unassigned):
64
+ """Helper method for generic forward checking.
65
+
66
+ Currently, this method acts only when there's a single
67
+ unassigned variable.
68
+
69
+ Args:
70
+ variables (sequence): Variables affected by that constraint,
71
+ in the same order provided by the user
72
+ domains (dict): Dictionary mapping variables to their
73
+ domains
74
+ assignments (dict): Dictionary mapping assigned variables to
75
+ their current assumed value
76
+
77
+ Returns:
78
+ bool: Boolean value stating if this constraint is currently
79
+ broken or not
80
+ """
81
+ unassignedvariable = _unassigned
82
+ for variable in variables:
83
+ if variable not in assignments:
84
+ if unassignedvariable is _unassigned:
85
+ unassignedvariable = variable
86
+ else:
87
+ break
88
+ else:
89
+ if unassignedvariable is not _unassigned:
90
+ # Remove from the unassigned variable domain's all
91
+ # values which break our variable's constraints.
92
+ domain = domains[unassignedvariable]
93
+ if domain:
94
+ for value in domain[:]:
95
+ assignments[unassignedvariable] = value
96
+ if not self(variables, domains, assignments):
97
+ domain.hideValue(value)
98
+ del assignments[unassignedvariable]
99
+ if not domain:
100
+ return False
101
+ return True
102
+
103
+
104
+ class FunctionConstraint(Constraint):
105
+ """Constraint which wraps a function defining the constraint logic.
106
+
107
+ Examples:
108
+ >>> problem = Problem()
109
+ >>> problem.addVariables(["a", "b"], [1, 2])
110
+ >>> def func(a, b):
111
+ ... return b > a
112
+ >>> problem.addConstraint(func, ["a", "b"])
113
+ >>> problem.getSolution()
114
+ {'a': 1, 'b': 2}
115
+
116
+ >>> problem = Problem()
117
+ >>> problem.addVariables(["a", "b"], [1, 2])
118
+ >>> def func(a, b):
119
+ ... return b > a
120
+ >>> problem.addConstraint(FunctionConstraint(func), ["a", "b"])
121
+ >>> problem.getSolution()
122
+ {'a': 1, 'b': 2}
123
+ """
124
+
125
+ def __init__(self, func: Callable, assigned: bool = True):
126
+ """Initialization method.
127
+
128
+ Args:
129
+ func (callable object): Function wrapped and queried for
130
+ constraint logic
131
+ assigned (bool): Whether the function may receive unassigned
132
+ variables or not
133
+ """
134
+ self._func = func
135
+ self._assigned = assigned
136
+
137
+ def __call__( # noqa: D102
138
+ self,
139
+ variables: Sequence,
140
+ domains: dict,
141
+ assignments: dict,
142
+ forwardcheck=False,
143
+ _unassigned=Unassigned,
144
+ ):
145
+ # # initial code: 0.94621 seconds, Cythonized: 0.92805 seconds
146
+ # parms = [assignments.get(x, _unassigned) for x in variables]
147
+ # missing = parms.count(_unassigned)
148
+
149
+ # # list comprehension and sum: 0.13744 seconds, Cythonized: 0.10059 seconds
150
+ # parms = [assignments.get(x, _unassigned) for x in variables]
151
+ # missing = sum(x not in assignments for x in variables)
152
+
153
+ # # sum check with fallback: , Cythonized: 0.10108 seconds
154
+ # missing = sum(x not in assignments for x in variables)
155
+ # parms = [assignments.get(x, _unassigned) for x in variables] if missing > 0 else [assignments[x] for x in var]
156
+
157
+ # # tuple list comprehension with unzipping: 0.14521 seconds, Cythonized: 0.12054 seconds
158
+ # lst = [(assignments[x], 0) if x in assignments else (_unassigned, 1) for x in variables]
159
+ # parms, missing_iter = zip(*lst)
160
+ # parms = list(parms)
161
+ # missing = sum(missing_iter)
162
+
163
+ # # single loop array: 0.11249 seconds, Cythonized: 0.09514 seconds
164
+ # parms = [None] * len(variables)
165
+ # missing = 0
166
+ # for i, x in enumerate(variables):
167
+ # if x in assignments:
168
+ # parms[i] = assignments[x]
169
+ # else:
170
+ # parms[i] = _unassigned
171
+ # missing += 1
172
+
173
+ # single loop list: 0.11462 seconds, Cythonized: 0.08686 seconds
174
+ parms = list()
175
+ missing = 0
176
+ for x in variables:
177
+ if x in assignments:
178
+ parms.append(assignments[x])
179
+ else:
180
+ parms.append(_unassigned)
181
+ missing += 1
182
+
183
+ # if there are unassigned variables, do a forward check before executing the restriction function
184
+ if missing > 0:
185
+ return (self._assigned or self._func(*parms)) and (
186
+ not forwardcheck or missing != 1 or self.forwardCheck(variables, domains, assignments)
187
+ )
188
+ return self._func(*parms)
189
+
190
+
191
+ class AllDifferentConstraint(Constraint):
192
+ """Constraint enforcing that values of all given variables are different.
193
+
194
+ Example:
195
+ >>> problem = Problem()
196
+ >>> problem.addVariables(["a", "b"], [1, 2])
197
+ >>> problem.addConstraint(AllDifferentConstraint())
198
+ >>> sorted(sorted(x.items()) for x in problem.getSolutions())
199
+ [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)]]
200
+ """
201
+
202
+ def __call__( # noqa: D102
203
+ self,
204
+ variables: Sequence,
205
+ domains: dict,
206
+ assignments: dict,
207
+ forwardcheck=False,
208
+ _unassigned=Unassigned,
209
+ ):
210
+ seen = {}
211
+ for variable in variables:
212
+ value = assignments.get(variable, _unassigned)
213
+ if value is not _unassigned:
214
+ if value in seen:
215
+ return False
216
+ seen[value] = True
217
+ if forwardcheck:
218
+ for variable in variables:
219
+ if variable not in assignments:
220
+ domain = domains[variable]
221
+ for value in seen:
222
+ if value in domain:
223
+ domain.hideValue(value)
224
+ if not domain:
225
+ return False
226
+ return True
227
+
228
+
229
+ class AllEqualConstraint(Constraint):
230
+ """Constraint enforcing that values of all given variables are equal.
231
+
232
+ Example:
233
+ >>> problem = Problem()
234
+ >>> problem.addVariables(["a", "b"], [1, 2])
235
+ >>> problem.addConstraint(AllEqualConstraint())
236
+ >>> sorted(sorted(x.items()) for x in problem.getSolutions())
237
+ [[('a', 1), ('b', 1)], [('a', 2), ('b', 2)]]
238
+ """
239
+
240
+ def __call__( # noqa: D102
241
+ self,
242
+ variables: Sequence,
243
+ domains: dict,
244
+ assignments: dict,
245
+ forwardcheck=False,
246
+ _unassigned=Unassigned,
247
+ ):
248
+ singlevalue = _unassigned
249
+ for variable in variables:
250
+ value = assignments.get(variable, _unassigned)
251
+ if singlevalue is _unassigned:
252
+ singlevalue = value
253
+ elif value is not _unassigned and value != singlevalue:
254
+ return False
255
+ if forwardcheck and singlevalue is not _unassigned:
256
+ for variable in variables:
257
+ if variable not in assignments:
258
+ domain = domains[variable]
259
+ if singlevalue not in domain:
260
+ return False
261
+ for value in domain[:]:
262
+ if value != singlevalue:
263
+ domain.hideValue(value)
264
+ return True
265
+
266
+
267
+ class MaxSumConstraint(Constraint):
268
+ """Constraint enforcing that values of given variables sum up to a given amount.
269
+
270
+ Example:
271
+ >>> problem = Problem()
272
+ >>> problem.addVariables(["a", "b"], [1, 2])
273
+ >>> problem.addConstraint(MaxSumConstraint(3))
274
+ >>> sorted(sorted(x.items()) for x in problem.getSolutions())
275
+ [[('a', 1), ('b', 1)], [('a', 1), ('b', 2)], [('a', 2), ('b', 1)]]
276
+ """
277
+
278
+ def __init__(self, maxsum: Union[int, float], multipliers: Optional[Sequence] = None):
279
+ """Initialization method.
280
+
281
+ Args:
282
+ maxsum (number): Value to be considered as the maximum sum
283
+ multipliers (sequence of numbers): If given, variable values
284
+ will be multiplied by the given factors before being
285
+ summed to be checked
286
+ """
287
+ self._maxsum = maxsum
288
+ self._multipliers = multipliers
289
+
290
+ def preProcess(self, variables: Sequence, domains: dict, constraints: list[tuple], vconstraints: dict): # noqa: D102
291
+ Constraint.preProcess(self, variables, domains, constraints, vconstraints)
292
+
293
+ # check if there are any negative values in the associated variables
294
+ variable_contains_negative: list[bool] = list()
295
+ variable_with_negative = None
296
+ for variable in variables:
297
+ contains_negative = any(value < 0 for value in domains[variable])
298
+ variable_contains_negative.append(contains_negative)
299
+ if contains_negative:
300
+ if variable_with_negative is not None:
301
+ # if more than one associated variables contain negative, we can't prune
302
+ return
303
+ variable_with_negative = variable
304
+
305
+ # prune the associated variables of values > maxsum
306
+ multipliers = self._multipliers
307
+ maxsum = self._maxsum
308
+ if multipliers:
309
+ for variable, multiplier in zip(variables, multipliers):
310
+ if variable_with_negative is not None and variable_with_negative != variable:
311
+ continue
312
+ domain = domains[variable]
313
+ for value in domain[:]:
314
+ if value * multiplier > maxsum:
315
+ domain.remove(value)
316
+ else:
317
+ for variable in variables:
318
+ if variable_with_negative is not None and variable_with_negative != variable:
319
+ continue
320
+ domain = domains[variable]
321
+ for value in domain[:]:
322
+ if value > maxsum:
323
+ domain.remove(value)
324
+
325
+ def __call__(self, variables: Sequence, domains: dict, assignments: dict, forwardcheck=False): # noqa: D102
326
+ multipliers = self._multipliers
327
+ maxsum = self._maxsum
328
+ sum = 0
329
+ if multipliers:
330
+ for variable, multiplier in zip(variables, multipliers):
331
+ if variable in assignments:
332
+ sum += assignments[variable] * multiplier
333
+ if isinstance(sum, float):
334
+ sum = round(sum, 10)
335
+ if sum > maxsum:
336
+ return False
337
+ if forwardcheck:
338
+ for variable, multiplier in zip(variables, multipliers):
339
+ if variable not in assignments:
340
+ domain = domains[variable]
341
+ for value in domain[:]:
342
+ if sum + value * multiplier > maxsum:
343
+ domain.hideValue(value)
344
+ if not domain:
345
+ return False
346
+ else:
347
+ for variable in variables:
348
+ if variable in assignments:
349
+ sum += assignments[variable]
350
+ if isinstance(sum, float):
351
+ sum = round(sum, 10)
352
+ if sum > maxsum:
353
+ return False
354
+ if forwardcheck:
355
+ for variable in variables:
356
+ if variable not in assignments:
357
+ domain = domains[variable]
358
+ for value in domain[:]:
359
+ if sum + value > maxsum:
360
+ domain.hideValue(value)
361
+ if not domain:
362
+ return False
363
+ return True
364
+
365
+
366
+ class ExactSumConstraint(Constraint):
367
+ """Constraint enforcing that values of given variables sum exactly to a given amount.
368
+
369
+ Example:
370
+ >>> problem = Problem()
371
+ >>> problem.addVariables(["a", "b"], [1, 2])
372
+ >>> problem.addConstraint(ExactSumConstraint(3))
373
+ >>> sorted(sorted(x.items()) for x in problem.getSolutions())
374
+ [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)]]
375
+ """
376
+
377
+ def __init__(self, exactsum: Union[int, float], multipliers: Optional[Sequence] = None):
378
+ """Initialization method.
379
+
380
+ Args:
381
+ exactsum (number): Value to be considered as the exact sum
382
+ multipliers (sequence of numbers): If given, variable values
383
+ will be multiplied by the given factors before being
384
+ summed to be checked
385
+ """
386
+ self._exactsum = exactsum
387
+ self._multipliers = multipliers
388
+
389
+ def preProcess(self, variables: Sequence, domains: dict, constraints: list[tuple], vconstraints: dict): # noqa: D102
390
+ Constraint.preProcess(self, variables, domains, constraints, vconstraints)
391
+ multipliers = self._multipliers
392
+ exactsum = self._exactsum
393
+ if multipliers:
394
+ for variable, multiplier in zip(variables, multipliers):
395
+ domain = domains[variable]
396
+ for value in domain[:]:
397
+ if value * multiplier > exactsum:
398
+ domain.remove(value)
399
+ else:
400
+ for variable in variables:
401
+ domain = domains[variable]
402
+ for value in domain[:]:
403
+ if value > exactsum:
404
+ domain.remove(value)
405
+
406
+ def __call__(self, variables: Sequence, domains: dict, assignments: dict, forwardcheck=False): # noqa: D102
407
+ multipliers = self._multipliers
408
+ exactsum = self._exactsum
409
+ sum = 0
410
+ missing = False
411
+ if multipliers:
412
+ for variable, multiplier in zip(variables, multipliers):
413
+ if variable in assignments:
414
+ sum += assignments[variable] * multiplier
415
+ else:
416
+ missing = True
417
+ if isinstance(sum, float):
418
+ sum = round(sum, 10)
419
+ if sum > exactsum:
420
+ return False
421
+ if forwardcheck and missing:
422
+ for variable, multiplier in zip(variables, multipliers):
423
+ if variable not in assignments:
424
+ domain = domains[variable]
425
+ for value in domain[:]:
426
+ if sum + value * multiplier > exactsum:
427
+ domain.hideValue(value)
428
+ if not domain:
429
+ return False
430
+ else:
431
+ for variable in variables:
432
+ if variable in assignments:
433
+ sum += assignments[variable]
434
+ else:
435
+ missing = True
436
+ if isinstance(sum, float):
437
+ sum = round(sum, 10)
438
+ if sum > exactsum:
439
+ return False
440
+ if forwardcheck and missing:
441
+ for variable in variables:
442
+ if variable not in assignments:
443
+ domain = domains[variable]
444
+ for value in domain[:]:
445
+ if sum + value > exactsum:
446
+ domain.hideValue(value)
447
+ if not domain:
448
+ return False
449
+ if missing:
450
+ return sum <= exactsum
451
+ else:
452
+ return sum == exactsum
453
+
454
+
455
+ class MinSumConstraint(Constraint):
456
+ """Constraint enforcing that values of given variables sum at least to a given amount.
457
+
458
+ Example:
459
+ >>> problem = Problem()
460
+ >>> problem.addVariables(["a", "b"], [1, 2])
461
+ >>> problem.addConstraint(MinSumConstraint(3))
462
+ >>> sorted(sorted(x.items()) for x in problem.getSolutions())
463
+ [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)], [('a', 2), ('b', 2)]]
464
+ """
465
+
466
+ def __init__(self, minsum: Union[int, float], multipliers: Optional[Sequence] = None):
467
+ """Initialization method.
468
+
469
+ Args:
470
+ minsum (number): Value to be considered as the minimum sum
471
+ multipliers (sequence of numbers): If given, variable values
472
+ will be multiplied by the given factors before being
473
+ summed to be checked
474
+ """
475
+ self._minsum = minsum
476
+ self._multipliers = multipliers
477
+
478
+ def __call__(self, variables: Sequence, domains: dict, assignments: dict, forwardcheck=False): # noqa: D102
479
+ # check if each variable is in the assignments
480
+ for variable in variables:
481
+ if variable not in assignments:
482
+ return True
483
+
484
+ # with each variable assigned, sum the values
485
+ multipliers = self._multipliers
486
+ minsum = self._minsum
487
+ sum = 0
488
+ if multipliers:
489
+ for variable, multiplier in zip(variables, multipliers):
490
+ sum += assignments[variable] * multiplier
491
+ else:
492
+ for variable in variables:
493
+ sum += assignments[variable]
494
+ if isinstance(sum, float):
495
+ sum = round(sum, 10)
496
+ return sum >= minsum
497
+
498
+
499
+ class MaxProdConstraint(Constraint):
500
+ """Constraint enforcing that values of given variables create a product up to at most a given amount."""
501
+
502
+ def __init__(self, maxprod: Union[int, float]):
503
+ """Instantiate a MaxProdConstraint.
504
+
505
+ Args:
506
+ maxprod: Value to be considered as the maximum product
507
+ """
508
+ self._maxprod = maxprod
509
+
510
+ def preProcess(self, variables: Sequence, domains: dict, constraints: list[tuple], vconstraints: dict): # noqa: D102
511
+ Constraint.preProcess(self, variables, domains, constraints, vconstraints)
512
+
513
+ # check if there are any values less than 1 in the associated variables
514
+ variable_contains_lt1: list[bool] = list()
515
+ variable_with_lt1 = None
516
+ for variable in variables:
517
+ contains_lt1 = any(value < 1 for value in domains[variable])
518
+ variable_contains_lt1.append(contains_lt1)
519
+ if contains_lt1 is True:
520
+ if variable_with_lt1 is not None:
521
+ # if more than one associated variables contain less than 1, we can't prune
522
+ return
523
+ variable_with_lt1 = variable
524
+
525
+ # prune the associated variables of values > maxprod
526
+ maxprod = self._maxprod
527
+ for variable in variables:
528
+ if variable_with_lt1 is not None and variable_with_lt1 != variable:
529
+ continue
530
+ domain = domains[variable]
531
+ for value in domain[:]:
532
+ if value > maxprod:
533
+ domain.remove(value)
534
+ elif value == 0 and maxprod < 0:
535
+ domain.remove(value)
536
+
537
+ def __call__(self, variables: Sequence, domains: dict, assignments: dict, forwardcheck=False): # noqa: D102
538
+ maxprod = self._maxprod
539
+ prod = 1
540
+ for variable in variables:
541
+ if variable in assignments:
542
+ prod *= assignments[variable]
543
+ if isinstance(prod, float):
544
+ prod = round(prod, 10)
545
+ if prod > maxprod:
546
+ return False
547
+ if forwardcheck:
548
+ for variable in variables:
549
+ if variable not in assignments:
550
+ domain = domains[variable]
551
+ for value in domain[:]:
552
+ if prod * value > maxprod:
553
+ domain.hideValue(value)
554
+ if not domain:
555
+ return False
556
+ return True
557
+
558
+
559
+ class MinProdConstraint(Constraint):
560
+ """Constraint enforcing that values of given variables create a product up to at least a given amount."""
561
+
562
+ def __init__(self, minprod: Union[int, float]):
563
+ """Instantiate a MinProdConstraint.
564
+
565
+ Args:
566
+ minprod: Value to be considered as the maximum product
567
+ """
568
+ self._minprod = minprod
569
+
570
+ def preProcess(self, variables: Sequence, domains: dict, constraints: list[tuple], vconstraints: dict): # noqa: D102
571
+ Constraint.preProcess(self, variables, domains, constraints, vconstraints)
572
+
573
+ # prune the associated variables of values > maxprod
574
+ minprod = self._minprod
575
+ for variable in variables:
576
+ domain = domains[variable]
577
+ for value in domain[:]:
578
+ if value == 0 and minprod > 0:
579
+ domain.remove(value)
580
+
581
+ def __call__(self, variables: Sequence, domains: dict, assignments: dict, forwardcheck=False): # noqa: D102
582
+ # check if each variable is in the assignments
583
+ for variable in variables:
584
+ if variable not in assignments:
585
+ return True
586
+
587
+ # with each variable assigned, sum the values
588
+ minprod = self._minprod
589
+ prod = 1
590
+ for variable in variables:
591
+ prod *= assignments[variable]
592
+ if isinstance(prod, float):
593
+ prod = round(prod, 10)
594
+ return prod >= minprod
595
+
596
+
597
+ class InSetConstraint(Constraint):
598
+ """Constraint enforcing that values of given variables are present in the given set.
599
+
600
+ Example:
601
+ >>> problem = Problem()
602
+ >>> problem.addVariables(["a", "b"], [1, 2])
603
+ >>> problem.addConstraint(InSetConstraint([1]))
604
+ >>> sorted(sorted(x.items()) for x in problem.getSolutions())
605
+ [[('a', 1), ('b', 1)]]
606
+ """
607
+
608
+ def __init__(self, set):
609
+ """Initialization method.
610
+
611
+ Args:
612
+ set (set): Set of allowed values
613
+ """
614
+ self._set = set
615
+
616
+ def __call__(self, variables, domains, assignments, forwardcheck=False): # noqa: D102
617
+ # preProcess() will remove it.
618
+ raise RuntimeError("Can't happen")
619
+
620
+ def preProcess(self, variables: Sequence, domains: dict, constraints: list[tuple], vconstraints: dict): # noqa: D102
621
+ set = self._set
622
+ for variable in variables:
623
+ domain = domains[variable]
624
+ for value in domain[:]:
625
+ if value not in set:
626
+ domain.remove(value)
627
+ vconstraints[variable].remove((self, variables))
628
+ constraints.remove((self, variables))
629
+
630
+
631
+ class NotInSetConstraint(Constraint):
632
+ """Constraint enforcing that values of given variables are not present in the given set.
633
+
634
+ Example:
635
+ >>> problem = Problem()
636
+ >>> problem.addVariables(["a", "b"], [1, 2])
637
+ >>> problem.addConstraint(NotInSetConstraint([1]))
638
+ >>> sorted(sorted(x.items()) for x in problem.getSolutions())
639
+ [[('a', 2), ('b', 2)]]
640
+ """
641
+
642
+ def __init__(self, set):
643
+ """Initialization method.
644
+
645
+ Args:
646
+ set (set): Set of disallowed values
647
+ """
648
+ self._set = set
649
+
650
+ def __call__(self, variables, domains, assignments, forwardcheck=False): # noqa: D102
651
+ # preProcess() will remove it.
652
+ raise RuntimeError("Can't happen")
653
+
654
+ def preProcess(self, variables: Sequence, domains: dict, constraints: list[tuple], vconstraints: dict): # noqa: D102
655
+ set = self._set
656
+ for variable in variables:
657
+ domain = domains[variable]
658
+ for value in domain[:]:
659
+ if value in set:
660
+ domain.remove(value)
661
+ vconstraints[variable].remove((self, variables))
662
+ constraints.remove((self, variables))
663
+
664
+
665
+ class SomeInSetConstraint(Constraint):
666
+ """Constraint enforcing that at least some of the values of given variables must be present in a given set.
667
+
668
+ Example:
669
+ >>> problem = Problem()
670
+ >>> problem.addVariables(["a", "b"], [1, 2])
671
+ >>> problem.addConstraint(SomeInSetConstraint([1]))
672
+ >>> sorted(sorted(x.items()) for x in problem.getSolutions())
673
+ [[('a', 1), ('b', 1)], [('a', 1), ('b', 2)], [('a', 2), ('b', 1)]]
674
+ """
675
+
676
+ def __init__(self, set, n=1, exact=False):
677
+ """Initialization method.
678
+
679
+ Args:
680
+ set (set): Set of values to be checked
681
+ n (int): Minimum number of assigned values that should be
682
+ present in set (default is 1)
683
+ exact (bool): Whether the number of assigned values which
684
+ are present in set must be exactly `n`
685
+ """
686
+ self._set = set
687
+ self._n = n
688
+ self._exact = exact
689
+
690
+ def __call__(self, variables: Sequence, domains: dict, assignments: dict, forwardcheck=False): # noqa: D102
691
+ set = self._set
692
+ missing = 0
693
+ found = 0
694
+ for variable in variables:
695
+ if variable in assignments:
696
+ found += assignments[variable] in set
697
+ else:
698
+ missing += 1
699
+ if missing:
700
+ if self._exact:
701
+ if not (found <= self._n <= missing + found):
702
+ return False
703
+ else:
704
+ if self._n > missing + found:
705
+ return False
706
+ if forwardcheck and self._n - found == missing:
707
+ # All unassigned variables must be assigned to
708
+ # values in the set.
709
+ for variable in variables:
710
+ if variable not in assignments:
711
+ domain = domains[variable]
712
+ for value in domain[:]:
713
+ if value not in set:
714
+ domain.hideValue(value)
715
+ if not domain:
716
+ return False
717
+ else:
718
+ if self._exact:
719
+ if found != self._n:
720
+ return False
721
+ else:
722
+ if found < self._n:
723
+ return False
724
+ return True
725
+
726
+
727
+ class SomeNotInSetConstraint(Constraint):
728
+ """Constraint enforcing that at least some of the values of given variables must not be present in a given set.
729
+
730
+ Example:
731
+ >>> problem = Problem()
732
+ >>> problem.addVariables(["a", "b"], [1, 2])
733
+ >>> problem.addConstraint(SomeNotInSetConstraint([1]))
734
+ >>> sorted(sorted(x.items()) for x in problem.getSolutions())
735
+ [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)], [('a', 2), ('b', 2)]]
736
+ """
737
+
738
+ def __init__(self, set, n=1, exact=False):
739
+ """Initialization method.
740
+
741
+ Args:
742
+ set (set): Set of values to be checked
743
+ n (int): Minimum number of assigned values that should not
744
+ be present in set (default is 1)
745
+ exact (bool): Whether the number of assigned values which
746
+ are not present in set must be exactly `n`
747
+ """
748
+ self._set = set
749
+ self._n = n
750
+ self._exact = exact
751
+
752
+ def __call__(self, variables: Sequence, domains: dict, assignments: dict, forwardcheck=False): # noqa: D102
753
+ set = self._set
754
+ missing = 0
755
+ found = 0
756
+ for variable in variables:
757
+ if variable in assignments:
758
+ found += assignments[variable] not in set
759
+ else:
760
+ missing += 1
761
+ if missing:
762
+ if self._exact:
763
+ if not (found <= self._n <= missing + found):
764
+ return False
765
+ else:
766
+ if self._n > missing + found:
767
+ return False
768
+ if forwardcheck and self._n - found == missing:
769
+ # All unassigned variables must be assigned to
770
+ # values not in the set.
771
+ for variable in variables:
772
+ if variable not in assignments:
773
+ domain = domains[variable]
774
+ for value in domain[:]:
775
+ if value in set:
776
+ domain.hideValue(value)
777
+ if not domain:
778
+ return False
779
+ else:
780
+ if self._exact:
781
+ if found != self._n:
782
+ return False
783
+ else:
784
+ if found < self._n:
785
+ return False
786
+ return True