From 8b384fd00087651e00eb70fe33414ea89f07186b Mon Sep 17 00:00:00 2001 From: Ilias Bilionis Date: Mon, 10 Aug 2015 16:36:49 -0400 Subject: [PATCH] Handpicked James Hensman's code that ensures that fixes the PDF of transformed variables. Fixed minor plotting bug. --- GPy/core/parameterization/parameter_core.py | 133 ++++++++++++------- GPy/core/parameterization/transformations.py | 123 +++++++++++------ 2 files changed, 169 insertions(+), 87 deletions(-) diff --git a/GPy/core/parameterization/parameter_core.py b/GPy/core/parameterization/parameter_core.py index a0d0981d..48c05a8d 100644 --- a/GPy/core/parameterization/parameter_core.py +++ b/GPy/core/parameterization/parameter_core.py @@ -13,11 +13,12 @@ Observable Pattern for patameterization """ -from transformations import Transformation,Logexp, NegativeLogexp, Logistic, __fixed__, FIXED, UNFIXED +from .transformations import Transformation,Logexp, NegativeLogexp, Logistic, __fixed__, FIXED, UNFIXED import numpy as np import re import logging -from updateable import Updateable +from .updateable import Updateable +from functools import reduce class HierarchyError(Exception): """ @@ -36,7 +37,7 @@ def adjust_name_for_printing(name): name = name.replace("/", "_l_").replace("@", '_at_') name = name.replace("(", "_of_").replace(")", "") if re.match(r'^[a-zA-Z_][a-zA-Z0-9-_]*$', name) is None: - raise NameError, "name {} converted to {} cannot be further converted to valid python variable name!".format(name2, name) + raise NameError("name {} converted to {} cannot be further converted to valid python variable name!".format(name2, name)) return name return '' @@ -65,13 +66,13 @@ class Parentable(object): Gets called, when the parent changed, so we can adjust our inner attributes according to the new parent. """ - raise NotImplementedError, "shouldnt happen, Parentable objects need to be able to change their parent" + raise NotImplementedError("shouldnt happen, Parentable objects need to be able to change their parent") def _disconnect_parent(self, *args, **kw): """ Disconnect this object from its parent """ - raise NotImplementedError, "Abstract superclass" + raise NotImplementedError("Abstract superclass") @property def _highest_parent_(self): @@ -109,7 +110,10 @@ class Pickleable(object): it properly. :param protocol: pickling protocol to use, python-pickle for details. """ - import cPickle as pickle + try: #Py2 + import cPickle as pickle + except ImportError: #Py3 + import pickle if isinstance(f, str): with open(f, 'wb') as f: pickle.dump(self, f, protocol) @@ -138,9 +142,9 @@ class Pickleable(object): which = self which.traverse_parents(parents.append) # collect parents for p in parents: - if not memo.has_key(id(p)):memo[id(p)] = None # set all parents to be None, so they will not be copied - if not memo.has_key(id(self.gradient)):memo[id(self.gradient)] = None # reset the gradient - if not memo.has_key(id(self._fixes_)):memo[id(self._fixes_)] = None # fixes have to be reset, as this is now highest parent + if not id(p) in memo :memo[id(p)] = None # set all parents to be None, so they will not be copied + if not id(self.gradient) in memo:memo[id(self.gradient)] = None # reset the gradient + if not id(self._fixes_) in memo :memo[id(self._fixes_)] = None # fixes have to be reset, as this is now highest parent copy = copy.deepcopy(self, memo) # and start the copy copy._parent_index_ = None copy._trigger_params_changed() @@ -163,14 +167,16 @@ class Pickleable(object): '_Cacher_wrap__cachers', # never pickle cachers ] dc = dict() - for k,v in self.__dict__.iteritems(): + #py3 fix + #for k,v in self.__dict__.iteritems(): + for k,v in self.__dict__.items(): if k not in ignore_list: dc[k] = v return dc def __setstate__(self, state): self.__dict__.update(state) - from lists_and_dicts import ObserverList + from .lists_and_dicts import ObserverList self.observers = ObserverList() self._setup_observers() self._optimizer_copy_transformed = False @@ -214,7 +220,7 @@ class Gradcheckable(Pickleable, Parentable): Perform the checkgrad on the model. TODO: this can be done more efficiently, when doing it inside here """ - raise HierarchyError, "This parameter is not in a model with a likelihood, and, therefore, cannot be gradient checked!" + raise HierarchyError("This parameter is not in a model with a likelihood, and, therefore, cannot be gradient checked!") class Nameable(Gradcheckable): """ @@ -268,7 +274,7 @@ class Indexable(Nameable, Updateable): def __init__(self, name, default_constraint=None, *a, **kw): super(Indexable, self).__init__(name=name, *a, **kw) self._default_constraint_ = default_constraint - from index_operations import ParameterIndexOperations + from .index_operations import ParameterIndexOperations self.constraints = ParameterIndexOperations() self.priors = ParameterIndexOperations() if self._default_constraint_ is not None: @@ -310,7 +316,7 @@ class Indexable(Nameable, Updateable): that is an int array, containing the indexes for the flattened param inside this parameterized logic. """ - from param import ParamConcatenation + from .param import ParamConcatenation if isinstance(param, ParamConcatenation): return np.hstack((self._raveled_index_for(p) for p in param.params)) return param._raveled_index() + self._offset_for(param) @@ -407,7 +413,7 @@ class Indexable(Nameable, Updateable): repriorized = self.unset_priors() self._add_to_index_operations(self.priors, repriorized, prior, warning) - from domains import _REAL, _POSITIVE, _NEGATIVE + from .domains import _REAL, _POSITIVE, _NEGATIVE if prior.domain is _POSITIVE: self.constrain_positive(warning) elif prior.domain is _NEGATIVE: @@ -424,19 +430,38 @@ class Indexable(Nameable, Updateable): def log_prior(self): """evaluate the prior""" - if self.priors.size > 0: - x = self.param_array - return reduce(lambda a, b: a + b, (p.lnpdf(x[ind]).sum() for p, ind in self.priors.iteritems()), 0) - return 0. + if self.priors.size == 0: + return 0. + x = self.param_array + #evaluate the prior log densities + log_p = reduce(lambda a, b: a + b, (p.lnpdf(x[ind]).sum() for p, ind in self.priors.items()), 0) + + #account for the transformation by evaluating the log Jacobian (where things are transformed) + log_j = 0. + priored_indexes = np.hstack([i for p, i in self.priors.items()]) + for c,j in self.constraints.items(): + if not isinstance(c, Transformation):continue + for jj in j: + if jj in priored_indexes: + log_j += c.log_jacobian(x[jj]) + return log_p + log_j def _log_prior_gradients(self): """evaluate the gradients of the priors""" - if self.priors.size > 0: - x = self.param_array - ret = np.zeros(x.size) - [np.put(ret, ind, p.lnpdf_grad(x[ind])) for p, ind in self.priors.iteritems()] - return ret - return 0. + if self.priors.size == 0: + return 0. + x = self.param_array + ret = np.zeros(x.size) + #compute derivate of prior density + [np.put(ret, ind, p.lnpdf_grad(x[ind])) for p, ind in self.priors.items()] + #add in jacobian derivatives if transformed + priored_indexes = np.hstack([i for p, i in self.priors.items()]) + for c,j in self.constraints.items(): + if not isinstance(c, Transformation):continue + for jj in j: + if jj in priored_indexes: + ret[jj] += c.log_jacobian_grad(x[jj]) + return ret #=========================================================================== # Tie parameters together @@ -471,7 +496,7 @@ class Indexable(Nameable, Updateable): self.param_array[...] = transform.initialize(self.param_array) reconstrained = self.unconstrain() added = self._add_to_index_operations(self.constraints, reconstrained, transform, warning) - self.notify_observers(self, None if trigger_parent else -np.inf) + self.trigger_update(trigger_parent) return added def unconstrain(self, *transforms): @@ -536,7 +561,7 @@ class Indexable(Nameable, Updateable): update the constraints and priors view, so that constraining is automized for the parent. """ - from index_operations import ParameterIndexOperationsView + from .index_operations import ParameterIndexOperationsView #if getattr(self, "_in_init_"): #import ipdb;ipdb.set_trace() #self.constraints.update(param.constraints, start) @@ -558,7 +583,7 @@ class Indexable(Nameable, Updateable): """ if warning and reconstrained.size > 0: # TODO: figure out which parameters have changed and only print those - print "WARNING: reconstraining parameters {}".format(self.hierarchy_name() or self.name) + print("WARNING: reconstraining parameters {}".format(self.hierarchy_name() or self.name)) index = self._raveled_index() which.add(what, index) return index @@ -571,7 +596,7 @@ class Indexable(Nameable, Updateable): if len(transforms) == 0: transforms = which.properties() removed = np.empty((0,), dtype=int) - for t in transforms: + for t in list(transforms): unconstrained = which.remove(t, self._raveled_index()) removed = np.union1d(removed, unconstrained) if t is __fixed__: @@ -612,7 +637,9 @@ class OptimizationHandlable(Indexable): if not self._optimizer_copy_transformed: self._optimizer_copy_.flat = self.param_array.flat - [np.put(self._optimizer_copy_, ind, c.finv(self.param_array[ind])) for c, ind in self.constraints.iteritems() if c != __fixed__] + #py3 fix + #[np.put(self._optimizer_copy_, ind, c.finv(self.param_array[ind])) for c, ind in self.constraints.iteritems() if c != __fixed__] + [np.put(self._optimizer_copy_, ind, c.finv(self.param_array[ind])) for c, ind in self.constraints.items() if c != __fixed__] if self.has_parent() and (self.constraints[__fixed__].size != 0 or self._has_ties()): fixes = np.ones(self.size).astype(bool) fixes[self.constraints[__fixed__]] = FIXED @@ -641,21 +668,25 @@ class OptimizationHandlable(Indexable): if f is None: self.param_array.flat = p [np.put(self.param_array, ind, c.f(self.param_array.flat[ind])) - for c, ind in self.constraints.iteritems() if c != __fixed__] + #py3 fix + #for c, ind in self.constraints.iteritems() if c != __fixed__] + for c, ind in self.constraints.items() if c != __fixed__] else: self.param_array.flat[f] = p [np.put(self.param_array, ind[f[ind]], c.f(self.param_array.flat[ind[f[ind]]])) - for c, ind in self.constraints.iteritems() if c != __fixed__] + #py3 fix + #for c, ind in self.constraints.iteritems() if c != __fixed__] + for c, ind in self.constraints.items() if c != __fixed__] #self._highest_parent_.tie.propagate_val() self._optimizer_copy_transformed = False self.trigger_update() def _get_params_transformed(self): - raise DeprecationWarning, "_get|set_params{_optimizer_copy_transformed} is deprecated, use self.optimizer array insetad!" + raise DeprecationWarning("_get|set_params{_optimizer_copy_transformed} is deprecated, use self.optimizer array insetad!") # def _set_params_transformed(self, p): - raise DeprecationWarning, "_get|set_params{_optimizer_copy_transformed} is deprecated, use self.optimizer array insetad!" + raise DeprecationWarning("_get|set_params{_optimizer_copy_transformed} is deprecated, use self.optimizer array insetad!") def _trigger_params_changed(self, trigger_parent=True): """ @@ -680,19 +711,24 @@ class OptimizationHandlable(Indexable): constraint to it. """ self._highest_parent_.tie.collate_gradient() - [np.put(g, i, c.gradfactor(self.param_array[i], g[i])) for c, i in self.constraints.iteritems() if c != __fixed__] + #py3 fix + #[np.put(g, i, c.gradfactor(self.param_array[i], g[i])) for c, i in self.constraints.iteritems() if c != __fixed__] + [np.put(g, i, c.gradfactor(self.param_array[i], g[i])) for c, i in self.constraints.items() if c != __fixed__] if self._has_fixes(): return g[self._fixes_] return g - def _log_det_jacobian(self): + def _transform_gradients_non_natural(self, g): """ - Return the logarithm of the Jacobian needed for a proper change of - variables. + Transform the gradients by multiplying the gradient factor for each + constraint to it. """ - J = np.ones(self.param_array.shape) - [np.put(J, i, c.jacobianfactor(self.param_array[i])) - for c, i in self.constraints.iteritems() if c != __fixed__] - return np.log(J).sum() + self._highest_parent_.tie.collate_gradient() + #py3 fix + #[np.put(g, i, c.gradfactor_non_natural(self.param_array[i], g[i])) for c, i in self.constraints.iteritems() if c != __fixed__] + [np.put(g, i, c.gradfactor_non_natural(self.param_array[i], g[i])) for c, i in self.constraints.items() if c != __fixed__] + if self._has_fixes(): return g[self._fixes_] + return g + @property def num_params(self): @@ -700,7 +736,7 @@ class OptimizationHandlable(Indexable): Return the number of parameters of this parameter_handle. Param objects will always return 0. """ - raise NotImplemented, "Abstract, please implement in respective classes" + raise NotImplemented("Abstract, please implement in respective classes") def parameter_names(self, add_self=False, adjust_for_printing=False, recursive=True): """ @@ -749,7 +785,9 @@ class OptimizationHandlable(Indexable): self.optimizer_array = x # makes sure all of the tied parameters get the same init (since there's only one prior object...) # now draw from prior where possible x = self.param_array.copy() - [np.put(x, ind, p.rvs(ind.size)) for p, ind in self.priors.iteritems() if not p is None] + #Py3 fix + #[np.put(x, ind, p.rvs(ind.size)) for p, ind in self.priors.iteritems() if not p is None] + [np.put(x, ind, p.rvs(ind.size)) for p, ind in self.priors.items() if not p is None] unfixlist = np.ones((self.size,),dtype=np.bool) unfixlist[self.constraints[__fixed__]] = False self.param_array.flat[unfixlist] = x.view(np.ndarray).ravel()[unfixlist] @@ -808,7 +846,7 @@ class Parameterizable(OptimizationHandlable): A parameterisable class. This class provides the parameters list (ArrayList) and standard parameter handling, - such as {add|remove}_parameter(), traverse hierarchy and param_array, gradient_array + such as {link|unlink}_parameter(), traverse hierarchy and param_array, gradient_array and the empty parameters_changed(). This class is abstract and should not be instantiated. @@ -946,7 +984,7 @@ class Parameterizable(OptimizationHandlable): self._add_parameter_name(param, ignore_added_names) # and makes sure to not delete programmatically added parameters for other in self.parameters[::-1]: - if other is not param and other.name.startswith(param.name): + if other is not param and other.name == param.name: warn_and_retry(param, _name_digit.match(other.name)) return if pname not in dir(self): @@ -1041,6 +1079,9 @@ class Parameterizable(OptimizationHandlable): p = param_to_array(p) d = f.create_dataset(n,p.shape,dtype=p.dtype) d[:] = p + if hasattr(self, 'param_array'): + d = f.create_dataset('param_array',self.param_array.shape, dtype=self.param_array.dtype) + d[:] = self.param_array f.close() except: raise 'Fails to write the parameters into a HDF5 file!' diff --git a/GPy/core/parameterization/transformations.py b/GPy/core/parameterization/transformations.py index bac98873..01a1f44b 100644 --- a/GPy/core/parameterization/transformations.py +++ b/GPy/core/parameterization/transformations.py @@ -3,7 +3,7 @@ import numpy as np -from domains import _POSITIVE,_NEGATIVE, _BOUNDED +from .domains import _POSITIVE,_NEGATIVE, _BOUNDED import weakref import sys @@ -31,13 +31,16 @@ class Transformation(object): raise NotImplementedError def finv(self, model_param): raise NotImplementedError - def jacobianfactor(self, model_param): + def log_jacobian(self, model_param): """ - Return log|det J| where J is the Jacobian of the inverse of the - transformation. + compute the log of the jacobian of f, evaluated at f(x)= model_param """ - return np.abs([self.gradfactor(np.array([theta]), np.ones((1,)))[0] - for theta in model_param]) + raise NotImplementedError + def log_jacobian_grad(self, model_param): + """ + compute the drivative of the log of the jacobian of f, evaluated at f(x)= model_param + """ + raise NotImplementedError def gradfactor(self, model_param, dL_dmodel_param): """ df(opt_param)_dopt_param evaluated at self.f(opt_param)=model_param, times the gradient dL_dmodel_param, @@ -49,6 +52,8 @@ class Transformation(object): \frac{\frac{\partial L}{\partial f}\left(\left.\partial f(x)}{\partial x}\right|_{x=f^{-1}(f)\right)} """ raise NotImplementedError + def gradfactor_non_natural(self, model_param, dL_dmodel_param): + return self.gradfactor(model_param, dL_dmodel_param) def initialize(self, f): """ produce a sensible initial value for f(x)""" raise NotImplementedError @@ -57,12 +62,10 @@ class Transformation(object): import matplotlib.pyplot as plt from ...plotting.matplot_dep import base_plots x = np.linspace(-8,8) - base_plots.meanplot(x, self.f(x), ax=axes, *args, **kw) + base_plots.meanplot(x, self.f(x),axes=axes*args,**kw) axes = plt.gca() axes.set_xlabel(xlabel) axes.set_ylabel(ylabel) - return axes - def __str__(self): raise NotImplementedError def __repr__(self): @@ -74,20 +77,46 @@ class Logexp(Transformation): return np.where(x>_lim_val, x, np.log1p(np.exp(np.clip(x, -_lim_val, _lim_val)))) + epsilon #raises overflow warning: return np.where(x>_lim_val, x, np.log(1. + np.exp(x))) def finv(self, f): - return np.where(f>_lim_val, f, np.log(np.exp(f+epsilon) - 1.)) + return np.where(f>_lim_val, f, np.log(np.exp(f+1e-20) - 1.)) def gradfactor(self, f, df): return np.einsum('i,i->i', df, np.where(f>_lim_val, 1., 1. - np.exp(-f))) def initialize(self, f): if np.any(f < 0.): - print "Warning: changing parameters to satisfy constraints" + print("Warning: changing parameters to satisfy constraints") return np.abs(f) + def log_jacobian(self, model_param): + return np.where(model_param>_lim_val, model_param, np.log(np.exp(model_param+1e-20) - 1.)) - model_param + def log_jacobian_grad(self, model_param): + return 1./(np.exp(model_param)-1.) + def __str__(self): + return '+ve' + +class Exponent(Transformation): + domain = _POSITIVE + def f(self, x): + return np.where(x<_lim_val, np.where(x>-_lim_val, np.exp(x), np.exp(-_lim_val)), np.exp(_lim_val)) + def finv(self, x): + return np.log(x) + def gradfactor(self, f, df): + return np.einsum('i,i->i', df, f) + def initialize(self, f): + if np.any(f < 0.): + print("Warning: changing parameters to satisfy constraints") + return np.abs(f) + def log_jacobian(self, model_param): + return np.log(model_param) + def log_jacobian_grad(self, model_param): + return 1./model_param def __str__(self): return '+ve' + class NormalTheta(Transformation): + "Do not use, not officially supported!" _instances = [] - def __new__(cls, mu_indices, var_indices): + def __new__(cls, mu_indices=None, var_indices=None): + "Do not use, not officially supported!" if cls._instances: cls._instances[:] = [instance for instance in cls._instances if instance()] for instance in cls._instances: @@ -107,6 +136,7 @@ class NormalTheta(Transformation): # that the values are ok # Before: theta[self.var_indices] = np.abs(-.5/theta[self.var_indices]) + #theta[self.var_indices] = np.exp(-.5/theta[self.var_indices]) theta[self.mu_indices] *= theta[self.var_indices] return theta # which is now {mu, var} @@ -115,6 +145,7 @@ class NormalTheta(Transformation): varp = muvar[self.var_indices] muvar[self.mu_indices] /= varp muvar[self.var_indices] = -.5/varp + #muvar[self.var_indices] = -.5/np.log(varp) return muvar # which is now {theta1, theta2} @@ -133,7 +164,7 @@ class NormalTheta(Transformation): def initialize(self, f): if np.any(f[self.var_indices] < 0.): - print "Warning: changing parameters to satisfy constraints" + print("Warning: changing parameters to satisfy constraints") f[self.var_indices] = np.abs(f[self.var_indices]) return f @@ -148,9 +179,10 @@ class NormalTheta(Transformation): self.var_indices = state[1] class NormalNaturalAntti(NormalTheta): + "Do not use, not officially supported!" _instances = [] - _logexp = Logexp() - def __new__(cls, mu_indices, var_indices): + def __new__(cls, mu_indices=None, var_indices=None): + "Do not use, not officially supported!" if cls._instances: cls._instances[:] = [instance for instance in cls._instances if instance()] for instance in cls._instances: @@ -179,7 +211,7 @@ class NormalNaturalAntti(NormalTheta): def initialize(self, f): if np.any(f[self.var_indices] < 0.): - print "Warning: changing parameters to satisfy constraints" + print("Warning: changing parameters to satisfy constraints") f[self.var_indices] = np.abs(f[self.var_indices]) return f @@ -187,8 +219,10 @@ class NormalNaturalAntti(NormalTheta): return "natantti" class NormalEta(Transformation): + "Do not use, not officially supported!" _instances = [] - def __new__(cls, mu_indices, var_indices): + def __new__(cls, mu_indices=None, var_indices=None): + "Do not use, not officially supported!" if cls._instances: cls._instances[:] = [instance for instance in cls._instances if instance()] for instance in cls._instances: @@ -220,7 +254,7 @@ class NormalEta(Transformation): def initialize(self, f): if np.any(f[self.var_indices] < 0.): - print "Warning: changing parameters to satisfy constraints" + print("Warning: changing parameters to satisfy constraints") f[self.var_indices] = np.abs(f[self.var_indices]) return f @@ -228,8 +262,10 @@ class NormalEta(Transformation): return "eta" class NormalNaturalThroughTheta(NormalTheta): + "Do not use, not officially supported!" _instances = [] - def __new__(cls, mu_indices, var_indices): + def __new__(cls, mu_indices=None, var_indices=None): + "Do not use, not officially supported!" if cls._instances: cls._instances[:] = [instance for instance in cls._instances if instance()] for instance in cls._instances: @@ -259,13 +295,28 @@ class NormalNaturalThroughTheta(NormalTheta): #======================================================================= return dmuvar # which is now the gradient multiplicator + def gradfactor_non_natural(self, muvar, dmuvar): + mu = muvar[self.mu_indices] + var = muvar[self.var_indices] + #======================================================================= + # theta gradients + # This works and the gradient checks! + dmuvar[self.mu_indices] *= var + dmuvar[self.var_indices] *= 2*(var)**2 + dmuvar[self.var_indices] += 2*dmuvar[self.mu_indices]*mu + #======================================================================= + + return dmuvar # which is now the gradient multiplicator for {theta1, theta2} + def __str__(self): return "natgrad" class NormalNaturalWhooot(NormalTheta): + "Do not use, not officially supported!" _instances = [] - def __new__(cls, mu_indices, var_indices): + def __new__(cls, mu_indices=None, var_indices=None): + "Do not use, not officially supported!" if cls._instances: cls._instances[:] = [instance for instance in cls._instances if instance()] for instance in cls._instances: @@ -299,8 +350,10 @@ class NormalNaturalWhooot(NormalTheta): return "natgrad" class NormalNaturalThroughEta(NormalEta): + "Do not use, not officially supported!" _instances = [] - def __new__(cls, mu_indices, var_indices): + def __new__(cls, mu_indices=None, var_indices=None): + "Do not use, not officially supported!" if cls._instances: cls._instances[:] = [instance for instance in cls._instances if instance()] for instance in cls._instances: @@ -341,7 +394,7 @@ class LogexpNeg(Transformation): return np.einsum('i,i->i', df, np.where(f>_lim_val, -1, -1 + np.exp(-f))) def initialize(self, f): if np.any(f < 0.): - print "Warning: changing parameters to satisfy constraints" + print("Warning: changing parameters to satisfy constraints") return np.abs(f) def __str__(self): return '+ve' @@ -393,27 +446,11 @@ class LogexpClipped(Logexp): return np.einsum('i,i->i', df, gf) # np.where(f < self.lower, 0, gf) def initialize(self, f): if np.any(f < 0.): - print "Warning: changing parameters to satisfy constraints" + print("Warning: changing parameters to satisfy constraints") return np.abs(f) def __str__(self): return '+ve_c' -class Exponent(Transformation): - # TODO: can't allow this to go to zero, need to set a lower bound. Similar with negative Exponent below. See old MATLAB code. - domain = _POSITIVE - def f(self, x): - return np.where(x<_lim_val, np.where(x>-_lim_val, np.exp(x), np.exp(-_lim_val)), np.exp(_lim_val)) - def finv(self, x): - return np.log(x) - def gradfactor(self, f, df): - return np.einsum('i,i->i', df, f) - def initialize(self, f): - if np.any(f < 0.): - print "Warning: changing parameters to satisfy constraints" - return np.abs(f) - def __str__(self): - return '+ve' - class NegativeExponent(Exponent): domain = _NEGATIVE def f(self, x): @@ -449,7 +486,11 @@ class Logistic(Transformation): for instance in cls._instances: if instance().lower == lower and instance().upper == upper: return instance() - o = super(Transformation, cls).__new__(cls, lower, upper, *args, **kwargs) + newfunc = super(Transformation, cls).__new__ + if newfunc is object.__new__: + o = newfunc(cls) + else: + o = newfunc(cls, lower, upper, *args, **kwargs) cls._instances.append(weakref.ref(o)) return cls._instances[-1]() def __init__(self, lower, upper): @@ -467,7 +508,7 @@ class Logistic(Transformation): return np.einsum('i,i->i', df, (f - self.lower) * (self.upper - f) / self.difference) def initialize(self, f): if np.any(np.logical_or(f < self.lower, f > self.upper)): - print "Warning: changing parameters to satisfy constraints" + print("Warning: changing parameters to satisfy constraints") #return np.where(np.logical_or(f < self.lower, f > self.upper), self.f(f * 0.), f) #FIXME: Max, zeros_like right? return np.where(np.logical_or(f < self.lower, f > self.upper), self.f(np.zeros_like(f)), f)