Coverage for moptipy / spaces / nparrayspace.py: 88%

40 statements  

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

1"""The base class for spaces based on numpy arrays.""" 

2from typing import Final 

3 

4import numpy as np 

5from pycommons.types import check_int_range, type_error 

6 

7from moptipy.api.logging import KEY_SPACE_NUM_VARS 

8from moptipy.api.space import Space 

9from moptipy.utils.logger import CSV_SEPARATOR, KeyValueLogSection 

10from moptipy.utils.nputils import ( 

11 KEY_NUMPY_TYPE, 

12 array_to_str, 

13 numpy_type_to_str, 

14) 

15 

16 

17class NPArraySpace(Space): 

18 """ 

19 A space where each element is a one-dimensional :class:`numpy.ndarray`. 

20 

21 Such spaces can serve as basis for implementing combinatorial 

22 optimization and can be extended to host permutations. 

23 

24 >>> import numpy as npx 

25 >>> s = NPArraySpace(9, npx.dtype(int)) 

26 >>> print(s.dimension) 

27 9 

28 >>> print(s.dtype) 

29 int64 

30 >>> print(s.create()) 

31 [0 0 0 0 0 0 0 0 0] 

32 >>> print(s.to_str(s.create())) 

33 0;0;0;0;0;0;0;0;0 

34 >>> print(s.from_str(s.to_str(s.create()))) 

35 [0 0 0 0 0 0 0 0 0] 

36 """ 

37 

38 def __init__(self, dimension: int, dtype: np.dtype) -> None: 

39 """ 

40 Create the numpy array-based search space. 

41 

42 :param dimension: The dimension of the search space, 

43 i.e., the number of decision variables. 

44 :param dtype: the data type 

45 """ 

46 if not isinstance(dtype, np.dtype): 

47 raise type_error(dtype, "dtype", np.dtype) 

48 if (not isinstance(dtype.char, str)) or (len(dtype.char) != 1): 

49 raise ValueError( 

50 f"dtype.char must be str of length 1, but is {dtype.char}") 

51 #: The basic data type of the vector space elements. 

52 self.dtype: Final[np.dtype] = dtype 

53 #: The dimension, i.e., the number of elements of the vectors. 

54 self.dimension: Final[int] = check_int_range( 

55 dimension, "dimension", 1, 100_000_000) 

56 # the function forwards 

57 self.copy = np.copyto # type: ignore 

58 self.is_equal = np.array_equal # type: ignore 

59 

60 def to_str(self, x: np.ndarray) -> str: 

61 """ 

62 Convert a np-array to a string. 

63 

64 :param x: the array 

65 :returns: the string 

66 """ 

67 return array_to_str(x) 

68 

69 def create(self) -> np.ndarray: 

70 """ 

71 Create a vector with all zeros. 

72 

73 :return: the vector 

74 """ 

75 return np.zeros(shape=self.dimension, dtype=self.dtype) 

76 

77 def from_str(self, text: str) -> np.ndarray: 

78 """ 

79 Convert a string to a vector. 

80 

81 :param text: the text 

82 :return: the vector 

83 :raises TypeError: if `text` is not a `str` 

84 :raises ValueError: if `text` cannot be converted to a valid vector 

85 """ 

86 if not (isinstance(text, str)): 

87 raise type_error(text, "text", str) 

88 x = np.fromstring(text, dtype=self.dtype, sep=CSV_SEPARATOR) 

89 self.validate(x) 

90 return x 

91 

92 def validate(self, x: np.ndarray) -> None: 

93 """ 

94 Validate a numpy nd-array. 

95 

96 :param x: the numpy vector 

97 :raises TypeError: if the string is not an :class:`numpy.ndarray`. 

98 :raises ValueError: if the shape or data type of the vector is wrong 

99 or any of its element is not finite. 

100 """ 

101 if not isinstance(x, np.ndarray): 

102 raise type_error(x, "x", np.ndarray) 

103 if x.dtype != self.dtype: 

104 raise ValueError( 

105 f"x must be of type {self.dtype} but is of type {x.dtype}.") 

106 if (len(x.shape) != 1) or (x.shape[0] != self.dimension): 

107 raise ValueError(f"x must be of shape ({self.dimension}) but is " 

108 f"of shape {x.shape}.") 

109 

110 def __str__(self) -> str: 

111 """ 

112 Get the name of this np array space. 

113 

114 :return: "ndarray" + dimension + dtype.char 

115 

116 >>> import numpy as npx 

117 >>> print(NPArraySpace(4, npx.dtype(int))) 

118 ndarray4l 

119 """ 

120 return f"ndarray{self.dimension}{self.dtype.char}" 

121 

122 def log_parameters_to(self, logger: KeyValueLogSection) -> None: 

123 """ 

124 Log the parameters of this space to the given logger. 

125 

126 :param logger: the logger for the parameters 

127 

128 >>> from moptipy.utils.logger import InMemoryLogger 

129 >>> import numpy as npx 

130 >>> dt = npx.dtype(float) 

131 >>> dt.char 

132 'd' 

133 >>> space = NPArraySpace(10, dt) 

134 >>> space.dimension 

135 10 

136 >>> with InMemoryLogger() as l: 

137 ... with l.key_values("C") as kv: 

138 ... space.log_parameters_to(kv) 

139 ... text = l.get_log() 

140 >>> text[-2] 

141 'dtype: d' 

142 >>> text[-3] 

143 'nvars: 10' 

144 >>> text[-4] 

145 'class: moptipy.spaces.nparrayspace.NPArraySpace' 

146 >>> text[-5] 

147 'name: ndarray10d' 

148 >>> len(text) 

149 6 

150 """ 

151 super().log_parameters_to(logger) 

152 logger.key_value(KEY_SPACE_NUM_VARS, self.dimension) 

153 logger.key_value(KEY_NUMPY_TYPE, numpy_type_to_str(self.dtype))