Coverage for moptipy / tests / op2.py: 77%
73 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
1"""Functions that can be used to test binary search operators."""
2from math import isqrt
3from typing import Any, Callable, Final
5from numpy.random import Generator, default_rng
6from pycommons.types import check_int_range, type_error
8from moptipy.api.operators import Op2, check_op2
9from moptipy.api.space import Space
10from moptipy.tests.component import validate_component
13def validate_op2(op2: Op2,
14 search_space: Space | None = None,
15 make_search_space_element_valid:
16 Callable[[Generator, Any], Any] | None = lambda _, x: x,
17 number_of_samples: int = 100,
18 min_unique_samples: int | Callable[[int, Space], int]
19 = lambda samples, space:
20 max(1, min(samples // 2, isqrt(space.n_points())))) -> None:
21 """
22 Check whether an object is valid a moptipy binary operator.
24 :param op2: the operator
25 :param search_space: the search space
26 :param make_search_space_element_valid: make a point in the search
27 space valid
28 :param number_of_samples: the number of times to invoke the operator
29 :param min_unique_samples: a lambda for computing the number
30 :raises ValueError: if `op2` is not a valid instance of
31 :class:`~moptipy.api.operators.Op2`
32 :raises TypeError: if incorrect types are encountered
33 """
34 if not isinstance(op2, Op2):
35 raise type_error(op2, "op2", Op2)
36 if op2.__class__ == Op2:
37 raise ValueError("Cannot use abstract base Op2 directly.")
38 check_op2(op2)
39 validate_component(op2)
41 count: int = 0
42 if search_space is not None:
43 count += 1
44 if make_search_space_element_valid is not None:
45 count += 1
46 if count <= 0:
47 return
48 if count < 2:
49 raise ValueError(
50 "either provide both of search_space and "
51 "make_search_space_element_valid or none.")
52 check_int_range(number_of_samples, "number_of_samples", 1, 1_000_000)
53 random = default_rng()
54 x1 = search_space.create()
55 if x1 is None:
56 raise ValueError("Space must not return None.")
57 x1 = make_search_space_element_valid(random, x1)
58 if x1 is None:
59 raise ValueError("validator turned point to None?")
60 search_space.validate(x1)
62 seen = set()
64 strstr = search_space.to_str(x1)
65 if (not isinstance(strstr, str)) or (len(strstr) <= 0):
66 raise ValueError("to_str produces either no string or "
67 f"empty string, namely {strstr}.")
68 seen.add(strstr)
69 x2: Any = None
70 for _ in range(1000):
71 x2 = search_space.create()
72 if x2 is None:
73 raise ValueError("Space must not return None.")
74 x2 = make_search_space_element_valid(random, x2)
75 if x2 is None:
76 raise ValueError("validator turned point to None?")
77 if x1 is x2:
78 raise ValueError(
79 "Search space.create must not return same object instance.")
80 search_space.validate(x2)
81 strstr = search_space.to_str(x2)
82 if (not isinstance(strstr, str)) or (len(strstr) <= 0):
83 raise ValueError("to_str produces either no string or "
84 f"empty string, namely {strstr}.")
85 seen.add(strstr)
86 if len(seen) > 1:
87 break
88 if len(seen) <= 1:
89 raise ValueError("failed to create two different initial elements.")
91 x3 = search_space.create()
92 if x3 is None:
93 raise ValueError("Space must not return None.")
94 if (x1 is x3) or (x2 is x3):
95 raise ValueError(
96 "Search space.create must not return same object instance.")
98 if not (hasattr(op2, "op2") and callable(getattr(op2, "op2"))):
99 raise ValueError("op2 must have method op2.")
100 for _ in range(number_of_samples):
101 op2.op2(random, x3, x2, x1)
102 search_space.validate(x3)
103 strstr = search_space.to_str(x3)
104 if (not isinstance(strstr, str)) or (len(strstr) <= 0):
105 raise ValueError("to_str produces either no string or "
106 f"empty string, namely {strstr!r}.")
107 seen.add(strstr)
109 expected: Final[int] = check_int_range(min_unique_samples(
110 number_of_samples, search_space) if callable(
111 min_unique_samples) else min_unique_samples,
112 "expected", 1, number_of_samples)
113 if len(seen) < expected:
114 raise ValueError(
115 f"It is expected that at least {expected} different elements "
116 "will be created by unary search operator from "
117 f"{number_of_samples} samples, but we only "
118 f"got {len(seen)} different points.")