Coverage for moptipy / tests / op1_with_step_size.py: 76%

62 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-24 08:49 +0000

1"""Functions for testing unary search operators with step size.""" 

2from math import isfinite 

3from typing import Any, Callable, Iterable 

4 

5from numpy.random import Generator, default_rng 

6from pycommons.types import type_error 

7 

8from moptipy.api.operators import Op1WithStepSize, check_op1_with_step_size 

9from moptipy.api.space import Space 

10from moptipy.tests.op1 import default_min_unique_samples, validate_op1 

11 

12 

13def validate_op1_with_step_size( 

14 op1: Op1WithStepSize, 

15 search_space: Space | None = None, 

16 make_search_space_element_valid: 

17 Callable[[Generator, Any], Any] | None = lambda _, x: x, 

18 number_of_samples: int = 100, 

19 min_unique_samples: int | Callable[[int, Space], int] 

20 = default_min_unique_samples, 

21 step_sizes: Iterable[float] = (), 

22 get_step_size: Callable[[ 

23 Space, Any, Any], float | None] | None = None) -> None: 

24 """ 

25 Check whether an object is a valid moptipy unary operator with step size. 

26 

27 :param op1: the operator 

28 :param search_space: the search space 

29 :param make_search_space_element_valid: make a point in the search 

30 space valid 

31 :param number_of_samples: the number of times to invoke the operator 

32 :param min_unique_samples: a lambda for computing the number 

33 :param step_sizes: the step sizes to test 

34 :param get_step_size: try to get the step size difference from two space 

35 elements 

36 :raises ValueError: if `op1` is not a valid instance of 

37 :class:`~moptipy.api.operators.Op1` 

38 :raises TypeError: if incorrect types are encountered 

39 """ 

40 if not isinstance(op1, Op1WithStepSize): 

41 raise type_error(op1, "op1", Op1WithStepSize) 

42 if op1.__class__ == Op1WithStepSize: 

43 raise ValueError("Cannot use abstract base Op1WithStepSize directly.") 

44 check_op1_with_step_size(op1) 

45 if not isinstance(step_sizes, Iterable): 

46 raise type_error(step_sizes, "step_sizes", Iterable) 

47 if (get_step_size is not None) and (not callable(get_step_size)): 

48 raise type_error(get_step_size, "get_step_size", None, call=True) 

49 

50 validate_op1(op1, search_space, make_search_space_element_valid, 

51 number_of_samples, min_unique_samples) 

52 

53 random: Generator | None = None 

54 x: Any = None 

55 x_copy: Any = None 

56 dest: Any = None 

57 if search_space is not None: 

58 if random is None: 

59 random = default_rng() 

60 x = search_space.create() 

61 if x is None: 

62 raise ValueError( 

63 f"search_space.create()=None for {search_space}") 

64 x_copy = search_space.create() 

65 if x_copy is None: 

66 raise ValueError( 

67 f"search_space.create()=None for {search_space}") 

68 dest = search_space.create() 

69 if dest is None: 

70 raise ValueError( 

71 f"search_space.create()=None for {search_space}") 

72 

73 for i, step_size in enumerate(step_sizes): 

74 if not isinstance(step_size, float): 

75 raise type_error(step_size, f"step_sizes[{i}]", float) 

76 if not (isfinite(step_size) and (0 <= step_size <= 1)): 

77 raise ValueError(f"Forbidden step_sizes[{i}]={step_size}.") 

78 if (random is not None) and (search_space is not None): 

79 if make_search_space_element_valid is not None: 

80 x = make_search_space_element_valid(random, x) 

81 if x is None: 

82 raise ValueError( 

83 "make_search_space_element_valid(search_space.create())=" 

84 f"None for {search_space}") 

85 search_space.validate(x) 

86 search_space.copy(x_copy, x) 

87 if not search_space.is_equal(x_copy, x): 

88 raise ValueError( 

89 f"error when copying {x}, got {x_copy} on {search_space}") 

90 op1.op1(random, dest, x, step_size) 

91 search_space.validate(dest) 

92 if search_space.is_equal(dest, x_copy): 

93 raise ValueError( 

94 f"operator copies source for step_size={step_size} " 

95 f"on {search_space} and {dest}") 

96 if not search_space.is_equal(x, x_copy): 

97 raise ValueError( 

98 f"operator modifies source for step_size={step_size} " 

99 f"on {search_space}") 

100 if get_step_size is not None: 

101 found_step_size: float | None = \ 

102 get_step_size(search_space, x, dest) 

103 if found_step_size is None: 

104 continue 

105 if not (isfinite(found_step_size) 

106 and (0 <= found_step_size <= 1)): 

107 raise ValueError( 

108 f"invalid detected step size {found_step_size} " 

109 f"for {search_space}.") 

110 if found_step_size != step_size: 

111 raise ValueError( 

112 f"step_size={step_size} but {op1} actually " 

113 f"performs step of size {found_step_size} for " 

114 f"x={x} and returns {dest} for {search_space}.")