class CommitInfo(tr.HasStrictTraits): hash = tr.Str parents = tr.List(tr.This) children = tr.Set(tr.This) diff = tr.Either(None, tr.CLong) gitdiff = tr.Either(None, tr.CLong)
def test_trait_utils4(self): trait = traits.Either(traits.Int(47), traits.Str("vovo")).as_ctrait() trait.ouptut = False trait.optional = False manhelp = get_trait_desc("choice", trait, None) desc = ' '.join([x.strip() for x in manhelp[:-1]]) self.assertTrue( desc in ("choice: an integer (int or long) or a string " "(['Int', 'Str'] - mandatory)", "choice: an integer or a string " "(['Int', 'Str'] - mandatory)")) self.assertEqual(manhelp[-1], " No description.")
def __init__(self, pipeline, name, input_names=['input_%d'], output_names=['outputs'], input_types=None): in_traits = [{ 'name': 'lengths', 'optional': True }, { 'name': 'skip_empty', 'optional': True }] out_traits = [] if input_types: ptypes = input_types else: ptypes = [traits.File(traits.Undefined, output=False)] \ * len(input_names) self.input_types = ptypes for tr in output_names: out_traits.append({'name': tr, 'optional': False}) super(ReduceNode, self).__init__(pipeline, name, in_traits, out_traits) for tr, ptype in zip(output_names, ptypes): self.add_trait( tr, traits.List(traits.Either(ptype, traits.Undefined), output=True)) self.add_trait( 'lengths', traits.List(traits.Int(), output=False, desc='lists lengths')) self.add_trait( 'skip_empty', traits.Bool(False, output=False, desc='remove empty (Undefined, None, empty strings) ' 'from the output lists')) self.input_names = input_names self.output_names = output_names self.lengths = [0] * len(input_names) self.set_callbacks() self.lengths = [1] * len(input_names)
class Parameter(t.HasTraits): """Model parameter Attributes ---------- value : float or array The value of the parameter for the current location. The value for other locations is stored in map. bmin, bmax: float Lower and upper bounds of the parameter value. twin : {None, Parameter} If it is not None, the value of the current parameter is a function of the given Parameter. The function is by default the identity function, but it can be defined by twin_function twin_function_expr: str Expression of the ``twin_function`` that enables setting a functional relationship between the parameter and its twin. If ``twin`` is not ``None``, the parameter value is calculated as the output of calling the twin function with the value of the twin parameter. The string is parsed using sympy, so permitted values are any valid sympy expressions of one variable. If the function is invertible the twin inverse function is set automatically. twin_inverse_function : str Expression of the ``twin_inverse_function`` that enables setting the value of the twin parameter. If ``twin`` is not ``None``, its value is set to the output of calling the twin inverse function with the value provided. The string is parsed using sympy, so permitted values are any valid sympy expressions of one variable. twin_function : function **Setting this attribute manually is deprecated in HyperSpy newer than 1.1.2. It will become private in HyperSpy 2.0. Please use ``twin_function_expr`` instead.** twin_inverse_function : function **Setting this attribute manually is deprecated in HyperSpy newer than 1.1.2. It will become private in HyperSpy 2.0. Please use ``twin_inverse_function_expr`` instead.** ext_force_positive : bool If True, the parameter value is set to be the absolute value of the input value i.e. if we set Parameter.value = -3, the value stored is 3 instead. This is useful to bound a value to be positive in an optimization without actually using an optimizer that supports bounding. ext_bounded : bool Similar to ext_force_positive, but in this case the bounds are defined by bmin and bmax. It is a better idea to use an optimizer that supports bounding though. Methods ------- as_signal(field = 'values') Get a parameter map as a signal object plot() Plots the value of the Parameter at all locations. export(folder=None, name=None, format=None, save_std=False) Saves the value of the parameter map to the specified format connect, disconnect(function) Call the functions connected when the value attribute changes. """ __number_of_elements = 1 __value = 0 __free = True _bounds = (None, None) __twin = None _axes_manager = None __ext_bounded = False __ext_force_positive = False # traitsui bugs out trying to make an editor for this, so always specify! # (it bugs out, because both editor shares the object, and Array editors # don't like non-sequence objects). TextEditor() works well, so does # RangeEditor() as it works with bmin/bmax. value = t.Property(t.Either([t.CFloat(0), Array()])) units = t.Str('') free = t.Property(t.CBool(True)) bmin = t.Property(NoneFloat(), label="Lower bounds") bmax = t.Property(NoneFloat(), label="Upper bounds") _twin_function_expr = "" _twin_inverse_function_expr = "" twin_function = None _twin_inverse_function = None _twin_inverse_sympy = None def __init__(self): self._twins = set() self.events = Events() self.events.value_changed = Event(""" Event that triggers when the `Parameter.value` changes. The event triggers after the internal state of the `Parameter` has been updated. Arguments --------- obj : Parameter The `Parameter` that the event belongs to value : {float | array} The new value of the parameter """, arguments=["obj", 'value']) self.std = None self.component = None self.grad = None self.name = '' self.units = '' self.map = None self.model = None self._whitelist = { '_id_name': None, 'value': None, 'std': None, 'free': None, 'units': None, 'map': None, '_bounds': None, 'ext_bounded': None, 'name': None, 'ext_force_positive': None, 'twin_function_expr': None, 'twin_inverse_function_expr': None, 'self': ('id', None), } self._slicing_whitelist = {'map': 'inav'} def _load_dictionary(self, dictionary): """Load data from dictionary Parameters ---------- dict : dictionary A dictionary containing at least the following items: _id_name : string _id_name of the original parameter, used to create the dictionary. Has to match with the self._id_name _whitelist : dictionary a dictionary, which keys are used as keywords to match with the parameter attributes. For more information see :meth:`hyperspy.misc.export_dictionary.load_from_dictionary` * any field from _whitelist.keys() * Returns ------- id_value : int the ID value of the original parameter, to be later used for setting up the correct twins """ if dictionary['_id_name'] == self._id_name: load_from_dictionary(self, dictionary) return dictionary['self'] else: raise ValueError( "_id_name of parameter and dictionary do not match, \nparameter._id_name = %s\ \ndictionary['_id_name'] = %s" % (self._id_name, dictionary['_id_name'])) def __repr__(self): text = '' text += 'Parameter %s' % self.name if self.component is not None: text += ' of %s' % self.component._get_short_description() text = '<' + text + '>' return text def __len__(self): return self._number_of_elements @property def twin_function_expr(self): return self._twin_function_expr @twin_function_expr.setter def twin_function_expr(self, value): if not value: self.twin_function = None self.twin_inverse_function = None self._twin_function_expr = "" self._twin_inverse_sympy = None return expr = sympy.sympify(value) if len(expr.free_symbols) > 1: raise ValueError("The expression must contain only one variable.") elif len(expr.free_symbols) == 0: raise ValueError("The expression must contain one variable, " "it contains none.") x = tuple(expr.free_symbols)[0] self.twin_function = lambdify(x, expr.evalf()) self._twin_function_expr = value if not self.twin_inverse_function: y = sympy.Symbol(x.name + "2") try: inv = sympy.solveset(sympy.Eq(y, expr), x) self._twin_inverse_sympy = lambdify(y, inv) self._twin_inverse_function = None except BaseException: # Not all may have a suitable solution. self._twin_inverse_function = None self._twin_inverse_sympy = None _logger.warning( "The function {} is not invertible. Setting the value of " "{} will raise an AttributeError unless you set manually " "``twin_inverse_function_expr``. Otherwise, set the " "value of its twin parameter instead.".format(value, self)) @property def twin_inverse_function_expr(self): if self.twin: return self._twin_inverse_function_expr else: return "" @twin_inverse_function_expr.setter def twin_inverse_function_expr(self, value): if not value: self.twin_inverse_function = None self._twin_inverse_function_expr = "" return expr = sympy.sympify(value) if len(expr.free_symbols) > 1: raise ValueError("The expression must contain only one variable.") elif len(expr.free_symbols) == 0: raise ValueError("The expression must contain one variable, " "it contains none.") x = tuple(expr.free_symbols)[0] self._twin_inverse_function = lambdify(x, expr.evalf()) self._twin_inverse_function_expr = value @property def twin_inverse_function(self): if (not self.twin_inverse_function_expr and self.twin_function_expr and self._twin_inverse_sympy): return lambda x: self._twin_inverse_sympy(x).pop() else: return self._twin_inverse_function @twin_inverse_function.setter def twin_inverse_function(self, value): self._twin_inverse_function = value def _get_value(self): if self.twin is None: return self.__value else: if self.twin_function: return self.twin_function(self.twin.value) else: return self.twin.value def _set_value(self, value): try: # Use try/except instead of hasattr("__len__") because a numpy # memmap has a __len__ wrapper even for numbers that raises a # TypeError when calling. See issue #349. if len(value) != self._number_of_elements: raise ValueError("The length of the parameter must be ", self._number_of_elements) else: if not isinstance(value, tuple): value = tuple(value) except TypeError: if self._number_of_elements != 1: raise ValueError("The length of the parameter must be ", self._number_of_elements) old_value = self.__value if self.twin is not None: if self.twin_function is not None: if self.twin_inverse_function is not None: self.twin.value = self.twin_inverse_function(value) return else: raise AttributeError( "This parameter has a ``twin_function`` but" "its ``twin_inverse_function`` is not defined.") else: self.twin.value = value return if self.ext_bounded is False: self.__value = value else: if self.ext_force_positive is True: value = np.abs(value) if self._number_of_elements == 1: if self.bmin is not None and value <= self.bmin: self.__value = self.bmin elif self.bmax is not None and value >= self.bmax: self.__value = self.bmax else: self.__value = value else: bmin = (self.bmin if self.bmin is not None else -np.inf) bmax = (self.bmax if self.bmin is not None else np.inf) self.__value = np.clip(value, bmin, bmax) if (self._number_of_elements != 1 and not isinstance(self.__value, tuple)): self.__value = tuple(self.__value) if old_value != self.__value: self.events.value_changed.trigger(value=self.__value, obj=self) self.trait_property_changed('value', old_value, self.__value) # Fix the parameter when coupled def _get_free(self): if self.twin is None: return self.__free else: return False def _set_free(self, arg): old_value = self.__free self.__free = arg if self.component is not None: self.component._update_free_parameters() self.trait_property_changed('free', old_value, self.__free) def _on_twin_update(self, value, twin=None): if (twin is not None and hasattr(twin, 'events') and hasattr(twin.events, 'value_changed')): with twin.events.value_changed.suppress_callback( self._on_twin_update): self.events.value_changed.trigger(value=value, obj=self) else: self.events.value_changed.trigger(value=value, obj=self) def _set_twin(self, arg): if arg is None: if self.twin is not None: # Store the value of the twin in order to set the # value of the parameter when it is uncoupled twin_value = self.value if self in self.twin._twins: self.twin._twins.remove(self) self.twin.events.value_changed.disconnect( self._on_twin_update) self.__twin = arg self.value = twin_value else: if self not in arg._twins: arg._twins.add(self) arg.events.value_changed.connect(self._on_twin_update, ["value"]) self.__twin = arg if self.component is not None: self.component._update_free_parameters() def _get_twin(self): return self.__twin twin = property(_get_twin, _set_twin) def _get_bmin(self): if self._number_of_elements == 1: return self._bounds[0] else: return self._bounds[0][0] def _set_bmin(self, arg): old_value = self.bmin if self._number_of_elements == 1: self._bounds = (arg, self.bmax) else: self._bounds = ((arg, self.bmax), ) * self._number_of_elements # Update the value to take into account the new bounds self.value = self.value self.trait_property_changed('bmin', old_value, arg) def _get_bmax(self): if self._number_of_elements == 1: return self._bounds[1] else: return self._bounds[0][1] def _set_bmax(self, arg): old_value = self.bmax if self._number_of_elements == 1: self._bounds = (self.bmin, arg) else: self._bounds = ((self.bmin, arg), ) * self._number_of_elements # Update the value to take into account the new bounds self.value = self.value self.trait_property_changed('bmax', old_value, arg) @property def _number_of_elements(self): return self.__number_of_elements @_number_of_elements.setter def _number_of_elements(self, arg): # Do nothing if the number of arguments stays the same if self.__number_of_elements == arg: return if arg <= 1: raise ValueError("Please provide an integer number equal " "or greater to 1") self._bounds = ((self.bmin, self.bmax), ) * arg self.__number_of_elements = arg if arg == 1: self._Parameter__value = 0 else: self._Parameter__value = (0, ) * arg if self.component is not None: self.component.update_number_parameters() @property def ext_bounded(self): return self.__ext_bounded @ext_bounded.setter def ext_bounded(self, arg): if arg is not self.__ext_bounded: self.__ext_bounded = arg # Update the value to take into account the new bounds self.value = self.value @property def ext_force_positive(self): return self.__ext_force_positive @ext_force_positive.setter def ext_force_positive(self, arg): if arg is not self.__ext_force_positive: self.__ext_force_positive = arg # Update the value to take into account the new bounds self.value = self.value def store_current_value_in_array(self): """Store the value and std attributes. See also -------- fetch, assign_current_value_to_all """ indices = self._axes_manager.indices[::-1] # If it is a single spectrum indices is () if not indices: indices = (0, ) self.map['values'][indices] = self.value self.map['is_set'][indices] = True if self.std is not None: self.map['std'][indices] = self.std def fetch(self): """Fetch the stored value and std attributes. See Also -------- store_current_value_in_array, assign_current_value_to_all """ indices = self._axes_manager.indices[::-1] # If it is a single spectrum indices is () if not indices: indices = (0, ) if self.map['is_set'][indices]: value = self.map['values'][indices] std = self.map['std'][indices] if isinstance(value, dArray): value = value.compute() if isinstance(std, dArray): std = std.compute() self.value = value self.std = std def assign_current_value_to_all(self, mask=None): """Assign the current value attribute to all the indices Parameters ---------- mask: {None, boolean numpy array} Set only the indices that are not masked i.e. where mask is False. See Also -------- store_current_value_in_array, fetch """ if mask is None: mask = np.zeros(self.map.shape, dtype='bool') self.map['values'][mask == False] = self.value self.map['is_set'][mask == False] = True def _create_array(self): """Create the map array to store the information in multidimensional datasets. """ shape = self._axes_manager._navigation_shape_in_array if not shape: shape = [ 1, ] # Shape-1 fields in dtypes won’t be collapsed to scalars in a future # numpy version (see release notes numpy 1.17.0) if self._number_of_elements > 1: dtype_ = np.dtype([('values', 'float', self._number_of_elements), ('std', 'float', self._number_of_elements), ('is_set', 'bool')]) else: dtype_ = np.dtype([('values', 'float'), ('std', 'float'), ('is_set', 'bool')]) if (self.map is None or self.map.shape != shape or self.map.dtype != dtype_): self.map = np.zeros(shape, dtype_) self.map['std'].fill(np.nan) # TODO: in the future this class should have access to # axes manager and should be able to fetch its own # values. Until then, the next line is necessary to avoid # erros when self.std is defined and the shape is different # from the newly defined arrays self.std = None def as_signal(self, field='values'): """Get a parameter map as a signal object. Please note that this method only works when the navigation dimension is greater than 0. Parameters ---------- field : {'values', 'std', 'is_set'} Raises ------ NavigationDimensionError : if the navigation dimension is 0 """ from hyperspy.signal import BaseSignal s = BaseSignal(data=self.map[field], axes=self._axes_manager._get_navigation_axes_dicts()) if self.component is not None and \ self.component.active_is_multidimensional: s.data[np.logical_not(self.component._active_array)] = np.nan s.metadata.General.title = ("%s parameter" % self.name if self.component is None else "%s parameter of %s component" % (self.name, self.component.name)) for axis in s.axes_manager._axes: axis.navigate = False if self._number_of_elements > 1: s.axes_manager._append_axis(size=self._number_of_elements, name=self.name, navigate=True) s._assign_subclass() if field == "values": # Add the variance if available std = self.as_signal(field="std") if not np.isnan(std.data).all(): std.data = std.data**2 std.metadata.General.title = "Variance" s.metadata.set_item("Signal.Noise_properties.variance", std) return s def plot(self, **kwargs): """Plot parameter signal. Parameters ---------- **kwargs Any extra keyword arguments are passed to the signal plot. Example ------- >>> parameter.plot() #doctest: +SKIP Set the minimum and maximum displayed values >>> parameter.plot(vmin=0, vmax=1) #doctest: +SKIP """ self.as_signal().plot(**kwargs) def export(self, folder=None, name=None, format="hspy", save_std=False): """Save the data to a file. All the arguments are optional. Parameters ---------- folder : str or None The path to the folder where the file will be saved. If `None` the current folder is used by default. name : str or None The name of the file. If `None` the Components name followed by the Parameter `name` attributes will be used by default. If a file with the same name exists the name will be modified by appending a number to the file path. save_std : bool If True, also the standard deviation will be saved format: str The extension of any file format supported by HyperSpy, default hspy """ if format is None: format = "hspy" if name is None: name = self.component.name + '_' + self.name filename = incremental_filename(slugify(name) + '.' + format) if folder is not None: filename = os.path.join(folder, filename) self.as_signal().save(filename) if save_std is True: self.as_signal(field='std').save(append2pathname(filename, '_std')) def as_dictionary(self, fullcopy=True): """Returns parameter as a dictionary, saving all attributes from self._whitelist.keys() For more information see :meth:`hyperspy.misc.export_dictionary.export_to_dictionary` Parameters ---------- fullcopy : Bool (optional, False) Copies of objects are stored, not references. If any found, functions will be pickled and signals converted to dictionaries Returns ------- dic : dictionary with the following keys: _id_name : string _id_name of the original parameter, used to create the dictionary. Has to match with the self._id_name _twins : list a list of ids of the twins of the parameter _whitelist : dictionary a dictionary, which keys are used as keywords to match with the parameter attributes. For more information see :meth:`hyperspy.misc.export_dictionary.export_to_dictionary` * any field from _whitelist.keys() * """ dic = {'_twins': [id(t) for t in self._twins]} export_to_dictionary(self, self._whitelist, dic, fullcopy) return dic def default_traits_view(self): # As mentioned above, the default editor for # value = t.Property(t.Either([t.CFloat(0), Array()])) # gives a ValueError. We therefore implement default_traits_view so # that configure/edit_traits will still work straight out of the box. # A whitelist controls which traits to include in this view. from traitsui.api import RangeEditor, View, Item whitelist = ['bmax', 'bmin', 'free', 'name', 'std', 'units', 'value'] editable_traits = [ trait for trait in self.editable_traits() if trait in whitelist ] if 'value' in editable_traits: i = editable_traits.index('value') v = editable_traits.pop(i) editable_traits.insert( i, Item(v, editor=RangeEditor(low_name='bmin', high_name='bmax'))) view = View(editable_traits, buttons=['OK', 'Cancel']) return view
class Parameter(t.HasTraits): """Model parameter Attributes ---------- value : float or array The value of the parameter for the current location. The value for other locations is stored in map. bmin, bmax: float Lower and upper bounds of the parameter value. twin : {None, Parameter} If it is not None, the value of the current parameter is a function of the given Parameter. The function is by default the identity function, but it can be defined by twin_function twin_function : function Function that, if selt.twin is not None, takes self.twin.value as its only argument and returns a float or array that is returned when getting Parameter.value twin_inverse_function : function The inverse of twin_function. If it is None then it is not possible to set the value of the parameter twin by setting the value of the current parameter. ext_force_positive : bool If True, the parameter value is set to be the absolute value of the input value i.e. if we set Parameter.value = -3, the value stored is 3 instead. This is useful to bound a value to be positive in an optimization without actually using an optimizer that supports bounding. ext_bounded : bool Similar to ext_force_positive, but in this case the bounds are defined by bmin and bmax. It is a better idea to use an optimizer that supports bounding though. Methods ------- as_signal(field = 'values') Get a parameter map as a signal object plot() Plots the value of the Parameter at all locations. export(folder=None, name=None, format=None, save_std=False) Saves the value of the parameter map to the specified format connect, disconnect(function) Call the functions connected when the value attribute changes. """ __number_of_elements = 1 __value = 0 __free = True _bounds = (None, None) __twin = None _axes_manager = None __ext_bounded = False __ext_force_positive = False # traitsui bugs out trying to make an editor for this, so always specify! # (it bugs out, because both editor shares the object, and Array editors # don't like non-sequence objects). TextEditor() works well, so does # RangeEditor() as it works with bmin/bmax. value = t.Property(t.Either([t.CFloat(0), Array()])) units = t.Str('') free = t.Property(t.CBool(True)) bmin = t.Property(NoneFloat(), label="Lower bounds") bmax = t.Property(NoneFloat(), label="Upper bounds") def __init__(self): self._twins = set() self.connected_functions = list() self.twin_function = lambda x: x self.twin_inverse_function = lambda x: x self.std = None self.component = None self.grad = None self.name = '' self.map = None self.model = None def __repr__(self): text = '' text += 'Parameter %s' % self.name if self.component is not None: text += ' of %s' % self.component._get_short_description() text = '<' + text + '>' return text.encode('utf8') def __len__(self): return self._number_of_elements def connect(self, f): if f not in self.connected_functions: self.connected_functions.append(f) if self.twin: self.twin.connect(f) def disconnect(self, f): if f in self.connected_functions: self.connected_functions.remove(f) if self.twin: self.twin.disconnect(f) def _get_value(self): if self.twin is None: return self.__value else: return self.twin_function(self.twin.value) def _set_value(self, arg): try: # Use try/except instead of hasattr("__len__") because a numpy # memmap has a __len__ wrapper even for numbers that raises a # TypeError when calling. See issue #349. if len(arg) != self._number_of_elements: raise ValueError("The length of the parameter must be ", self._number_of_elements) else: if not isinstance(arg, tuple): arg = tuple(arg) except TypeError: if self._number_of_elements != 1: raise ValueError("The length of the parameter must be ", self._number_of_elements) old_value = self.__value if self.twin is not None: if self.twin_inverse_function is not None: self.twin.value = self.twin_inverse_function(arg) return if self.ext_bounded is False: self.__value = arg else: if self.ext_force_positive is True: arg = np.abs(arg) if self._number_of_elements == 1: if self.bmin is not None and arg <= self.bmin: self.__value = self.bmin elif self.bmax is not None and arg >= self.bmax: self.__value = self.bmax else: self.__value = arg else: bmin = (self.bmin if self.bmin is not None else -np.inf) bmax = (self.bmax if self.bmin is not None else np.inf) self.__value = np.clip(arg, bmin, bmax) if (self._number_of_elements != 1 and not isinstance(self.__value, tuple)): self.__value = tuple(self.__value) if old_value != self.__value: for f in self.connected_functions: try: f() except: self.disconnect(f) self.trait_property_changed('value', old_value, self.__value) # Fix the parameter when coupled def _get_free(self): if self.twin is None: return self.__free else: return False def _set_free(self, arg): old_value = self.__free self.__free = arg if self.component is not None: self.component._update_free_parameters() self.trait_property_changed('free', old_value, self.__free) def _set_twin(self, arg): if arg is None: if self.twin is not None: # Store the value of the twin in order to set the # value of the parameter when it is uncoupled twin_value = self.value if self in self.twin._twins: self.twin._twins.remove(self) for f in self.connected_functions: self.twin.disconnect(f) self.__twin = arg self.value = twin_value else: if self not in arg._twins: arg._twins.add(self) for f in self.connected_functions: arg.connect(f) self.__twin = arg if self.component is not None: self.component._update_free_parameters() def _get_twin(self): return self.__twin twin = property(_get_twin, _set_twin) def _get_bmin(self): if self._number_of_elements == 1: return self._bounds[0] else: return self._bounds[0][0] def _set_bmin(self, arg): old_value = self.bmin if self._number_of_elements == 1: self._bounds = (arg, self.bmax) else: self._bounds = ((arg, self.bmax), ) * self._number_of_elements # Update the value to take into account the new bounds self.value = self.value self.trait_property_changed('bmin', old_value, arg) def _get_bmax(self): if self._number_of_elements == 1: return self._bounds[1] else: return self._bounds[0][1] def _set_bmax(self, arg): old_value = self.bmax if self._number_of_elements == 1: self._bounds = (self.bmin, arg) else: self._bounds = ((self.bmin, arg), ) * self._number_of_elements # Update the value to take into account the new bounds self.value = self.value self.trait_property_changed('bmax', old_value, arg) @property def _number_of_elements(self): return self.__number_of_elements @_number_of_elements.setter def _number_of_elements(self, arg): # Do nothing if the number of arguments stays the same if self.__number_of_elements == arg: return if arg <= 1: raise ValueError("Please provide an integer number equal " "or greater to 1") self._bounds = ((self.bmin, self.bmax), ) * arg self.__number_of_elements = arg if arg == 1: self._Parameter__value = 0 else: self._Parameter__value = (0, ) * arg if self.component is not None: self.component.update_number_parameters() @property def ext_bounded(self): return self.__ext_bounded @ext_bounded.setter def ext_bounded(self, arg): if arg is not self.__ext_bounded: self.__ext_bounded = arg # Update the value to take into account the new bounds self.value = self.value @property def ext_force_positive(self): return self.__ext_force_positive @ext_force_positive.setter def ext_force_positive(self, arg): if arg is not self.__ext_force_positive: self.__ext_force_positive = arg # Update the value to take into account the new bounds self.value = self.value def store_current_value_in_array(self): """Store the value and std attributes. See also -------- fetch, assign_current_value_to_all """ indices = self._axes_manager.indices[::-1] # If it is a single spectrum indices is () if not indices: indices = (0, ) self.map['values'][indices] = self.value self.map['is_set'][indices] = True if self.std is not None: self.map['std'][indices] = self.std def fetch(self): """Fetch the stored value and std attributes. See Also -------- store_current_value_in_array, assign_current_value_to_all """ indices = self._axes_manager.indices[::-1] # If it is a single spectrum indices is () if not indices: indices = (0, ) if self.map['is_set'][indices]: self.value = self.map['values'][indices] self.std = self.map['std'][indices] def assign_current_value_to_all(self, mask=None): '''Assign the current value attribute to all the indices Parameters ---------- mask: {None, boolean numpy array} Set only the indices that are not masked i.e. where mask is False. See Also -------- store_current_value_in_array, fetch ''' if mask is None: mask = np.zeros(self.map.shape, dtype='bool') self.map['values'][mask == False] = self.value self.map['is_set'][mask == False] = True def _create_array(self): """Create the map array to store the information in multidimensional datasets. """ shape = self._axes_manager._navigation_shape_in_array if not shape: shape = [ 1, ] dtype_ = np.dtype([('values', 'float', self._number_of_elements), ('std', 'float', self._number_of_elements), ('is_set', 'bool', 1)]) if (self.map is None or self.map.shape != shape or self.map.dtype != dtype_): self.map = np.zeros(shape, dtype_) self.map['std'].fill(np.nan) # TODO: in the future this class should have access to # axes manager and should be able to fetch its own # values. Until then, the next line is necessary to avoid # erros when self.std is defined and the shape is different # from the newly defined arrays self.std = None def as_signal(self, field='values'): """Get a parameter map as a signal object. Please note that this method only works when the navigation dimension is greater than 0. Parameters ---------- field : {'values', 'std', 'is_set'} Raises ------ NavigationDimensionError : if the navigation dimension is 0 """ from hyperspy.signal import Signal if self._axes_manager.navigation_dimension == 0: raise NavigationDimensionError(0, '>0') s = Signal(data=self.map[field], axes=self._axes_manager._get_navigation_axes_dicts()) if self.component.active_is_multidimensional: s.data[np.logical_not(self.component._active_array)] = np.nan s.metadata.General.title = ("%s parameter" % self.name if self.component is None else "%s parameter of %s component" % (self.name, self.component.name)) for axis in s.axes_manager._axes: axis.navigate = False if self._number_of_elements > 1: s.axes_manager.append_axis(size=self._number_of_elements, name=self.name, navigate=True) return s def plot(self): self.as_signal().plot() def export(self, folder=None, name=None, format=None, save_std=False): '''Save the data to a file. All the arguments are optional. Parameters ---------- folder : str or None The path to the folder where the file will be saved. If `None` the current folder is used by default. name : str or None The name of the file. If `None` the Components name followed by the Parameter `name` attributes will be used by default. If a file with the same name exists the name will be modified by appending a number to the file path. save_std : bool If True, also the standard deviation will be saved ''' if format is None: format = preferences.General.default_export_format if name is None: name = self.component.name + '_' + self.name filename = incremental_filename(slugify(name) + '.' + format) if folder is not None: filename = os.path.join(folder, filename) self.as_signal().save(filename) if save_std is True: self.as_signal(field='std').save(append2pathname(filename, '_std')) def default_traits_view(self): # As mentioned above, the default editor for # value = t.Property(t.Either([t.CFloat(0), Array()])) # gives a ValueError. We therefore implement default_traits_view so # that configure/edit_traits will still work straight out of the box. # A whitelist controls which traits to include in this view. from traitsui.api import RangeEditor, View, Item whitelist = ['bmax', 'bmin', 'free', 'name', 'std', 'units', 'value'] editable_traits = [ trait for trait in self.editable_traits() if trait in whitelist ] if 'value' in editable_traits: i = editable_traits.index('value') v = editable_traits.pop(i) editable_traits.insert( i, Item(v, editor=RangeEditor(low_name='bmin', high_name='bmax'))) view = View(editable_traits, buttons=['OK', 'Cancel']) return view
class Agent(t.HasTraits): """ An Agent is the basic class of the simulation. Agents communicate asynchronously. """ #DEBUG_RECEIVE = True #DEBUG_SEND = True _address_book = t.Instance(addressing.AddressBook, transient=True, allow_none=False) _default_queue = t.Instance(queue.Queue, transient=True, allow_none=False) _greenlet = t.Instance(gevent.Greenlet, transient=True, allow_none=False) _node_db = t.Instance(agent_db.IAgentStorage, transient=True, allow_none=False) id = t.Either(t.CInt, t.Str) __ = t.PythonValue(transient=True) def join(self): try: return self._greenlet.join() except (KeyboardInterrupt, SystemExit) as e: print "[%s] I was caught with the hands in the jam!" % self.id print self._create_error_text(self._greenlet) raise AgentError(e) def sleep(self, seconds): gevent.sleep(seconds) def setup(self): """ Additional initialization before the run loop goes here. The default implementation is empty. """ def _compute_identifier(self, identifier): if identifier is None: try: self.id = self.name except Exception: raise AgentError("%r is not a valid Agent Id." % identifier) else: self.id = identifier def start(self, address_book, agent_db, identifier=None): """ Start executing the agent. :param address_book: the address book where the other agents references can be found. :type address_book: :class:`addressing.AddressBook` :param agent_db: the agent database that is used if this agent needs to be serialized :type agent_db: :class:`agent_db.IAgentStorage` :param identifier: this agent identifier. It is also added to the address book. :type identifier: str | int :return: the agent itself :rtype: :class:`Agent` """ self._compute_identifier(identifier) self._address_book = address_book self._address_book.register(self, self.id) self._default_queue = queue.Queue() self._node_db = agent_db self._greenlet = gevent.Greenlet(self._start) self._greenlet.link_exception(self.on_error) if hasattr(self, 'on_completion'): self._greenlet.link_value(self.on_completion) self._greenlet.start() return self @property def started(self): """ True if the agent has been started. """ return ((self.id is not None) and (self._address_book is not None) and (self._greenlet is not None)) def _revert_start(self, _source): self._address_book.unregister(self.id) del self._address_book del self._default_queue del self._node_db del self._greenlet def _create_error_text(self, source): ss = StringIO() print >> ss, self print >> ss, source print >> ss, 'succesful:', source.successful() print >> ss, 'value:', source.value print >> ss, 'exception:', source.exception if self._default_queue is not None: print >> ss, 'messages:', self._default_queue.qsize() if self._default_queue.qsize(): import pprint pprint.pprint([ message for message, _result in self._default_queue.queue ], stream=ss) return ss.getvalue() def on_error(self, source): """ This handler is called when an error arises in the greenlet executing the agent. The default implementation logs the error. Other implementations may take different actions. :param source: the greenlet where the error originated. :type source: gevent.Greenlet """ text = self._create_error_text(source) self._get_logger().put_error(self, text) def kill(self): return self._greenlet.kill() def read(self, timeout=None): """ Reads the next message that was sent to this agent. :param timeout: can specify a timeout :return: (Message, event.AsyncResult) :raise: NoMessage if no message arrives after timeout .. warning:: It is not really meant to be called directly unless you are building a runloop. Strictly speaking it is not non public, though. """ try: entry = self._default_queue.get(timeout=timeout) if getattr(self, 'DEBUG_RECEIVE', False): self.log_received(entry[0]) return entry except queue.Empty, e: raise NoMessage(e)
class Parameter(t.HasTraits): """Model parameter Attributes ---------- value : float or array The value of the parameter for the current location. The value for other locations is stored in map. bmin, bmax: float Lower and upper bounds of the parameter value. twin : {None, Parameter} If it is not None, the value of the current parameter is a function of the given Parameter. The function is by default the identity function, but it can be defined by twin_function twin_function : function Function that, if selt.twin is not None, takes self.twin.value as its only argument and returns a float or array that is returned when getting Parameter.value twin_inverse_function : function The inverse of twin_function. If it is None then it is not possible to set the value of the parameter twin by setting the value of the current parameter. ext_force_positive : bool If True, the parameter value is set to be the absolute value of the input value i.e. if we set Parameter.value = -3, the value stored is 3 instead. This is useful to bound a value to be positive in an optimization without actually using an optimizer that supports bounding. ext_bounded : bool Similar to ext_force_positive, but in this case the bounds are defined by bmin and bmax. It is a better idea to use an optimizer that supports bounding though. Methods ------- as_signal(field = 'values') Get a parameter map as a signal object plot() Plots the value of the Parameter at all locations. export(folder=None, name=None, format=None, save_std=False) Saves the value of the parameter map to the specified format connect, disconnect(function) Call the functions connected when the value attribute changes. """ __number_of_elements = 1 __value = 0 __free = True _bounds = (None, None) __twin = None _axes_manager = None __ext_bounded = False __ext_force_positive = False # traitsui bugs out trying to make an editor for this, so always specify! # (it bugs out, because both editor shares the object, and Array editors # don't like non-sequence objects). TextEditor() works well, so does # RangeEditor() as it works with bmin/bmax. value = t.Property(t.Either([t.CFloat(0), Array()])) units = t.Str('') free = t.Property(t.CBool(True)) bmin = t.Property(NoneFloat(), label="Lower bounds") bmax = t.Property(NoneFloat(), label="Upper bounds") def __init__(self): self._twins = set() self.events = Events() self.events.value_changed = Event(""" Event that triggers when the `Parameter.value` changes. The event triggers after the internal state of the `Parameter` has been updated. Arguments --------- obj : Parameter The `Parameter` that the event belongs to value : {float | array} The new value of the parameter """, arguments=["obj", 'value']) self.twin_function = lambda x: x self.twin_inverse_function = lambda x: x self.std = None self.component = None self.grad = None self.name = '' self.units = '' self.map = None self.model = None self._whitelist = { '_id_name': None, 'value': None, 'std': None, 'free': None, 'units': None, 'map': None, '_bounds': None, 'ext_bounded': None, 'name': None, 'ext_force_positive': None, 'self': ('id', None), 'twin_function': ('fn', None), 'twin_inverse_function': ('fn', None), } self._slicing_whitelist = {'map': 'inav'} def _load_dictionary(self, dictionary): """Load data from dictionary Parameters ---------- dict : dictionary A dictionary containing at least the following items: _id_name : string _id_name of the original parameter, used to create the dictionary. Has to match with the self._id_name _whitelist : dictionary a dictionary, which keys are used as keywords to match with the parameter attributes. For more information see :meth:`hyperspy.misc.export_dictionary.load_from_dictionary` * any field from _whitelist.keys() * Returns ------- id_value : int the ID value of the original parameter, to be later used for setting up the correct twins """ if dictionary['_id_name'] == self._id_name: load_from_dictionary(self, dictionary) return dictionary['self'] else: raise ValueError( "_id_name of parameter and dictionary do not match, \nparameter._id_name = %s\ \ndictionary['_id_name'] = %s" % (self._id_name, dictionary['_id_name'])) def __repr__(self): text = '' text += 'Parameter %s' % self.name if self.component is not None: text += ' of %s' % self.component._get_short_description() text = '<' + text + '>' return text def __len__(self): return self._number_of_elements def _get_value(self): if self.twin is None: return self.__value else: return self.twin_function(self.twin.value) def _set_value(self, value): try: # Use try/except instead of hasattr("__len__") because a numpy # memmap has a __len__ wrapper even for numbers that raises a # TypeError when calling. See issue #349. if len(value) != self._number_of_elements: raise ValueError("The length of the parameter must be ", self._number_of_elements) else: if not isinstance(value, tuple): value = tuple(value) except TypeError: if self._number_of_elements != 1: raise ValueError("The length of the parameter must be ", self._number_of_elements) old_value = self.__value if self.twin is not None: if self.twin_inverse_function is not None: self.twin.value = self.twin_inverse_function(value) return if self.ext_bounded is False: self.__value = value else: if self.ext_force_positive is True: value = np.abs(value) if self._number_of_elements == 1: if self.bmin is not None and value <= self.bmin: self.__value = self.bmin elif self.bmax is not None and value >= self.bmax: self.__value = self.bmax else: self.__value = value else: bmin = (self.bmin if self.bmin is not None else -np.inf) bmax = (self.bmax if self.bmin is not None else np.inf) self.__value = np.clip(value, bmin, bmax) if (self._number_of_elements != 1 and not isinstance(self.__value, tuple)): self.__value = tuple(self.__value) if old_value != self.__value: self.events.value_changed.trigger(value=self.__value, obj=self) self.trait_property_changed('value', old_value, self.__value) # Fix the parameter when coupled def _get_free(self): if self.twin is None: return self.__free else: return False def _set_free(self, arg): old_value = self.__free self.__free = arg if self.component is not None: self.component._update_free_parameters() self.trait_property_changed('free', old_value, self.__free) def _on_twin_update(self, value, twin=None): if (twin is not None and hasattr(twin, 'events') and hasattr(twin.events, 'value_changed')): with twin.events.value_changed.suppress_callback( self._on_twin_update): self.events.value_changed.trigger(value=value, obj=self) else: self.events.value_changed.trigger(value=value, obj=self) def _set_twin(self, arg): if arg is None: if self.twin is not None: # Store the value of the twin in order to set the # value of the parameter when it is uncoupled twin_value = self.value if self in self.twin._twins: self.twin._twins.remove(self) self.twin.events.value_changed.disconnect( self._on_twin_update) self.__twin = arg self.value = twin_value else: if self not in arg._twins: arg._twins.add(self) arg.events.value_changed.connect(self._on_twin_update, ["value"]) self.__twin = arg if self.component is not None: self.component._update_free_parameters() def _get_twin(self): return self.__twin twin = property(_get_twin, _set_twin) def _get_bmin(self): if self._number_of_elements == 1: return self._bounds[0] else: return self._bounds[0][0] def _set_bmin(self, arg): old_value = self.bmin if self._number_of_elements == 1: self._bounds = (arg, self.bmax) else: self._bounds = ((arg, self.bmax), ) * self._number_of_elements # Update the value to take into account the new bounds self.value = self.value self.trait_property_changed('bmin', old_value, arg) def _get_bmax(self): if self._number_of_elements == 1: return self._bounds[1] else: return self._bounds[0][1] def _set_bmax(self, arg): old_value = self.bmax if self._number_of_elements == 1: self._bounds = (self.bmin, arg) else: self._bounds = ((self.bmin, arg), ) * self._number_of_elements # Update the value to take into account the new bounds self.value = self.value self.trait_property_changed('bmax', old_value, arg) @property def _number_of_elements(self): return self.__number_of_elements @_number_of_elements.setter def _number_of_elements(self, arg): # Do nothing if the number of arguments stays the same if self.__number_of_elements == arg: return if arg <= 1: raise ValueError("Please provide an integer number equal " "or greater to 1") self._bounds = ((self.bmin, self.bmax), ) * arg self.__number_of_elements = arg if arg == 1: self._Parameter__value = 0 else: self._Parameter__value = (0, ) * arg if self.component is not None: self.component.update_number_parameters() @property def ext_bounded(self): return self.__ext_bounded @ext_bounded.setter def ext_bounded(self, arg): if arg is not self.__ext_bounded: self.__ext_bounded = arg # Update the value to take into account the new bounds self.value = self.value @property def ext_force_positive(self): return self.__ext_force_positive @ext_force_positive.setter def ext_force_positive(self, arg): if arg is not self.__ext_force_positive: self.__ext_force_positive = arg # Update the value to take into account the new bounds self.value = self.value def store_current_value_in_array(self): """Store the value and std attributes. See also -------- fetch, assign_current_value_to_all """ indices = self._axes_manager.indices[::-1] # If it is a single spectrum indices is () if not indices: indices = (0, ) self.map['values'][indices] = self.value self.map['is_set'][indices] = True if self.std is not None: self.map['std'][indices] = self.std def fetch(self): """Fetch the stored value and std attributes. See Also -------- store_current_value_in_array, assign_current_value_to_all """ indices = self._axes_manager.indices[::-1] # If it is a single spectrum indices is () if not indices: indices = (0, ) if self.map['is_set'][indices]: self.value = self.map['values'][indices] self.std = self.map['std'][indices] def assign_current_value_to_all(self, mask=None): """Assign the current value attribute to all the indices Parameters ---------- mask: {None, boolean numpy array} Set only the indices that are not masked i.e. where mask is False. See Also -------- store_current_value_in_array, fetch """ if mask is None: mask = np.zeros(self.map.shape, dtype='bool') self.map['values'][mask == False] = self.value self.map['is_set'][mask == False] = True def _create_array(self): """Create the map array to store the information in multidimensional datasets. """ shape = self._axes_manager._navigation_shape_in_array if not shape: shape = [ 1, ] dtype_ = np.dtype([('values', 'float', self._number_of_elements), ('std', 'float', self._number_of_elements), ('is_set', 'bool', 1)]) if (self.map is None or self.map.shape != shape or self.map.dtype != dtype_): self.map = np.zeros(shape, dtype_) self.map['std'].fill(np.nan) # TODO: in the future this class should have access to # axes manager and should be able to fetch its own # values. Until then, the next line is necessary to avoid # erros when self.std is defined and the shape is different # from the newly defined arrays self.std = None def as_signal(self, field='values'): """Get a parameter map as a signal object. Please note that this method only works when the navigation dimension is greater than 0. Parameters ---------- field : {'values', 'std', 'is_set'} Raises ------ NavigationDimensionError : if the navigation dimension is 0 """ from hyperspy.signal import BaseSignal s = BaseSignal(data=self.map[field], axes=self._axes_manager._get_navigation_axes_dicts()) if self.component is not None and \ self.component.active_is_multidimensional: s.data[np.logical_not(self.component._active_array)] = np.nan s.metadata.General.title = ("%s parameter" % self.name if self.component is None else "%s parameter of %s component" % (self.name, self.component.name)) for axis in s.axes_manager._axes: axis.navigate = False if self._number_of_elements > 1: s.axes_manager._append_axis(size=self._number_of_elements, name=self.name, navigate=True) s._assign_subclass() if field == "values": # Add the variance if available std = self.as_signal(field="std") if not np.isnan(std.data).all(): std.data = std.data**2 std.metadata.General.title = "Variance" s.metadata.set_item("Signal.Noise_properties.variance", std) return s def plot(self, **kwargs): """Plot parameter signal. Parameters ---------- **kwargs Any extra keyword arguments are passed to the signal plot. Example ------- >>> parameter.plot() Set the minimum and maximum displayed values >>> parameter.plot(vmin=0, vmax=1) """ self.as_signal().plot(**kwargs) def export(self, folder=None, name=None, format=None, save_std=False): """Save the data to a file. All the arguments are optional. Parameters ---------- folder : str or None The path to the folder where the file will be saved. If `None` the current folder is used by default. name : str or None The name of the file. If `None` the Components name followed by the Parameter `name` attributes will be used by default. If a file with the same name exists the name will be modified by appending a number to the file path. save_std : bool If True, also the standard deviation will be saved """ if format is None: format = preferences.General.default_export_format if name is None: name = self.component.name + '_' + self.name filename = incremental_filename(slugify(name) + '.' + format) if folder is not None: filename = os.path.join(folder, filename) self.as_signal().save(filename) if save_std is True: self.as_signal(field='std').save(append2pathname(filename, '_std')) def as_dictionary(self, fullcopy=True): """Returns parameter as a dictionary, saving all attributes from self._whitelist.keys() For more information see :meth:`hyperspy.misc.export_dictionary.export_to_dictionary` Parameters ---------- fullcopy : Bool (optional, False) Copies of objects are stored, not references. If any found, functions will be pickled and signals converted to dictionaries Returns ------- dic : dictionary with the following keys: _id_name : string _id_name of the original parameter, used to create the dictionary. Has to match with the self._id_name _twins : list a list of ids of the twins of the parameter _whitelist : dictionary a dictionary, which keys are used as keywords to match with the parameter attributes. For more information see :meth:`hyperspy.misc.export_dictionary.export_to_dictionary` * any field from _whitelist.keys() * """ dic = {'_twins': [id(t) for t in self._twins]} export_to_dictionary(self, self._whitelist, dic, fullcopy) return dic def default_traits_view(self): # As mentioned above, the default editor for # value = t.Property(t.Either([t.CFloat(0), Array()])) # gives a ValueError. We therefore implement default_traits_view so # that configure/edit_traits will still work straight out of the box. # A whitelist controls which traits to include in this view. from traitsui.api import RangeEditor, View, Item whitelist = ['bmax', 'bmin', 'free', 'name', 'std', 'units', 'value'] editable_traits = [ trait for trait in self.editable_traits() if trait in whitelist ] if 'value' in editable_traits: i = editable_traits.index('value') v = editable_traits.pop(i) editable_traits.insert( i, Item(v, editor=RangeEditor(low_name='bmin', high_name='bmax'))) view = View(editable_traits, buttons=['OK', 'Cancel']) return view def _interactive_slider_bounds(self, index=None): """Guesstimates the bounds for the slider. They will probably have to be changed later by the user. """ fraction = 10. _min, _max, step = None, None, None value = self.value if index is None else self.value[index] if self.bmin is not None: _min = self.bmin if self.bmax is not None: _max = self.bmax if _max is None and _min is not None: _max = value + fraction * (value - _min) if _min is None and _max is not None: _min = value - fraction * (_max - value) if _min is None and _max is None: if self is self.component._position: axis = self._axes_manager.signal_axes[-1] _min = axis.axis.min() _max = axis.axis.max() step = np.abs(axis.scale) else: _max = value + np.abs(value * fraction) _min = value - np.abs(value * fraction) if step is None: step = (_max - _min) * 0.001 return {'min': _min, 'max': _max, 'step': step} def _interactive_update(self, value=None, index=None): """Callback function for the widgets, to update the value """ if value is not None: if index is None: self.value = value['new'] else: self.value = self.value[:index] + (value['new'],) +\ self.value[index + 1:] def notebook_interaction(self, display=True): """Creates interactive notebook widgets for the parameter, if available. Requires `ipywidgets` to be installed. Parameters ---------- display : bool if True (default), attempts to display the parameter widget. Otherwise returns the formatted widget object. """ from ipywidgets import VBox from traitlets import TraitError as TraitletError from IPython.display import display as ip_display try: if self._number_of_elements == 1: container = self._create_notebook_widget() else: children = [ self._create_notebook_widget(index=i) for i in range(self._number_of_elements) ] container = VBox(children) if not display: return container ip_display(container) except TraitletError: if display: _logger.info('This function is only avialable when running in' ' a notebook') else: raise def _create_notebook_widget(self, index=None): from ipywidgets import (FloatSlider, FloatText, Layout, HBox) widget_bounds = self._interactive_slider_bounds(index=index) thismin = FloatText( value=widget_bounds['min'], description='min', layout=Layout(flex='0 1 auto', width='auto'), ) thismax = FloatText( value=widget_bounds['max'], description='max', layout=Layout(flex='0 1 auto', width='auto'), ) current_value = self.value if index is None else self.value[index] current_name = self.name if index is not None: current_name += '_{}'.format(index) widget = FloatSlider(value=current_value, min=thismin.value, max=thismax.value, step=widget_bounds['step'], description=current_name, layout=Layout(flex='1 1 auto', width='auto')) def on_min_change(change): if widget.max > change['new']: widget.min = change['new'] widget.step = np.abs(widget.max - widget.min) * 0.001 def on_max_change(change): if widget.min < change['new']: widget.max = change['new'] widget.step = np.abs(widget.max - widget.min) * 0.001 thismin.observe(on_min_change, names='value') thismax.observe(on_max_change, names='value') this_observed = functools.partial(self._interactive_update, index=index) widget.observe(this_observed, names='value') container = HBox((thismin, widget, thismax)) return container