From 617bace854cc79678c8eb5d7bf96412659c6280c Mon Sep 17 00:00:00 2001 From: Sait Cakmak Date: Wed, 10 Apr 2024 19:09:32 -0700 Subject: [PATCH 1/4] numpy 2.0 compatibility --- cma/bbobbenchmarks.py | 4 +- cma/constraints_handler.py | 15 +++--- cma/evolution_strategy.py | 64 ++++++++++++------------ cma/fitness_functions.py | 22 ++++---- cma/fitness_models.py | 9 ++-- cma/fitness_transformations.py | 18 +++---- cma/logger.py | 18 +++---- cma/optimization_tools.py | 17 +++---- cma/sampler.py | 1 - cma/sigma_adaptation.py | 7 ++- cma/transformations.py | 51 +++++++++---------- cma/utilities/math.py | 4 +- cma/utilities/utils.py | 11 ++-- cma_signals.in | 15 +++--- notebooks/notebook-usecases-basics.ipynb | 2 +- 15 files changed, 125 insertions(+), 133 deletions(-) diff --git a/cma/bbobbenchmarks.py b/cma/bbobbenchmarks.py index 14ae4ba..f6d1f4f 100644 --- a/cma/bbobbenchmarks.py +++ b/cma/bbobbenchmarks.py @@ -69,7 +69,7 @@ >>> f3 = bn.F3(13) # instantiate instance 13 of function f3 ->>> f3([0, 1, 2]) # short-cut for f3.evaluate([0, 1, 2]) # doctest:+ELLIPSIS +>>> f3([0, 1, 2]).item() # short-cut for f3.evaluate([0, 1, 2]) # doctest:+ELLIPSIS 59.8733529... >>> print(bn.instantiate(5)[1]) # returns function instance and optimal f-value 51.53 @@ -395,7 +395,7 @@ def __call__(self, x): # makes the instances callable >>> from cma import bbobbenchmarks as bn >>> f3 = bn.F3(13) # instantiate function 3 on instance 13 - >>> 59.8733529 < f3([0, 1, 2]) < 59.87335292 # call f3, same as f3.evaluate([0, 1, 2]) + >>> 59.8733529 < f3([0, 1, 2]).item() < 59.87335292 # call f3, same as f3.evaluate([0, 1, 2]) True """ diff --git a/cma/constraints_handler.py b/cma/constraints_handler.py index 5106273..00ea122 100644 --- a/cma/constraints_handler.py +++ b/cma/constraints_handler.py @@ -108,12 +108,12 @@ def _get_bounds(self, ib, dimension): sign_ = 2 * ib - 1 assert sign_**2 == 1 if self.bounds is None or self.bounds[ib] is None: - return np.array(dimension * [sign_ * np.Inf]) + return np.array(dimension * [sign_ * np.inf]) res = [] for i in range(dimension): res.append(self.bounds[ib][min([i, len(self.bounds[ib]) - 1])]) if res[-1] is None: - res[-1] = sign_ * np.Inf + res[-1] = sign_ * np.inf return np.array(res) def has_bounds(self): @@ -328,7 +328,7 @@ class BoundPenalty(BoundaryHandlerBase): >>> if res[1] >= 13.76: print(res) # should never happen Reference: Hansen et al 2009, A Method for Handling Uncertainty... - IEEE TEC, with addendum, see + IEEE TEC, with addendum, see https://ieeexplore.ieee.org/abstract/document/4634579 https://hal.inria.fr/inria-00276216/file/TEC2008.pdf @@ -411,7 +411,7 @@ def __call__(self, x, archive, gp): gamma = list(self.gamma) # fails if self.gamma is a scalar for i in sorted(gp.fixed_values): # fails if fixed_values is None gamma.insert(i, 0.0) - gamma = np.array(gamma, copy=False) + gamma = np.asarray(gamma) except TypeError: gamma = self.gamma pen = [] @@ -737,7 +737,7 @@ def init(self, dimension, force=False): if force or self.chi_exponent_threshold is None: # threshold for number of consecutive changes # self.chi_exponent_threshold = 5 + self.dimension**0.5 # previous value, probably too small in >- 20-D # self.chi_exponent_threshold = 5 + self.dimension**0.75 # sweeps in aug-lag4-mu-update.ipynv - self.chi_exponent_threshold = 2 + self.dimension + self.chi_exponent_threshold = 2 + self.dimension if force or self.chi_exponent_factor is None: # factor used in shape_exponent # self.chi_exponent_factor = 3 / dimension**0.5 # previous value # self.chi_exponent_factor = 1 * dimension**0.25 / self.chi_exponent_threshold # steepness of ramp up @@ -835,7 +835,7 @@ class AugmentedLagrangian(object): More testing based on the simpler `ConstrainedFitnessAL` interface: >>> import cma - >>> for algorithm, evals in zip((0, 1, 2, 3), (2000, 2200, 1500, 1800)): + >>> for algorithm, evals in zip((0, 1, 2, 3), (2000, 2200, 1500, 1800)): ... alf = cma.ConstrainedFitnessAL(cma.ff.sphere, lambda x: [x[0] + 1], 3, ... find_feasible_first=True) ... _ = alf.al.set_algorithm(algorithm) @@ -1302,7 +1302,7 @@ class ConstrainedFitnessAL: (g_i > 0) x g_i^2`` and omits ``f + sum_i lam_i g_i`` altogether. Whenever a feasible solution is found, the `finding_feasible` flag is reset to `False`. - + `find_feasible(es)` sets ``finding_feasible = True`` and uses `es.optimize` to optimize `self.__call__`. This works well with `CMAEvolutionStrategy` but may easily fail with solvers that do not @@ -1576,4 +1576,3 @@ def log_in_es(self, es, f, g): ] except: pass - diff --git a/cma/evolution_strategy.py b/cma/evolution_strategy.py index 834ff8b..9d5a892 100644 --- a/cma/evolution_strategy.py +++ b/cma/evolution_strategy.py @@ -32,9 +32,9 @@ # TODO: keep best ten solutions # TODO: implement constraints handling # TODO: eigh(): thorough testing would not hurt -# TODO: (partly done) apply style guide -# WON'T FIX ANYTIME SOON (done within fmin): implement bipop in a separate -# algorithm as meta portfolio algorithm of IPOP and a local restart +# TODO: (partly done) apply style guide +# WON'T FIX ANYTIME SOON (done within fmin): implement bipop in a separate +# algorithm as meta portfolio algorithm of IPOP and a local restart # option to be implemented # in fmin (e.g. option restart_mode in [IPOP, local]) # DONE: extend function unitdoctest, or use unittest? @@ -264,7 +264,7 @@ def __init__(self): self.cc_multiplier = 1.0 ## [~0.01, ~20] l self.cs_multiplier = 1.0 ## [~0.01, ~10] l # learning rate for cs self.CSA_dampfac = 1.0 ## [~0.01, ~10] - self.CMA_dampsvec_fac = None ## [~0.01, ~100] # def=np.Inf or 0.5, not clear whether this is a log parameter + self.CMA_dampsvec_fac = None ## [~0.01, ~100] # def=np.inf or 0.5, not clear whether this is a log parameter self.CMA_dampsvec_fade = 0.1 ## [0, ~2] # exponents for learning rates @@ -438,7 +438,7 @@ def cma_default_options_( # to get keyword completion back CMA_rankmu='1.0 # multiplier for rank-mu update learning rate of covariance matrix', CMA_rankone='1.0 # multiplier for rank-one update learning rate of covariance matrix', CMA_recombination_weights='None # a list, see class RecombinationWeights, overwrites CMA_mu and popsize options', - CMA_dampsvec_fac='np.Inf # tentative and subject to changes, 0.5 would be a "default" damping for sigma vector update', + CMA_dampsvec_fac='np.inf # tentative and subject to changes, 0.5 would be a "default" damping for sigma vector update', CMA_dampsvec_fade='0.1 # tentative fading out parameter for sigma vector update', CMA_teststds='None # factors for non-isotropic initial distr. of C, mainly for test purpose, see CMA_stds for production', CMA_stds='None # multipliers for sigma0 in each coordinate (not represented in C), or use `cma.ScaleCoordinates` instead', @@ -461,7 +461,7 @@ def cma_default_options_( # to get keyword completion back fixed_variables='None # dictionary with index-value pairs like {0:1.1, 2:0.1} that are not optimized', ftarget='-inf #v target function value, minimization', integer_variables='[] # index list, invokes basic integer handling: prevent std dev to become too small in the given variables', - is_feasible='is_feasible #v a function that computes feasibility, by default lambda x, f: f not in (None, np.NaN)', + is_feasible='is_feasible #v a function that computes feasibility, by default lambda x, f: f not in (None, np.nan)', maxfevals='inf #v maximum number of function evaluations', maxiter='100 + 150 * (N+3)**2 // popsize**0.5 #v maximum number of iterations', mean_shift_line_samples='False #v sample two new solutions colinear to previous mean shift', @@ -543,7 +543,7 @@ def safe_str(s): return purecma.safe_str(s.split('#')[0], dict([k, k] for k in ['True', 'False', 'None', - 'N', 'dim', 'popsize', 'int', 'np.Inf', 'inf', + 'N', 'dim', 'popsize', 'int', 'np.inf', 'inf', 'np.log', 'np.random.randn', 'time', # 'cma_signals.in', 'outcmaes/', 'BoundTransform', 'is_feasible', 'np.linalg.eigh', @@ -629,7 +629,7 @@ def versatile_options(): to date. The string ' #v ' in the default value indicates a versatile - option that can be changed any time, however a string will not + option that can be changed any time, however a string will not necessarily be evaluated again. """ @@ -1073,7 +1073,7 @@ def _generate(self): self.countevals, self.countiter, self.gp.pheno(self.mean, into_bounds=self.boundary_handler.repair), - self.stds)) # + self.stds)) # class CMAEvolutionStrategy(interfaces.OOOptimizer): """CMA-ES stochastic optimizer class with ask-and-tell interface. @@ -1213,9 +1213,9 @@ class (see also there). An object instance is generated from:: ... fit, X = [], [] ... while len(X) < es.popsize: ... curr_fit = None - ... while curr_fit in (None, np.NaN): + ... while curr_fit in (None, np.nan): ... x = es.ask(1)[0] - ... curr_fit = cma.ff.somenan(x, cma.ff.elli) # might return np.NaN + ... curr_fit = cma.ff.somenan(x, cma.ff.elli) # might return np.nan ... X.append(x) ... fit.append(curr_fit) ... es.tell(X, fit) @@ -1567,7 +1567,7 @@ def identity(x): # self.mean = self.gp.geno(array(self.x0, copy=True), copy_if_changed=False) self.N = len(self.mean) assert N == self.N - # self.fmean = np.NaN # TODO name should change? prints nan in output files (OK with matlab&octave) + # self.fmean = np.nan # TODO name should change? prints nan in output files (OK with matlab&octave) # self.fmean_noise_free = 0. # for output only self.sp = _CMAParameters(N, opts, verbose=opts['verbose'] > 0) @@ -1926,12 +1926,12 @@ def get_i(bnds, i): def _copy_light(self, sigma=None, inopts=None): """tentative copy of self, versatile (interface and functionalities may change). - + `sigma` overwrites the original initial `sigma`. `inopts` allows to overwrite any of the original options. This copy may not work as expected depending on the used sampler. - + Copy mean and sample distribution parameters and input options. Do not copy evolution paths, termination status or other state variables. @@ -1961,8 +1961,8 @@ def _copy_light(self, sigma=None, inopts=None): except: warnings.warn("self.sm.C.copy failed") es.sm.update_now(-1) # make B and D consistent with C es._updateBDfromSM() - return es - + return es + # ____________________________________________________________ # ____________________________________________________________ def ask(self, number=None, xmean=None, sigma_fac=1, @@ -2446,7 +2446,7 @@ def ask_and_eval(self, func, args=(), gradf=None, number=None, xmean=None, sigma ------- While ``not self.is_feasible(x, func(x))`` new solutions are sampled. By default - ``self.is_feasible == cma.feasible == lambda x, f: f not in (None, np.NaN)``. + ``self.is_feasible == cma.feasible == lambda x, f: f not in (None, np.nan)``. The argument to `func` can be freely modified within `func`. Depending on the ``CMA_mirrors`` option, some solutions are not @@ -2493,7 +2493,7 @@ def ask_and_eval(self, func, args=(), gradf=None, number=None, xmean=None, sigma is_feasible = self.opts['is_feasible'] # do the work - fit = [] # or np.NaN * np.empty(number) + fit = [] # or np.nan * np.empty(number) X_first = self.ask(popsize, xmean=xmean, gradf=gradf, args=args) if xmean is None: xmean = self.mean # might have changed in self.ask @@ -2595,7 +2595,7 @@ def _prepare_injection_directions(self): if self.mean_shift_samples: ary = [self.mean - self.mean_old] ary.append(self.mean_old - self.mean) # another copy! - if np.alltrue(ary[-1] == 0.0): + if np.all(ary[-1] == 0.0): utils.print_warning('zero mean shift encountered', '_prepare_injection_directions', 'CMAEvolutionStrategy', self.countiter) @@ -2878,7 +2878,7 @@ def tell(self, solutions, function_values, check_points=None, x_elit = self.mean0.copy() # self.clip_or_fit_solutions([x_elit], [0]) # just calls repair_genotype self.random_rescale_to_mahalanobis(x_elit) - pop = array([x_elit] + list(pop), copy=False) + pop = np.asarray([x_elit] + list(pop)) utils.print_message('initial solution injected %f<%f' % (self.f0, fit.fit[0]), 'tell', 'CMAEvolutionStrategy', @@ -3144,8 +3144,8 @@ def tell(self, solutions, function_values, check_points=None, copy_if_changed=False), copy=False) if _new_injections: self.pop_injection_directions = self._prepare_injection_directions() - if (self.opts['verbose'] > 4 and self.countiter < 3 and - not isinstance(self.adapt_sigma, CMAAdaptSigmaTPA) and + if (self.opts['verbose'] > 4 and self.countiter < 3 and + not isinstance(self.adapt_sigma, CMAAdaptSigmaTPA) and len(self.pop_injection_directions)): utils.print_message(' %d directions prepared for injection %s' % (len(self.pop_injection_directions), @@ -3221,7 +3221,7 @@ def inject(self, solutions, force=None): if len(solution) != self.N: raise ValueError('method `inject` needs a list or array' + (' each el with dimension (`len`) %d' % self.N)) - solution = array(solution, copy=False, dtype=float) + solution = np.asarray(solution, dtype=float) if force: self.pop_injection_solutions.append(solution) else: @@ -3342,8 +3342,8 @@ def repair_genotype(self, x, copy_if_changed=False): ``N**0.5 + 2 * N / (N + 2)``, but the specific repair mechanism may change in future. """ - x = array(x, copy=False) - mold = array(self.mean, copy=False) + x = np.asarray(x) + mold = np.asarray(self.mean) if 1 < 3: # hard clip at upper_length upper_length = self.N**0.5 + 2 * self.N / (self.N + 2) # should become an Option, but how? e.g. [0, 2, 2] @@ -3795,7 +3795,7 @@ def __init__(self, x=None): def set_params(self, param_values, names=( 'delta', 'time_delta_offset', 'time_delta_frac', 'time_delta_expo')): """`param_values` is a `list` conforming to ``CMAOptions['tolxstagnation']``. - + Do nothing if ``param_values in (None, True)``, set ``delta = -1`` if ``param_values is False``. @@ -3830,7 +3830,7 @@ def update(self, x): return self @property def time_threshold(self): - return (self.time_delta_offset + + return (self.time_delta_offset + self.time_delta_frac * self.count**self.time_delta_expo) @property def stop(self): @@ -4014,8 +4014,8 @@ def _update(self, es): es.timer.elapsed > opts['timeout'], es.timer.elapsed if self._get_value else None) except AttributeError: - if es.countiter <= 0: - pass + if es.countiter <= 0: + pass # else: raise if 11 < 3 and 2 * l < len(es.fit.histbest): # TODO: this might go wrong, because the nb of written columns changes @@ -4113,12 +4113,12 @@ class _CMAParameters(object): {'CMA_on': True, 'N': 20, 'c1': 0.00437235..., - 'c1_sep': 0.0343279..., + 'c1_sep': np.float64(0.0343279..., 'cc': 0.171767..., 'cc_sep': 0.252594..., 'cmean': array(1..., 'cmu': 0.00921656..., - 'cmu_sep': 0.0565385..., + 'cmu_sep': np.float64(0.0565385..., 'lam_mirr': 0, 'mu': 6, 'popsize': 12, @@ -5268,7 +5268,7 @@ def update(es): def f_post(x): return sum(gi ** 2 for gi in g(x) if gi > 0) + sum( hi ** 2 for hi in h(x) if hi ** 2 > post_optimization ** 2) - + kwargs_post = kwargs.copy() kwargs_post.setdefault('options', {})['ftarget'] = 0 diff --git a/cma/fitness_functions.py b/cma/fitness_functions.py index 8b7b536..0f579f7 100644 --- a/cma/fitness_functions.py +++ b/cma/fitness_functions.py @@ -95,9 +95,9 @@ def rot(self, x, fun, rot=1, args=()): else: return fun(x) def somenan(self, x, fun, p=0.1): - """returns sometimes np.NaN, otherwise fun(x)""" + """returns sometimes np.nan, otherwise fun(x)""" if np.random.rand(1) < p: - return np.NaN + return np.nan else: return fun(x) @@ -130,9 +130,9 @@ def subspace_sphere(self, x, visible_ratio=1/2): def pnorm(self, x, p=0.5): return sum(np.abs(x)**p)**(1./p) def grad_sphere(self, x, *args): - return 2*array(x, copy=False) + return 2*np.asarray(x) def grad_to_one(self, x, *args): - return array(x, copy=False) - 1 + return np.asarray(x) - 1 def sphere_pos(self, x): """Sphere (squared norm) test objective function""" # return np.random.rand(1)[0]**0 * sum(x**2) + 1 * np.random.rand(1)[0] @@ -179,17 +179,17 @@ def cornersphere(self, x): """Sphere (squared norm) test objective function constraint to the corner""" nconstr = len(x) - 0 if any(x[:nconstr] < 1): - return np.NaN + return np.nan return sum(x**2) - nconstr def cornerelli(self, x): """ """ if any(x < 1): - return np.NaN + return np.nan return self.elli(x) - self.elli(np.ones(len(x))) def cornerellirot(self, x): """ """ if any(x < 1): - return np.NaN + return np.nan return self.ellirot(x) def normalSkew(self, f): N = np.random.randn(1)[0]**2 @@ -309,7 +309,7 @@ def ellihalfrot(self, x, frac=0.5, cond1=1e6, cond2=1e6): def grad_elli(self, x, *args): cond = 1e6 N = len(x) - return 2 * cond**(np.arange(N) / (N - 1.)) * array(x, copy=False) + return 2 * cond**(np.arange(N) / (N - 1.)) * np.asarray(x) def fun_as_arg(self, x, *args): """``fun_as_arg(x, fun, *more_args)`` calls ``fun(x, *more_args)``. @@ -471,7 +471,7 @@ def optprob(self, x): def lincon(self, x, theta=0.01): """ridge like linear function with one linear constraint""" if x[0] < 0: - return np.NaN + return np.nan return theta * x[1] + x[0] def rosen_nesterov(self, x, rho=100): """needs exponential number of steps in a non-increasing @@ -536,7 +536,7 @@ def xinsheyang2(self, x, termination_friendly=True): to the global optimum >= 1. That is, the global optimum is surrounded by 3^n - 1 local optima that have the better values the further they are away from the global optimum. - + Conclusion: it is a rather suspicious sign if an algorithm finds the global optimum of this function in larger dimension. @@ -565,7 +565,7 @@ def binval(x, foffset=1e-2 - 1e-5): """return sum_i(0 if (1 <= x[i] < 2) else 2**i)**(1/n)""" s = sum([0 if 1 <= val < 2 else 2**i for i, val in enumerate(x)]) return s**(1/len(x)) + (s == 0) * foffset - + def _fetch_bbob_fcts(self): """Fetch GECCO BBOB 2009 functions from WWW and set as `self.BBOB`. diff --git a/cma/fitness_models.py b/cma/fitness_models.py index b536fce..a36ba20 100644 --- a/cma/fitness_models.py +++ b/cma/fitness_models.py @@ -472,10 +472,10 @@ class LQModel(object): >>> # fm.Logger = Logger For results see: - + Hansen (2019). A Global Surrogate Model for CMA-ES. In Genetic and Evolutionary Computation Conference (GECCO 2019), Proceedings, ACM. - + lq-CMA-ES at http://lq-cma.gforge.inria.fr/ppdata-archives/pap-gecco2019/figure5/ """ @@ -810,14 +810,14 @@ def _hash(self, x): # let _hash be idempotent: _hash = _hash o _hash return x elif isinstance(x, np.ndarray): - try: + try: return hash(x.tobytes()) # fails with numpy < 1.9 except AttributeError: return hash(bytes(x)) # hash(tuple(x)) would be faster when x.size < 1e4 else: try: return hash(x) - except TypeError: + except TypeError: # Data type must be immutable, transform into tuple first return hash(tuple(x)) @@ -942,4 +942,3 @@ def minY(self): def eigenvalues(self): """eigenvalues of the Hessian of the model""" return sorted(np.linalg.eigvals(self.hessian)) - diff --git a/cma/fitness_transformations.py b/cma/fitness_transformations.py index 03031fe..4af16cd 100644 --- a/cma/fitness_transformations.py +++ b/cma/fitness_transformations.py @@ -146,7 +146,7 @@ class ComposedFunction(Function, list): - The parallelizing call with a list of solutions of the `Function` class is not inherited. The inheritence from `Function` is rather - declarative than funtional and could be omitted. + declarative than funtional and could be omitted. """ def __init__(self, list_of_functions, list_of_inverses=None): @@ -352,14 +352,14 @@ class ScaleCoordinates(ComposedFunction): >>> import numpy as np >>> import cma >>> fun = cma.ScaleCoordinates(cma.ff.sphere, upper=[30, 1]) - >>> fun([1, 1]) == 30**2 + 1**2 + >>> fun([1, 1]).item() == 30**2 + 1**2 True - >>> list(fun.transform([1, 1])), list(fun.transform([0.2, 0.2])) + >>> fun.transform([1, 1]).tolist(), fun.transform([0.2, 0.2]).tolist() ([30.0, 1.0], [6.0, 0.2]) - >>> list(fun.inverse(fun.transform([0.1, 0.3]))) + >>> fun.inverse(fun.transform([0.1, 0.3])).tolist() [0.1, 0.3] >>> fun = cma.ScaleCoordinates(cma.ff.sphere, upper=[31, 3], lower=[1, 2]) - >>> -1e-9 < fun([1, -1]) - (31**2 + 1**2) < 1e-9 + >>> -1e-9 < fun([1, -1]).item() - (31**2 + 1**2) < 1e-9 True >>> f = cma.ScaleCoordinates(cma.ff.sphere, [100, 1]) >>> assert f[0] == cma.ff.sphere # first element of f-composition @@ -369,7 +369,7 @@ class ScaleCoordinates(ComposedFunction): >>> assert np.all(f.inverse(f.scale_and_offset([1, 2, 3, 4])) == ... np.asarray([1, 2, 3, 4])) >>> f = cma.ScaleCoordinates(f, [-2, 7], [2, 3, 4]) # last is recycled - >>> f([5, 6]) == sum(x**2 for x in [100 * -2 * (5 - 2), 7 * (6 - 3)]) + >>> f([5, 6]).item() == sum(x**2 for x in [100 * -2 * (5 - 2), 7 * (6 - 3)]) True See also these [Practical Hints](https://cma-es.github.io/cmaes_sourcecode_page.html#practical) @@ -593,7 +593,7 @@ def __init__(self, fitness_function, probability_of_nan=0.1): def __call__(self, x, *args): Function.__call__(self, x, *args) if np.random.rand(1) <= self.p: - return np.NaN + return np.nan else: return self.fitness_function(x, *args) @@ -639,8 +639,8 @@ class IntegerMixedFunction(ComposedFunction): ``1 / (2 * len(integer_variable_indices) + 1)``, in which case in an independent model at least 33% (1 integer variable) -> 39% (many integer variables) of the solutions should have an integer mutation - on average. Option ``integer_variables`` of `cma.CMAOptions` - implements this simple measure. + on average. Option ``integer_variables`` of `cma.CMAOptions` + implements this simple measure. """ def __init__(self, function, integer_variable_indices, operator=np.floor, copy_arg=True): """apply operator(x[i]) for i in integer_variable_indices before to call function(x)""" diff --git a/cma/logger.py b/cma/logger.py index ffc5538..2c2d640 100644 --- a/cma/logger.py +++ b/cma/logger.py @@ -680,7 +680,7 @@ def zip(self, filename=None, unique=True): # extract: zipfile.ZipFile(filename, "r").extractall(dirname) ? # tarfile.open(filename, "r").extractall() import tarfile - + def unique_time_stamp(decimals=3): "a unique timestamp up to 1/10^decimals seconds" s = '{:.' + str(decimals) + 'f}' + '.' * (decimals == 0) @@ -1140,7 +1140,7 @@ class _tmp: pass return self def plot_sigvec(self, iabscissa=0, idx=None): """plot (outer) scaling from diagonal decoding. - + ``iabscissa=1`` plots vs function evaluations `idx` picks variables to plot if len(idx) < N, otherwise it picks @@ -1305,9 +1305,9 @@ def plot_divers(self, iabscissa=0, foffset=1e-19, fshift=0, message=None): dat.f[0, i] = dat.f[1, i] minfit = np.nanmin(dat.f[:, 5]) dfit1 = dat.f[:, 5] - minfit # why not using idx? - dfit1[dfit1 < 1e-98] = np.NaN + dfit1[dfit1 < 1e-98] = np.nan dfit2 = dat.f[:, 5] - dat.f[-1, 5] - dfit2[dfit2 < 1e-28] = np.NaN + dfit2[dfit2 < 1e-28] = np.nan self._enter_plotting() _x = _monotone_abscissa(dat.f[:, iabscissa], iabscissa) @@ -1322,7 +1322,7 @@ def plot_divers(self, iabscissa=0, foffset=1e-19, fshift=0, message=None): # (larger indices): additional fitness data, for example constraints values if dat.f.shape[1] > 9: # dd = abs(dat.f[:,7:]) + 10*foffset - # dd = _where(dat.f[:,7:]==0, np.NaN, dd) # cannot be + # dd = _where(dat.f[:,7:]==0, np.nan, dd) # cannot be semilogy(_x, np.abs(dat.f[:, 8:]) + 10 * foffset, 'y') # hold(True) @@ -1467,7 +1467,7 @@ def c_odds(c): semilogy(_x, c_odds(dat.corrspec[:, 5]), 'c', label=r'$\max (c + 1) / (c - 1)$') text(_x[-1], c_odds(np.asarray([dat.corrspec[-1, 2]])), r'$\max (c + 1) / (c - 1)$') text(_x[-1], c_odds(np.asarray([dat.corrspec[-1, 5]])), r'$-{\min}^{-1} (c + 1)\dots$') - + # title('abs(f) (blue), f-min(f) (cyan), Sigma (green), Axis Ratio (red)') # title(r'blue:$\mathrm{abs}(f)$, cyan:$f - \min(f)$, green:$\sigma$, red:axis ratio', @@ -1627,7 +1627,7 @@ def apply_xopt(dat_x, x_opt_idx): title('Object Variables (' + (remark + ', ' if remark is not None else '') + str(dat_x.shape[1] - 5) + '-D, popsize=' + - ((str(int(popsi)) if popsi is not None else 'NA') + + ((str(int(popsi)) if popsi is not None else 'NA') + ('|' + str(np.round(mpopsi, 1)) if mpopsi != popsi else '') ) + ')') @@ -2212,7 +2212,7 @@ def plot(self, plot=None, clear=True, transformations=None): try: from matplotlib import pyplot as plt except ImportError: pass - if not callable(plot): # this may allow to use this method as callback + if not callable(plot): # this may allow to use this method as callback from matplotlib.pyplot import plot self.load() n = len(self.data) # number of data rows @@ -2268,7 +2268,7 @@ def smartlogygrid(**kwargs): return if lims[1] / lims[0] < 1e5: plt.grid(True, which='minor') - _fix_lower_xlim_and_clipping() + _fix_lower_xlim_and_clipping() def custom_default_matplotlib(): """reduce wasted margin area from 19.0% to 4.0% and make both grids default and diff --git a/cma/optimization_tools.py b/cma/optimization_tools.py index ca16136..0fae7f9 100644 --- a/cma/optimization_tools.py +++ b/cma/optimization_tools.py @@ -189,9 +189,9 @@ class EvalParallel2(object): """A class and context manager for parallel evaluations. This class is based on the ``Pool`` class of the `multiprocessing` module. - + The interface in v2 changed, such that the fitness function can be - given once in the constructor. Hence the number of processes has + given once in the constructor. Hence the number of processes has become the second (optional) argument of `__init__` and the function has become the second and optional argument of `__call__`. @@ -226,7 +226,7 @@ class EvalParallel2(object): >>> # class usage, don't forget to call terminate >>> ep = EvalParallel2(cma.fitness_functions.elli, 4) >>> ep([[1,2], [3,4], [4, 5]]) # doctest:+ELLIPSIS - [4000000.944... + [np.float64(4000000.944... >>> ep.terminate() ... >>> # use with `with` statement (context manager) @@ -793,17 +793,17 @@ def indices(self, fit): if choice == 1: # take n_first first and reev - n_first best of the remaining n_first = lam_reev - lam_reev // 2 - sort_idx = np.argsort(np.array(fit, copy=False)[n_first:]) + n_first - return np.array(list(range(0, n_first)) + - list(sort_idx[0:lam_reev - n_first]), copy=False) + sort_idx = np.argsort(np.asarray(fit)[n_first:]) + n_first + return np.asarray(list(range(0, n_first)) + + list(sort_idx[0:lam_reev - n_first])) elif choice == 2: - idx_sorted = np.argsort(np.array(fit, copy=False)) + idx_sorted = np.argsort(np.asarray(fit)) # take lam_reev equally spaced, starting with best linsp = np.linspace(0, len(fit) - len(fit) / lam_reev, lam_reev) return idx_sorted[[int(i) for i in linsp]] # take the ``lam_reeval`` best from the first ``2 * lam_reeval + 2`` values. elif choice == 3: - return np.argsort(np.array(fit, copy=False)[:2 * (lam_reev + 1)])[:lam_reev] + return np.argsort(np.asarray(fit)[:2 * (lam_reev + 1)])[:lam_reev] else: raise ValueError('unrecognized choice value %d for noise reev' % choice) @@ -999,4 +999,3 @@ def load(self, name=None): s = pickle.load(open(name + '.pkl', 'rb')) self.res = s.res # disregard the class return self - diff --git a/cma/sampler.py b/cma/sampler.py index 1c68062..3549df2 100644 --- a/cma/sampler.py +++ b/cma/sampler.py @@ -883,4 +883,3 @@ def norm(self, x): `d` is the Euclidean distance, because C = I. """ return sum(np.asarray(x)**2 / self.C)**0.5 - diff --git a/cma/sigma_adaptation.py b/cma/sigma_adaptation.py index f45e3ca..8bcce5c 100644 --- a/cma/sigma_adaptation.py +++ b/cma/sigma_adaptation.py @@ -150,15 +150,15 @@ def initialize(self, es): if es.opts['CSA_clip_length_value'] is not None: try: if len(es.opts['CSA_clip_length_value']) == 0: - es.opts['CSA_clip_length_value'] = [-np.Inf, np.Inf] + es.opts['CSA_clip_length_value'] = [-np.inf, np.inf] elif len(es.opts['CSA_clip_length_value']) == 1: - es.opts['CSA_clip_length_value'] = [-np.Inf, es.opts['CSA_clip_length_value'][0]] + es.opts['CSA_clip_length_value'] = [-np.inf, es.opts['CSA_clip_length_value'][0]] elif len(es.opts['CSA_clip_length_value']) == 2: es.opts['CSA_clip_length_value'] = np.sort(es.opts['CSA_clip_length_value']) else: raise ValueError('option CSA_clip_length_value should be a number of len(.) in [1,2]') except TypeError: # len(...) failed - es.opts['CSA_clip_length_value'] = [-np.Inf, es.opts['CSA_clip_length_value']] + es.opts['CSA_clip_length_value'] = [-np.inf, es.opts['CSA_clip_length_value']] es.opts['CSA_clip_length_value'] = list(np.sort(es.opts['CSA_clip_length_value'])) if es.opts['CSA_clip_length_value'][0] > 0 or es.opts['CSA_clip_length_value'][1] < 0: raise ValueError('option CSA_clip_length_value must be a single positive or a negative and a positive number') @@ -495,4 +495,3 @@ def check_consistency(self, es): ' (sigma=%f)' % es.sigma, 'check_consistency', 'CMAAdaptSigmaTPA', es.countiter) - diff --git a/cma/transformations.py b/cma/transformations.py index 71426a7..dcd0deb 100644 --- a/cma/transformations.py +++ b/cma/transformations.py @@ -39,7 +39,7 @@ def __call__(self, x): shift = np.random.randn(len(x)) np.random.set_state(rstate) x_opt = self._xopt.setdefault(len(x), self.stddev * shift) - return array(x, copy=False) - x_opt + return np.asarray(x) - x_opt def get(self, dimension): """return shift applied to ``zeros(dimension)`` @@ -64,7 +64,7 @@ class Rotation(object): >>> R = cma.transformations.Rotation() >>> R2 = cma.transformations.Rotation() # another rotation >>> x = np.array((1,2,3)) - >>> list(np.round(R(R(x), inverse=1), 9)) + >>> np.round(R(R(x), inverse=1), 9).tolist() [1.0, 2.0, 3.0] :See also: `Rotated` @@ -80,7 +80,7 @@ def __call__(self, x, inverse=False, **kwargs): """Rotates the input array `x` with a fixed rotation matrix (``self.dicMatrices[len(x)]``) """ - x = np.array(x, copy=False) + x = np.asarray(x) N = x.shape[0] # can be an array or matrix, TODO: accept also a list of arrays? if N not in self.dicMatrices: # create new N-basis once and for all rstate = np.random.get_state() @@ -218,7 +218,7 @@ class BoxConstraintsLinQuadTransformation(BoxConstraintsTransformationBase): In contrast to ``sin(.)``, the transformation is robust to "arbitrary" large values for boundaries, e.g. a lower bound of - ``-1e99`` or upper bound of ``np.Inf`` or bound ``None``. + ``-1e99`` or upper bound of ``np.inf`` or bound ``None``. Examples ======== @@ -257,10 +257,10 @@ class BoxConstraintsLinQuadTransformation(BoxConstraintsTransformationBase): [[1, 2], [1, 11], [1, 11]] >>> tf([1.5, 1.5, 1.5]) [1.5, 1.5, 1.5] - >>> list(np.round(tf([1.52, -2.2, -0.2, 2, 4, 10.4]), 9)) + >>> np.round(tf([1.52, -2.2, -0.2, 2, 4, 10.4]), 9).tolist() [1.52, 4.0, 2.0, 2.0, 4.0, 10.4] >>> res = np.round(tf._au, 2) - >>> assert list(res[:4]) == [ 0.15, 0.6, 0.6, 0.6], list(res[:4]) + >>> assert res[:4].tolist() == [ 0.15, 0.6, 0.6, 0.6], res[:4].tolist() >>> res = [round(x, 2) for x in tf.shift_or_mirror_into_invertible_domain([1.52, -12.2, -0.2, 2, 4, 10.4])] >>> assert res == [1.52, 9.2, 2.0, 2.0, 4.0, 10.4], res >>> tmp = tf([1]) # call with lower dimension @@ -271,7 +271,7 @@ class BoxConstraintsLinQuadTransformation(BoxConstraintsTransformationBase): [[1, 2], [1, 11], [1, 11]] >>> tf([1.5, 1.5, 1.5]) [1.5, 1.5, 1.5] - >>> list(np.round(tf([1.52, -2.2, -0.2, 2, 4, 10.4]), 9)) + >>> np.round(tf([1.52, -2.2, -0.2, 2, 4, 10.4]), 9).tolist() [1.52, 4.1, 2.1, 2.0, 4.0, 10.4] >>> res = np.round(tf._au, 2) >>> assert list(res[:4]) == [ 0.1, 0.55, 0.55, 0.55], list(res[:4]) @@ -296,14 +296,14 @@ def initialize(self, length=None): if length is None: length = len(self.bounds) max_i = min((len(self.bounds) - 1, length - 1)) - self._lb = array([self.bounds[min((i, max_i))][0] + self._lb = np.asarray([self.bounds[min((i, max_i))][0] if self.bounds[min((i, max_i))][0] is not None - else -np.Inf - for i in range(length)], copy=False) - self._ub = array([self.bounds[min((i, max_i))][1] + else -np.inf + for i in range(length)]) + self._ub = np.asarray([self.bounds[min((i, max_i))][1] if self.bounds[min((i, max_i))][1] is not None - else np.Inf - for i in range(length)], copy=False) + else np.inf + for i in range(length)]) lb = self._lb ub = self._ub if any(lb >= ub): @@ -311,10 +311,10 @@ def initialize(self, length=None): ' were not at idx={} where lb={}, ub={}' .format(np.where(lb >= ub)[0], lb, ub)) # define added values for lower and upper bound - self._al = array([min([(ub[i] - lb[i]) / 2, linquad_margin_width(lb[i])]) - if isfinite(lb[i]) else 1 for i in rglen(lb)], copy=False) - self._au = array([min([(ub[i] - lb[i]) / 2, linquad_margin_width(ub[i])]) - if isfinite(ub[i]) else 1 for i in rglen(ub)], copy=False) + self._al = np.asarray([min([(ub[i] - lb[i]) / 2, linquad_margin_width(lb[i])]) + if isfinite(lb[i]) else 1 for i in rglen(lb)]) + self._au = np.asarray([min([(ub[i] - lb[i]) / 2, linquad_margin_width(ub[i])]) + if isfinite(ub[i]) else 1 for i in rglen(ub)]) def __call__(self, solution_genotype, copy=True): # about four times faster version of array([self._transform_i(x, i) for i, x in enumerate(solution_genotype)]) @@ -737,7 +737,7 @@ def parameters(self, mueff, c1_factor=1, cmu_factor=1): self._parameters[input_parameters] = {'c1': c1, 'cmu': cmu, 'cc': cc} def check_values(d, input_parameters=None): """`d` is the parameters dictionary""" - if not (0 <= d['c1'] < 0.75 and 0 <= d['cmu'] <= 1 and + if not (0 <= d['c1'] < 0.75 and 0 <= d['cmu'] <= 1 and d['c1'] <= d['cc'] <= 1): raise ValueError("On input {},\n" "the values {}\n" @@ -746,7 +746,7 @@ def check_values(d, input_parameters=None): " c1 <= cc <= 1`".format(str(input_parameters), str(d))) check_values(self._parameters[input_parameters], input_parameters) return self._parameters[input_parameters] - + def _init_(self, int_or_vector): """init scaling (only) when not yet done""" if not self.is_identity or not np.size(self.scaling) == 1: @@ -805,7 +805,7 @@ def update(self, vectors, weights, ignore_indices=None): # z2=0, w=-1, d=log(2) => exp(d w (0 - 1)) = 2 = 1 + w (0 - 1) # z2=2, w=1, d=log(2) => exp(d w (2 - 1)) = 2 = 1 + w (2 - 1) - + if 1 < 3: # bound increment to observed value if 1 < 3: idx = facs > 1 @@ -1029,7 +1029,7 @@ def pheno(self, x, into_bounds=None, copy=True, input_type = type(x) if into_bounds is None: into_bounds = (lambda x, copy=False: - x if not copy else array(x, copy=copy)) + x if not copy else np.asarray(x, copy=copy)) if self.isidentity: y = into_bounds(x) # was into_bounds(x, False) before (bug before v0.96.22) else: @@ -1039,7 +1039,7 @@ def pheno(self, x, into_bounds=None, copy=True, y = list(x) # is a copy for i in sorted(self.fixed_values.keys()): y.insert(i, self.fixed_values[i]) - y = array(y, copy=False) + y = np.asarray(y) copy = False if not is_one(self.scales): # just for efficiency @@ -1049,7 +1049,7 @@ def pheno(self, x, into_bounds=None, copy=True, y += self.typical_x if self.tf_pheno is not None: - y = array(self.tf_pheno(y), copy=False) + y = np.asarray(self.tf_pheno(y)) y = into_bounds(y, copy) # copy is False @@ -1058,7 +1058,7 @@ def pheno(self, x, into_bounds=None, copy=True, y[i] = k if input_type is np.ndarray: - y = array(y, copy=False) + y = np.asarray(y) if archive is not None: archive.insert(y, geno=x, iteration=iteration) return y @@ -1140,6 +1140,5 @@ def repair_and_flag_change(self, repair, x, copy): # repair injected solutions x = repair_and_flag_change(self, repair, x, copy) if input_type is np.ndarray: - x = array(x, copy=False) + x = np.asarray(x) return x - diff --git a/cma/utilities/math.py b/cma/utilities/math.py index 1c40308..7e95afb 100644 --- a/cma/utilities/math.py +++ b/cma/utilities/math.py @@ -106,7 +106,7 @@ def Hessian(f, x0, eps=1e-6): def geometric_sd(vals, **kwargs): """return geometric standard deviation of `vals`. - + The gsd is invariant under linear scaling and independent of the choice of the log-exp base. @@ -493,7 +493,7 @@ def vequals_approximately(a, b, eps=1e-12): if len(idx): a[idx], b[idx] = -1 * a[idx], -1 * b[idx] return (np.all(a - eps < b) and np.all(b < a + eps) - ) or (np.all((1 - eps) * a < b) and np.all(b < (1 + eps) * a)) + ).item() or (np.all((1 - eps) * a < b) and np.all(b < (1 + eps) * a)).item() @staticmethod def expms(A, eig=np.linalg.eigh): """matrix exponential for a symmetric matrix""" diff --git a/cma/utilities/utils.py b/cma/utilities/utils.py index ff06daf..d9e0001 100644 --- a/cma/utilities/utils.py +++ b/cma/utilities/utils.py @@ -540,9 +540,9 @@ def key(self, x): # Do not hash key again return x elif isinstance(x, np.ndarray): - try: + try: return hash(x.tobytes()) - except AttributeError: + except AttributeError: if x.size < 1e4: # based on timing results return hash(tuple(x)) else: @@ -550,7 +550,7 @@ def key(self, x): else: try: return hash(x) - except TypeError: + except TypeError: # Data type must be immutable, transform into tuple first return hash(tuple(x)) def __contains__(self, key): @@ -998,7 +998,7 @@ def __call__(self, *args, **kwargs): class ShowInFolder: # was ShowInline """callable instance to save and show figures from `matplotlib`. - + Saves figures to a folder ``'figs-...'`` with incremental filenames allowing to conveniently view and compare these figures. @@ -1010,7 +1010,7 @@ class ShowInFolder: # was ShowInline >> import cma, matplotlib.pyplot as plt >> show = cma.utilities.utils.ShowInline('01') # use './figs-01' folder - >> + >> >> plt.plot([1,2,3]) >> show() # save current plot with a unique name in figs-01 folder >> # and switch focus to the folder @@ -1087,4 +1087,3 @@ def show_in_notebook(self, width=None, **kwargs_show): def open_path(self): os.system('open ' + self.path) - diff --git a/cma_signals.in b/cma_signals.in index 41ee28d..7263ca6 100644 --- a/cma_signals.in +++ b/cma_signals.in @@ -1,17 +1,17 @@ # This file can change versatile options while running CMA-ES. # -# To take effect, the option 'signals_filename' of the `cma.CMAOptions` -# argument to `cma.fmin` or `cma.CMAEvolutionStrategy` must be set to +# To take effect, the option 'signals_filename' of the `cma.CMAOptions` +# argument to `cma.fmin` or `cma.CMAEvolutionStrategy` must be set to # 'cma_signals.in', i.e. the name of this file (set to `None` or '' if -# no file should be checked). -# -# To be effective, this file must contain valid Python code, namely +# no file should be checked). +# +# To be effective, this file must contain valid Python code, namely # a single Python expression, namely a `dict`. The opening brace # must be in the first column. Keys are strings, values of filenames # must be strings. # # Outcomment the desired option to change (the options list could be outdated): -# +# { # 'CMA_elitist': 'False #v or "initial" or True, elitism likely impairs global search performance', # 'CMA_sample_on_sphere_surface': 'False #v all mutation vectors have the same length, currently (with new_sampling) not in effect', # 'CSA_dampfac': '1 #v positive multiplier for step-size damping, 0.3 is close to optimal on the sphere', @@ -19,7 +19,7 @@ # 'CSA_clip_length_value': 'None #v untested, [0, 0] means disregarding length completely', # 'CSA_squared': 'False #v use squared length for sigma-adaptation ', # 'ftarget': '-inf #v target function value, minimization', - # 'is_feasible': 'is_feasible #v a function that computes feasibility, by default lambda x, f: f not in (None, np.NaN)', + # 'is_feasible': 'is_feasible #v a function that computes feasibility, by default lambda x, f: f not in (None, np.nan)', # 'maxfevals': 'inf #v maximum number of function evaluations', # 'maxiter': '100 + 50 * (N+3)**2 // popsize**0.5 #v maximum number of iterations', # 'mean_shift_line_samples': 'False #v sample two new solutions colinear to previous mean shift', @@ -43,4 +43,3 @@ # 'verb_plot': '0 #v in fmin(): plot() is called every verb_plot iteration', # 'verb_time': 'True #v output timings on console', } - diff --git a/notebooks/notebook-usecases-basics.ipynb b/notebooks/notebook-usecases-basics.ipynb index 5cee160..f449679 100644 --- a/notebooks/notebook-usecases-basics.ipynb +++ b/notebooks/notebook-usecases-basics.ipynb @@ -422,7 +422,7 @@ "data": { "text/plain": [ "{'AdaptSigma': 'True # or False or any CMAAdaptSigmaBase class e.g. CMAAdaptSigmaTPA, CMAAdaptSigmaCSA',\n", - " 'CMA_dampsvec_fac': 'np.Inf # tentative and subject to changes, 0.5 would be a \"default\" damping for sigma vector update',\n", + " 'CMA_dampsvec_fac': 'np.inf # tentative and subject to changes, 0.5 would be a \"default\" damping for sigma vector update',\n", " 'CMA_dampsvec_fade': '0.1 # tentative fading out parameter for sigma vector update',\n", " 'CMA_stds': 'None # multipliers for sigma0 in each coordinate, not represented in C, better use `cma.ScaleCoordinates` instead',\n", " 'CSA_squared': 'False #v use squared length for sigma-adaptation ',\n", From 3ea4cc64e999b0352934e08e61400e9ebd41b8c8 Mon Sep 17 00:00:00 2001 From: Sait Cakmak Date: Wed, 10 Apr 2024 19:31:40 -0700 Subject: [PATCH 2/4] make _CMAParameters test compatible with both versions --- cma/evolution_strategy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cma/evolution_strategy.py b/cma/evolution_strategy.py index 9d5a892..3ad64b0 100644 --- a/cma/evolution_strategy.py +++ b/cma/evolution_strategy.py @@ -4113,12 +4113,12 @@ class _CMAParameters(object): {'CMA_on': True, 'N': 20, 'c1': 0.00437235..., - 'c1_sep': np.float64(0.0343279..., + 'c1_sep': ...0.0343279..., 'cc': 0.171767..., 'cc_sep': 0.252594..., 'cmean': array(1..., 'cmu': 0.00921656..., - 'cmu_sep': np.float64(0.0565385..., + 'cmu_sep': ...0.0565385..., 'lam_mirr': 0, 'mu': 6, 'popsize': 12, From 41abbe8f535d8e77d66396f237d2d35184ce6919 Mon Sep 17 00:00:00 2001 From: Sait Cakmak Date: Wed, 10 Apr 2024 19:52:39 -0700 Subject: [PATCH 3/4] one more compatibility fix --- cma/optimization_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cma/optimization_tools.py b/cma/optimization_tools.py index 0fae7f9..d69cffe 100644 --- a/cma/optimization_tools.py +++ b/cma/optimization_tools.py @@ -225,8 +225,8 @@ class EvalParallel2(object): ... res = eval_all([[1,2], [3,4]]) >>> # class usage, don't forget to call terminate >>> ep = EvalParallel2(cma.fitness_functions.elli, 4) - >>> ep([[1,2], [3,4], [4, 5]]) # doctest:+ELLIPSIS - [np.float64(4000000.944... + >>> [v.item() if isinstance(v, np.float64) else v for v in ep([[1,2], [3,4], [4, 5]])] # doctest:+ELLIPSIS + [4000000.944... >>> ep.terminate() ... >>> # use with `with` statement (context manager) From 4a54ff658220f917ac3638d739cdfda644180f44 Mon Sep 17 00:00:00 2001 From: Sait Cakmak Date: Thu, 11 Apr 2024 09:31:37 -0700 Subject: [PATCH 4/4] fix bug from diff review --- cma/transformations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cma/transformations.py b/cma/transformations.py index dcd0deb..049e695 100644 --- a/cma/transformations.py +++ b/cma/transformations.py @@ -1029,7 +1029,7 @@ def pheno(self, x, into_bounds=None, copy=True, input_type = type(x) if into_bounds is None: into_bounds = (lambda x, copy=False: - x if not copy else np.asarray(x, copy=copy)) + x if not copy else array(x, copy=True)) if self.isidentity: y = into_bounds(x) # was into_bounds(x, False) before (bug before v0.96.22) else: