Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scikit_learn-1.4.dev0-py3.8-linux-x86_64.egg/sklearn/utils/_param_validation.py: 44%

359 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-12 06:31 +0000

1import functools 

2import math 

3import operator 

4import re 

5from abc import ABC, abstractmethod 

6from collections.abc import Iterable 

7from inspect import signature 

8from numbers import Integral, Real 

9 

10import numpy as np 

11from scipy.sparse import csr_matrix, issparse 

12 

13from .._config import config_context, get_config 

14from .validation import _is_arraylike_not_scalar 

15 

16 

17class InvalidParameterError(ValueError, TypeError): 

18 """Custom exception to be raised when the parameter of a class/method/function 

19 does not have a valid type or value. 

20 """ 

21 

22 # Inherits from ValueError and TypeError to keep backward compatibility. 

23 

24 

25def validate_parameter_constraints(parameter_constraints, params, caller_name): 

26 """Validate types and values of given parameters. 

27 

28 Parameters 

29 ---------- 

30 parameter_constraints : dict or {"no_validation"} 

31 If "no_validation", validation is skipped for this parameter. 

32 

33 If a dict, it must be a dictionary `param_name: list of constraints`. 

34 A parameter is valid if it satisfies one of the constraints from the list. 

35 Constraints can be: 

36 - an Interval object, representing a continuous or discrete range of numbers 

37 - the string "array-like" 

38 - the string "sparse matrix" 

39 - the string "random_state" 

40 - callable 

41 - None, meaning that None is a valid value for the parameter 

42 - any type, meaning that any instance of this type is valid 

43 - an Options object, representing a set of elements of a given type 

44 - a StrOptions object, representing a set of strings 

45 - the string "boolean" 

46 - the string "verbose" 

47 - the string "cv_object" 

48 - the string "nan" 

49 - a MissingValues object representing markers for missing values 

50 - a HasMethods object, representing method(s) an object must have 

51 - a Hidden object, representing a constraint not meant to be exposed to the user 

52 

53 params : dict 

54 A dictionary `param_name: param_value`. The parameters to validate against the 

55 constraints. 

56 

57 caller_name : str 

58 The name of the estimator or function or method that called this function. 

59 """ 

60 for param_name, param_val in params.items(): 

61 # We allow parameters to not have a constraint so that third party estimators 

62 # can inherit from sklearn estimators without having to necessarily use the 

63 # validation tools. 

64 if param_name not in parameter_constraints: 

65 continue 

66 

67 constraints = parameter_constraints[param_name] 

68 

69 if constraints == "no_validation": 

70 continue 

71 

72 constraints = [make_constraint(constraint) for constraint in constraints] 

73 

74 for constraint in constraints: 

75 if constraint.is_satisfied_by(param_val): 

76 # this constraint is satisfied, no need to check further. 

77 break 

78 else: 

79 # No constraint is satisfied, raise with an informative message. 

80 

81 # Ignore constraints that we don't want to expose in the error message, 

82 # i.e. options that are for internal purpose or not officially supported. 

83 constraints = [ 

84 constraint for constraint in constraints if not constraint.hidden 

85 ] 

86 

87 if len(constraints) == 1: 

88 constraints_str = f"{constraints[0]}" 

89 else: 

90 constraints_str = ( 

91 f"{', '.join([str(c) for c in constraints[:-1]])} or" 

92 f" {constraints[-1]}" 

93 ) 

94 

95 raise InvalidParameterError( 

96 f"The {param_name!r} parameter of {caller_name} must be" 

97 f" {constraints_str}. Got {param_val!r} instead." 

98 ) 

99 

100 

101def make_constraint(constraint): 

102 """Convert the constraint into the appropriate Constraint object. 

103 

104 Parameters 

105 ---------- 

106 constraint : object 

107 The constraint to convert. 

108 

109 Returns 

110 ------- 

111 constraint : instance of _Constraint 

112 The converted constraint. 

113 """ 

114 if isinstance(constraint, str) and constraint == "array-like": 

115 return _ArrayLikes() 

116 if isinstance(constraint, str) and constraint == "sparse matrix": 

117 return _SparseMatrices() 

118 if isinstance(constraint, str) and constraint == "random_state": 

119 return _RandomStates() 

120 if constraint is callable: 

121 return _Callables() 

122 if constraint is None: 

123 return _NoneConstraint() 

124 if isinstance(constraint, type): 

125 return _InstancesOf(constraint) 

126 if isinstance( 

127 constraint, (Interval, StrOptions, Options, HasMethods, MissingValues) 

128 ): 

129 return constraint 

130 if isinstance(constraint, str) and constraint == "boolean": 

131 return _Booleans() 

132 if isinstance(constraint, str) and constraint == "verbose": 

133 return _VerboseHelper() 

134 if isinstance(constraint, str) and constraint == "cv_object": 

135 return _CVObjects() 

136 if isinstance(constraint, Hidden): 

137 constraint = make_constraint(constraint.constraint) 

138 constraint.hidden = True 

139 return constraint 

140 if isinstance(constraint, str) and constraint == "nan": 

141 return _NanConstraint() 

142 raise ValueError(f"Unknown constraint type: {constraint}") 

143 

144 

145def validate_params(parameter_constraints, *, prefer_skip_nested_validation): 

146 """Decorator to validate types and values of functions and methods. 

147 

148 Parameters 

149 ---------- 

150 parameter_constraints : dict 

151 A dictionary `param_name: list of constraints`. See the docstring of 

152 `validate_parameter_constraints` for a description of the accepted constraints. 

153 

154 Note that the *args and **kwargs parameters are not validated and must not be 

155 present in the parameter_constraints dictionary. 

156 

157 prefer_skip_nested_validation : bool 

158 If True, the validation of parameters of inner estimators or functions 

159 called by the decorated function will be skipped. 

160 

161 This is useful to avoid validating many times the parameters passed by the 

162 user from the public facing API. It's also useful to avoid validating 

163 parameters that we pass internally to inner functions that are guaranteed to 

164 be valid by the test suite. 

165 

166 It should be set to True for most functions, except for those that receive 

167 non-validated objects as parameters or that are just wrappers around classes 

168 because they only perform a partial validation. 

169 

170 Returns 

171 ------- 

172 decorated_function : function or method 

173 The decorated function. 

174 """ 

175 

176 def decorator(func): 

177 # The dict of parameter constraints is set as an attribute of the function 

178 # to make it possible to dynamically introspect the constraints for 

179 # automatic testing. 

180 setattr(func, "_skl_parameter_constraints", parameter_constraints) 

181 

182 @functools.wraps(func) 

183 def wrapper(*args, **kwargs): 

184 global_skip_validation = get_config()["skip_parameter_validation"] 

185 if global_skip_validation: 

186 return func(*args, **kwargs) 

187 

188 func_sig = signature(func) 

189 

190 # Map *args/**kwargs to the function signature 

191 params = func_sig.bind(*args, **kwargs) 

192 params.apply_defaults() 

193 

194 # ignore self/cls and positional/keyword markers 

195 to_ignore = [ 

196 p.name 

197 for p in func_sig.parameters.values() 

198 if p.kind in (p.VAR_POSITIONAL, p.VAR_KEYWORD) 

199 ] 

200 to_ignore += ["self", "cls"] 

201 params = {k: v for k, v in params.arguments.items() if k not in to_ignore} 

202 

203 validate_parameter_constraints( 

204 parameter_constraints, params, caller_name=func.__qualname__ 

205 ) 

206 

207 try: 

208 with config_context( 

209 skip_parameter_validation=( 

210 prefer_skip_nested_validation or global_skip_validation 

211 ) 

212 ): 

213 return func(*args, **kwargs) 

214 except InvalidParameterError as e: 

215 # When the function is just a wrapper around an estimator, we allow 

216 # the function to delegate validation to the estimator, but we replace 

217 # the name of the estimator by the name of the function in the error 

218 # message to avoid confusion. 

219 msg = re.sub( 

220 r"parameter of \w+ must be", 

221 f"parameter of {func.__qualname__} must be", 

222 str(e), 

223 ) 

224 raise InvalidParameterError(msg) from e 

225 

226 return wrapper 

227 

228 return decorator 

229 

230 

231class RealNotInt(Real): 

232 """A type that represents reals that are not instances of int. 

233 

234 Behaves like float, but also works with values extracted from numpy arrays. 

235 isintance(1, RealNotInt) -> False 

236 isinstance(1.0, RealNotInt) -> True 

237 """ 

238 

239 

240RealNotInt.register(float) 

241 

242 

243def _type_name(t): 

244 """Convert type into human readable string.""" 

245 module = t.__module__ 

246 qualname = t.__qualname__ 

247 if module == "builtins": 

248 return qualname 

249 elif t == Real: 

250 return "float" 

251 elif t == Integral: 

252 return "int" 

253 return f"{module}.{qualname}" 

254 

255 

256class _Constraint(ABC): 

257 """Base class for the constraint objects.""" 

258 

259 def __init__(self): 

260 self.hidden = False 

261 

262 @abstractmethod 

263 def is_satisfied_by(self, val): 

264 """Whether or not a value satisfies the constraint. 

265 

266 Parameters 

267 ---------- 

268 val : object 

269 The value to check. 

270 

271 Returns 

272 ------- 

273 is_satisfied : bool 

274 Whether or not the constraint is satisfied by this value. 

275 """ 

276 

277 @abstractmethod 

278 def __str__(self): 

279 """A human readable representational string of the constraint.""" 

280 

281 

282class _InstancesOf(_Constraint): 

283 """Constraint representing instances of a given type. 

284 

285 Parameters 

286 ---------- 

287 type : type 

288 The valid type. 

289 """ 

290 

291 def __init__(self, type): 

292 super().__init__() 

293 self.type = type 

294 

295 def is_satisfied_by(self, val): 

296 return isinstance(val, self.type) 

297 

298 def __str__(self): 

299 return f"an instance of {_type_name(self.type)!r}" 

300 

301 

302class _NoneConstraint(_Constraint): 

303 """Constraint representing the None singleton.""" 

304 

305 def is_satisfied_by(self, val): 

306 return val is None 

307 

308 def __str__(self): 

309 return "None" 

310 

311 

312class _NanConstraint(_Constraint): 

313 """Constraint representing the indicator `np.nan`.""" 

314 

315 def is_satisfied_by(self, val): 

316 return ( 

317 not isinstance(val, Integral) and isinstance(val, Real) and math.isnan(val) 

318 ) 

319 

320 def __str__(self): 

321 return "numpy.nan" 

322 

323 

324class _PandasNAConstraint(_Constraint): 

325 """Constraint representing the indicator `pd.NA`.""" 

326 

327 def is_satisfied_by(self, val): 

328 try: 

329 import pandas as pd 

330 

331 return isinstance(val, type(pd.NA)) and pd.isna(val) 

332 except ImportError: 

333 return False 

334 

335 def __str__(self): 

336 return "pandas.NA" 

337 

338 

339class Options(_Constraint): 

340 """Constraint representing a finite set of instances of a given type. 

341 

342 Parameters 

343 ---------- 

344 type : type 

345 

346 options : set 

347 The set of valid scalars. 

348 

349 deprecated : set or None, default=None 

350 A subset of the `options` to mark as deprecated in the string 

351 representation of the constraint. 

352 """ 

353 

354 def __init__(self, type, options, *, deprecated=None): 

355 super().__init__() 

356 self.type = type 

357 self.options = options 

358 self.deprecated = deprecated or set() 

359 

360 if self.deprecated - self.options: 

361 raise ValueError("The deprecated options must be a subset of the options.") 

362 

363 def is_satisfied_by(self, val): 

364 return isinstance(val, self.type) and val in self.options 

365 

366 def _mark_if_deprecated(self, option): 

367 """Add a deprecated mark to an option if needed.""" 

368 option_str = f"{option!r}" 

369 if option in self.deprecated: 

370 option_str = f"{option_str} (deprecated)" 

371 return option_str 

372 

373 def __str__(self): 

374 options_str = ( 

375 f"{', '.join([self._mark_if_deprecated(o) for o in self.options])}" 

376 ) 

377 return f"a {_type_name(self.type)} among {{{options_str}}}" 

378 

379 

380class StrOptions(Options): 

381 """Constraint representing a finite set of strings. 

382 

383 Parameters 

384 ---------- 

385 options : set of str 

386 The set of valid strings. 

387 

388 deprecated : set of str or None, default=None 

389 A subset of the `options` to mark as deprecated in the string 

390 representation of the constraint. 

391 """ 

392 

393 def __init__(self, options, *, deprecated=None): 

394 super().__init__(type=str, options=options, deprecated=deprecated) 

395 

396 

397class Interval(_Constraint): 

398 """Constraint representing a typed interval. 

399 

400 Parameters 

401 ---------- 

402 type : {numbers.Integral, numbers.Real, RealNotInt} 

403 The set of numbers in which to set the interval. 

404 

405 If RealNotInt, only reals that don't have the integer type 

406 are allowed. For example 1.0 is allowed but 1 is not. 

407 

408 left : float or int or None 

409 The left bound of the interval. None means left bound is -∞. 

410 

411 right : float, int or None 

412 The right bound of the interval. None means right bound is +∞. 

413 

414 closed : {"left", "right", "both", "neither"} 

415 Whether the interval is open or closed. Possible choices are: 

416 

417 - `"left"`: the interval is closed on the left and open on the right. 

418 It is equivalent to the interval `[ left, right )`. 

419 - `"right"`: the interval is closed on the right and open on the left. 

420 It is equivalent to the interval `( left, right ]`. 

421 - `"both"`: the interval is closed. 

422 It is equivalent to the interval `[ left, right ]`. 

423 - `"neither"`: the interval is open. 

424 It is equivalent to the interval `( left, right )`. 

425 

426 Notes 

427 ----- 

428 Setting a bound to `None` and setting the interval closed is valid. For instance, 

429 strictly speaking, `Interval(Real, 0, None, closed="both")` corresponds to 

430 `[0, +∞) U {+∞}`. 

431 """ 

432 

433 def __init__(self, type, left, right, *, closed): 

434 super().__init__() 

435 self.type = type 

436 self.left = left 

437 self.right = right 

438 self.closed = closed 

439 

440 self._check_params() 

441 

442 def _check_params(self): 

443 if self.type not in (Integral, Real, RealNotInt): 

444 raise ValueError( 

445 "type must be either numbers.Integral, numbers.Real or RealNotInt." 

446 f" Got {self.type} instead." 

447 ) 

448 

449 if self.closed not in ("left", "right", "both", "neither"): 

450 raise ValueError( 

451 "closed must be either 'left', 'right', 'both' or 'neither'. " 

452 f"Got {self.closed} instead." 

453 ) 

454 

455 if self.type is Integral: 

456 suffix = "for an interval over the integers." 

457 if self.left is not None and not isinstance(self.left, Integral): 

458 raise TypeError(f"Expecting left to be an int {suffix}") 

459 if self.right is not None and not isinstance(self.right, Integral): 

460 raise TypeError(f"Expecting right to be an int {suffix}") 

461 if self.left is None and self.closed in ("left", "both"): 

462 raise ValueError( 

463 f"left can't be None when closed == {self.closed} {suffix}" 

464 ) 

465 if self.right is None and self.closed in ("right", "both"): 

466 raise ValueError( 

467 f"right can't be None when closed == {self.closed} {suffix}" 

468 ) 

469 else: 

470 if self.left is not None and not isinstance(self.left, Real): 

471 raise TypeError("Expecting left to be a real number.") 

472 if self.right is not None and not isinstance(self.right, Real): 

473 raise TypeError("Expecting right to be a real number.") 

474 

475 if self.right is not None and self.left is not None and self.right <= self.left: 

476 raise ValueError( 

477 f"right can't be less than left. Got left={self.left} and " 

478 f"right={self.right}" 

479 ) 

480 

481 def __contains__(self, val): 

482 if not isinstance(val, Integral) and np.isnan(val): 

483 return False 

484 

485 left_cmp = operator.lt if self.closed in ("left", "both") else operator.le 

486 right_cmp = operator.gt if self.closed in ("right", "both") else operator.ge 

487 

488 left = -np.inf if self.left is None else self.left 

489 right = np.inf if self.right is None else self.right 

490 

491 if left_cmp(val, left): 

492 return False 

493 if right_cmp(val, right): 

494 return False 

495 return True 

496 

497 def is_satisfied_by(self, val): 

498 if not isinstance(val, self.type): 

499 return False 

500 

501 return val in self 

502 

503 def __str__(self): 

504 type_str = "an int" if self.type is Integral else "a float" 

505 left_bracket = "[" if self.closed in ("left", "both") else "(" 

506 left_bound = "-inf" if self.left is None else self.left 

507 right_bound = "inf" if self.right is None else self.right 

508 right_bracket = "]" if self.closed in ("right", "both") else ")" 

509 

510 # better repr if the bounds were given as integers 

511 if not self.type == Integral and isinstance(self.left, Real): 

512 left_bound = float(left_bound) 

513 if not self.type == Integral and isinstance(self.right, Real): 

514 right_bound = float(right_bound) 

515 

516 return ( 

517 f"{type_str} in the range " 

518 f"{left_bracket}{left_bound}, {right_bound}{right_bracket}" 

519 ) 

520 

521 

522class _ArrayLikes(_Constraint): 

523 """Constraint representing array-likes""" 

524 

525 def is_satisfied_by(self, val): 

526 return _is_arraylike_not_scalar(val) 

527 

528 def __str__(self): 

529 return "an array-like" 

530 

531 

532class _SparseMatrices(_Constraint): 

533 """Constraint representing sparse matrices.""" 

534 

535 def is_satisfied_by(self, val): 

536 return issparse(val) 

537 

538 def __str__(self): 

539 return "a sparse matrix" 

540 

541 

542class _Callables(_Constraint): 

543 """Constraint representing callables.""" 

544 

545 def is_satisfied_by(self, val): 

546 return callable(val) 

547 

548 def __str__(self): 

549 return "a callable" 

550 

551 

552class _RandomStates(_Constraint): 

553 """Constraint representing random states. 

554 

555 Convenience class for 

556 [Interval(Integral, 0, 2**32 - 1, closed="both"), np.random.RandomState, None] 

557 """ 

558 

559 def __init__(self): 

560 super().__init__() 

561 self._constraints = [ 

562 Interval(Integral, 0, 2**32 - 1, closed="both"), 

563 _InstancesOf(np.random.RandomState), 

564 _NoneConstraint(), 

565 ] 

566 

567 def is_satisfied_by(self, val): 

568 return any(c.is_satisfied_by(val) for c in self._constraints) 

569 

570 def __str__(self): 

571 return ( 

572 f"{', '.join([str(c) for c in self._constraints[:-1]])} or" 

573 f" {self._constraints[-1]}" 

574 ) 

575 

576 

577class _Booleans(_Constraint): 

578 """Constraint representing boolean likes. 

579 

580 Convenience class for 

581 [bool, np.bool_, Integral (deprecated)] 

582 """ 

583 

584 def __init__(self): 

585 super().__init__() 

586 self._constraints = [ 

587 _InstancesOf(bool), 

588 _InstancesOf(np.bool_), 

589 ] 

590 

591 def is_satisfied_by(self, val): 

592 return any(c.is_satisfied_by(val) for c in self._constraints) 

593 

594 def __str__(self): 

595 return ( 

596 f"{', '.join([str(c) for c in self._constraints[:-1]])} or" 

597 f" {self._constraints[-1]}" 

598 ) 

599 

600 

601class _VerboseHelper(_Constraint): 

602 """Helper constraint for the verbose parameter. 

603 

604 Convenience class for 

605 [Interval(Integral, 0, None, closed="left"), bool, numpy.bool_] 

606 """ 

607 

608 def __init__(self): 

609 super().__init__() 

610 self._constraints = [ 

611 Interval(Integral, 0, None, closed="left"), 

612 _InstancesOf(bool), 

613 _InstancesOf(np.bool_), 

614 ] 

615 

616 def is_satisfied_by(self, val): 

617 return any(c.is_satisfied_by(val) for c in self._constraints) 

618 

619 def __str__(self): 

620 return ( 

621 f"{', '.join([str(c) for c in self._constraints[:-1]])} or" 

622 f" {self._constraints[-1]}" 

623 ) 

624 

625 

626class MissingValues(_Constraint): 

627 """Helper constraint for the `missing_values` parameters. 

628 

629 Convenience for 

630 [ 

631 Integral, 

632 Interval(Real, None, None, closed="both"), 

633 str, # when numeric_only is False 

634 None, # when numeric_only is False 

635 _NanConstraint(), 

636 _PandasNAConstraint(), 

637 ] 

638 

639 Parameters 

640 ---------- 

641 numeric_only : bool, default=False 

642 Whether to consider only numeric missing value markers. 

643 

644 """ 

645 

646 def __init__(self, numeric_only=False): 

647 super().__init__() 

648 

649 self.numeric_only = numeric_only 

650 

651 self._constraints = [ 

652 _InstancesOf(Integral), 

653 # we use an interval of Real to ignore np.nan that has its own constraint 

654 Interval(Real, None, None, closed="both"), 

655 _NanConstraint(), 

656 _PandasNAConstraint(), 

657 ] 

658 if not self.numeric_only: 

659 self._constraints.extend([_InstancesOf(str), _NoneConstraint()]) 

660 

661 def is_satisfied_by(self, val): 

662 return any(c.is_satisfied_by(val) for c in self._constraints) 

663 

664 def __str__(self): 

665 return ( 

666 f"{', '.join([str(c) for c in self._constraints[:-1]])} or" 

667 f" {self._constraints[-1]}" 

668 ) 

669 

670 

671class HasMethods(_Constraint): 

672 """Constraint representing objects that expose specific methods. 

673 

674 It is useful for parameters following a protocol and where we don't want to impose 

675 an affiliation to a specific module or class. 

676 

677 Parameters 

678 ---------- 

679 methods : str or list of str 

680 The method(s) that the object is expected to expose. 

681 """ 

682 

683 @validate_params( 

684 {"methods": [str, list]}, 

685 prefer_skip_nested_validation=True, 

686 ) 

687 def __init__(self, methods): 

688 super().__init__() 

689 if isinstance(methods, str): 

690 methods = [methods] 

691 self.methods = methods 

692 

693 def is_satisfied_by(self, val): 

694 return all(callable(getattr(val, method, None)) for method in self.methods) 

695 

696 def __str__(self): 

697 if len(self.methods) == 1: 

698 methods = f"{self.methods[0]!r}" 

699 else: 

700 methods = ( 

701 f"{', '.join([repr(m) for m in self.methods[:-1]])} and" 

702 f" {self.methods[-1]!r}" 

703 ) 

704 return f"an object implementing {methods}" 

705 

706 

707class _IterablesNotString(_Constraint): 

708 """Constraint representing iterables that are not strings.""" 

709 

710 def is_satisfied_by(self, val): 

711 return isinstance(val, Iterable) and not isinstance(val, str) 

712 

713 def __str__(self): 

714 return "an iterable" 

715 

716 

717class _CVObjects(_Constraint): 

718 """Constraint representing cv objects. 

719 

720 Convenient class for 

721 [ 

722 Interval(Integral, 2, None, closed="left"), 

723 HasMethods(["split", "get_n_splits"]), 

724 _IterablesNotString(), 

725 None, 

726 ] 

727 """ 

728 

729 def __init__(self): 

730 super().__init__() 

731 self._constraints = [ 

732 Interval(Integral, 2, None, closed="left"), 

733 HasMethods(["split", "get_n_splits"]), 

734 _IterablesNotString(), 

735 _NoneConstraint(), 

736 ] 

737 

738 def is_satisfied_by(self, val): 

739 return any(c.is_satisfied_by(val) for c in self._constraints) 

740 

741 def __str__(self): 

742 return ( 

743 f"{', '.join([str(c) for c in self._constraints[:-1]])} or" 

744 f" {self._constraints[-1]}" 

745 ) 

746 

747 

748class Hidden: 

749 """Class encapsulating a constraint not meant to be exposed to the user. 

750 

751 Parameters 

752 ---------- 

753 constraint : str or _Constraint instance 

754 The constraint to be used internally. 

755 """ 

756 

757 def __init__(self, constraint): 

758 self.constraint = constraint 

759 

760 

761def generate_invalid_param_val(constraint): 

762 """Return a value that does not satisfy the constraint. 

763 

764 Raises a NotImplementedError if there exists no invalid value for this constraint. 

765 

766 This is only useful for testing purpose. 

767 

768 Parameters 

769 ---------- 

770 constraint : _Constraint instance 

771 The constraint to generate a value for. 

772 

773 Returns 

774 ------- 

775 val : object 

776 A value that does not satisfy the constraint. 

777 """ 

778 if isinstance(constraint, StrOptions): 

779 return f"not {' or '.join(constraint.options)}" 

780 

781 if isinstance(constraint, MissingValues): 

782 return np.array([1, 2, 3]) 

783 

784 if isinstance(constraint, _VerboseHelper): 

785 return -1 

786 

787 if isinstance(constraint, HasMethods): 

788 return type("HasNotMethods", (), {})() 

789 

790 if isinstance(constraint, _IterablesNotString): 

791 return "a string" 

792 

793 if isinstance(constraint, _CVObjects): 

794 return "not a cv object" 

795 

796 if isinstance(constraint, Interval) and constraint.type is Integral: 

797 if constraint.left is not None: 

798 return constraint.left - 1 

799 if constraint.right is not None: 

800 return constraint.right + 1 

801 

802 # There's no integer outside (-inf, +inf) 

803 raise NotImplementedError 

804 

805 if isinstance(constraint, Interval) and constraint.type in (Real, RealNotInt): 

806 if constraint.left is not None: 

807 return constraint.left - 1e-6 

808 if constraint.right is not None: 

809 return constraint.right + 1e-6 

810 

811 # bounds are -inf, +inf 

812 if constraint.closed in ("right", "neither"): 

813 return -np.inf 

814 if constraint.closed in ("left", "neither"): 

815 return np.inf 

816 

817 # interval is [-inf, +inf] 

818 return np.nan 

819 

820 raise NotImplementedError 

821 

822 

823def generate_valid_param(constraint): 

824 """Return a value that does satisfy a constraint. 

825 

826 This is only useful for testing purpose. 

827 

828 Parameters 

829 ---------- 

830 constraint : Constraint instance 

831 The constraint to generate a value for. 

832 

833 Returns 

834 ------- 

835 val : object 

836 A value that does satisfy the constraint. 

837 """ 

838 if isinstance(constraint, _ArrayLikes): 

839 return np.array([1, 2, 3]) 

840 

841 if isinstance(constraint, _SparseMatrices): 

842 return csr_matrix([[0, 1], [1, 0]]) 

843 

844 if isinstance(constraint, _RandomStates): 

845 return np.random.RandomState(42) 

846 

847 if isinstance(constraint, _Callables): 

848 return lambda x: x 

849 

850 if isinstance(constraint, _NoneConstraint): 

851 return None 

852 

853 if isinstance(constraint, _InstancesOf): 

854 if constraint.type is np.ndarray: 

855 # special case for ndarray since it can't be instantiated without arguments 

856 return np.array([1, 2, 3]) 

857 

858 if constraint.type in (Integral, Real): 

859 # special case for Integral and Real since they are abstract classes 

860 return 1 

861 

862 return constraint.type() 

863 

864 if isinstance(constraint, _Booleans): 

865 return True 

866 

867 if isinstance(constraint, _VerboseHelper): 

868 return 1 

869 

870 if isinstance(constraint, MissingValues) and constraint.numeric_only: 

871 return np.nan 

872 

873 if isinstance(constraint, MissingValues) and not constraint.numeric_only: 

874 return "missing" 

875 

876 if isinstance(constraint, HasMethods): 

877 return type( 

878 "ValidHasMethods", (), {m: lambda self: None for m in constraint.methods} 

879 )() 

880 

881 if isinstance(constraint, _IterablesNotString): 

882 return [1, 2, 3] 

883 

884 if isinstance(constraint, _CVObjects): 

885 return 5 

886 

887 if isinstance(constraint, Options): # includes StrOptions 

888 for option in constraint.options: 

889 return option 

890 

891 if isinstance(constraint, Interval): 

892 interval = constraint 

893 if interval.left is None and interval.right is None: 

894 return 0 

895 elif interval.left is None: 

896 return interval.right - 1 

897 elif interval.right is None: 

898 return interval.left + 1 

899 else: 

900 if interval.type is Real: 

901 return (interval.left + interval.right) / 2 

902 else: 

903 return interval.left + 1 

904 

905 raise ValueError(f"Unknown constraint type: {constraint}")