Пример #1
0
    def __init__(self, name=None, do_demag=True,
                 id="Generic Simulation class"):
        self.class_id = id       # String identifying the kind of Simulation
                                 # class
        self.units = None        # Simulation units used by this class
        self.do_demag = do_demag # Whether we should include the demag field

        # List of all the materials used by the Simulation object
        self.materials = None

        # Dictionary used by the hysteresis method to find abbreviations for
        # frequently used things to save or do.
        # Example: for ``sim.hysteresis(..., save=[('averages', at(...))])``
        # the string 'averages' needs to be a key in this dictionary.
        # The corresponding value is the function to call.
        self.action_abbreviations = {}

        # Every quantity the user may want to save needs to be listed here
        self.known_quantities = known_quantities
        self.known_quantities_by_name = known_quantities_by_name
        self.known_field_quantities = known_field_quantities

        ### Set the simulation name
        if name == None:
            self.name = features.get('etc', 'runid')
        else:
            self.name = name
        log.info("Simulation(name=%s) object created" % self.name)

        ### Check whether the files we are going to write do already exist.
        # if this is the case we should stop, unless the --clean option was
        # given: we do not want to overwrite data as a default!
        self._restarting = False
        data_filenames = [self._ndtfilename(), self._h5filename(),
                          self._tolfilename()]
        if features.get('nmag', 'clean', raw=True):
            # Existing data files should be deleted
            nsim.snippets.rename_old_files(data_filenames)

        elif features.get('nmag', 'restart', raw=True):
            log.info("Starting simulation in restart mode...")
            self._restarting = True

        else:
            # Check that no data files exist
            for filename in data_filenames:
                if os.path.exists(filename):
                    msg = ("Error: Found old file %s -- cannot proceed. "
                           "To start a simulation script with old data "
                           "files present you either need to use '--clean' "
                           "(and then the old files will be deleted), "
                           "or use '--restart' in which case the run "
                           "will be continued." % filename)
                    raise NmagUserError(msg)

        # See documentation for SimulationClock object
        self.clock = SimulationClock()

        # The advance_time method does not allow to carry on the simulation
        # up to t = infinite. Sometimes we want to simulate for n steps,
        # without any time limits. However we must give a time limit.
        # This is then how we approximate t = infinite.
        # For now, we do not provide any function to set or change it.
        # The user should just use:
        #   sim = Simulation()
        #   sim.max_time_reached = SI(1000, "s")
        self.max_time_reached = SI(1, "s")

        # Add abbreviations so that things can be saved just by giving
        # corresponding ID strings.
        # Example: hysteresis(..., save=[('averages', ...)])
        self.add_save_abbrev('save_averages',
                             lambda sim: sim.save_data(avoid_same_step=True))
        self.add_save_abbrev('save_fields',
                             lambda sim: sim.save_data(fields='all',
                                                       avoid_same_step=True))
        self.add_save_abbrev('save_field_m',
                             lambda sim: sim.save_data(fields=['m'],
                                                       avoid_same_step=True))
        self.add_save_abbrev('save_restart',
                             lambda sim: sim.save_restart_file())
        self.add_do_abbrev('do_next_stage',
                           SimulationCore.hysteresis_next_stage)
        self.add_do_abbrev('do_exit', SimulationCore.hysteresis_exit)

        # Used to write the ndt file
        self._ndt_writer = ColWriter(out=self._ndtfilename())
        self._ndt_writer.set_formats([( 'float', '% 25.13g'),
                                      (   'int', '% 25d'),
                                      (  'date', '% 25s'),
                                      ('pfield', '% 25.13g'),
                                      ( 'field', '% 25.13g')])

        # The following list contains a description of the physics components
        # which are included in the physical model For example,
        # ["exch", "demag"] indicates that exchange and demag are included.
        # In this case, spin transfer torque is not. This information
        # is used to understand which fields are relevant and which are not
        # (so that we do not save empty fields). Following the previous
        # example, dm_dcurrent, current_density won't be saved.
        self._components = None
Пример #2
0
    def __init__(self,
                 name=None,
                 do_demag=True,
                 id="Generic Simulation class"):
        self.class_id = id  # String identifying the kind of Simulation
        # class
        self.units = None  # Simulation units used by this class
        self.do_demag = do_demag  # Whether we should include the demag field

        # List of all the materials used by the Simulation object
        self.materials = None

        # Dictionary used by the hysteresis method to find abbreviations for
        # frequently used things to save or do.
        # Example: for ``sim.hysteresis(..., save=[('averages', at(...))])``
        # the string 'averages' needs to be a key in this dictionary.
        # The corresponding value is the function to call.
        self.action_abbreviations = {}

        # Every quantity the user may want to save needs to be listed here
        self.known_quantities = known_quantities
        self.known_quantities_by_name = known_quantities_by_name
        self.known_field_quantities = known_field_quantities

        ### Set the simulation name
        if name == None:
            self.name = features.get('etc', 'runid')
        else:
            self.name = name
        log.info("Simulation(name=%s) object created" % self.name)

        ### Check whether the files we are going to write do already exist.
        # if this is the case we should stop, unless the --clean option was
        # given: we do not want to overwrite data as a default!
        self._restarting = False
        data_filenames = [
            self._ndtfilename(),
            self._h5filename(),
            self._tolfilename()
        ]
        if features.get('nmag', 'clean', raw=True):
            # Existing data files should be deleted
            nsim.snippets.rename_old_files(data_filenames)

        elif features.get('nmag', 'restart', raw=True):
            log.info("Starting simulation in restart mode...")
            self._restarting = True

        else:
            # Check that no data files exist
            for filename in data_filenames:
                if os.path.exists(filename):
                    msg = ("Error: Found old file %s -- cannot proceed. "
                           "To start a simulation script with old data "
                           "files present you either need to use '--clean' "
                           "(and then the old files will be deleted), "
                           "or use '--restart' in which case the run "
                           "will be continued." % filename)
                    raise NmagUserError(msg)

        # See documentation for SimulationClock object
        self.clock = SimulationClock()

        # The advance_time method does not allow to carry on the simulation
        # up to t = infinite. Sometimes we want to simulate for n steps,
        # without any time limits. However we must give a time limit.
        # This is then how we approximate t = infinite.
        # For now, we do not provide any function to set or change it.
        # The user should just use:
        #   sim = Simulation()
        #   sim.max_time_reached = SI(1000, "s")
        self.max_time_reached = SI(1, "s")

        # Add abbreviations so that things can be saved just by giving
        # corresponding ID strings.
        # Example: hysteresis(..., save=[('averages', ...)])
        self.add_save_abbrev('save_averages',
                             lambda sim: sim.save_data(avoid_same_step=True))
        self.add_save_abbrev(
            'save_fields',
            lambda sim: sim.save_data(fields='all', avoid_same_step=True))
        self.add_save_abbrev(
            'save_field_m',
            lambda sim: sim.save_data(fields=['m'], avoid_same_step=True))
        self.add_save_abbrev('save_restart',
                             lambda sim: sim.save_restart_file())
        self.add_do_abbrev('do_next_stage',
                           SimulationCore.hysteresis_next_stage)
        self.add_do_abbrev('do_exit', SimulationCore.hysteresis_exit)

        # Used to write the ndt file
        self._ndt_writer = ColWriter(out=self._ndtfilename())
        self._ndt_writer.set_formats([('float', '% 25.13g'), ('int', '% 25d'),
                                      ('date', '% 25s'),
                                      ('pfield', '% 25.13g'),
                                      ('field', '% 25.13g')])

        # The following list contains a description of the physics components
        # which are included in the physical model For example,
        # ["exch", "demag"] indicates that exchange and demag are included.
        # In this case, spin transfer torque is not. This information
        # is used to understand which fields are relevant and which are not
        # (so that we do not save empty fields). Following the previous
        # example, dm_dcurrent, current_density won't be saved.
        self._components = None
Пример #3
0
class SimulationCore(object):
    def __init__(self, name=None, do_demag=True,
                 id="Generic Simulation class"):
        self.class_id = id       # String identifying the kind of Simulation
                                 # class
        self.units = None        # Simulation units used by this class
        self.do_demag = do_demag # Whether we should include the demag field

        # List of all the materials used by the Simulation object
        self.materials = None

        # Dictionary used by the hysteresis method to find abbreviations for
        # frequently used things to save or do.
        # Example: for ``sim.hysteresis(..., save=[('averages', at(...))])``
        # the string 'averages' needs to be a key in this dictionary.
        # The corresponding value is the function to call.
        self.action_abbreviations = {}

        # Every quantity the user may want to save needs to be listed here
        self.known_quantities = known_quantities
        self.known_quantities_by_name = known_quantities_by_name
        self.known_field_quantities = known_field_quantities

        ### Set the simulation name
        if name == None:
            self.name = features.get('etc', 'runid')
        else:
            self.name = name
        log.info("Simulation(name=%s) object created" % self.name)

        ### Check whether the files we are going to write do already exist.
        # if this is the case we should stop, unless the --clean option was
        # given: we do not want to overwrite data as a default!
        self._restarting = False
        data_filenames = [self._ndtfilename(), self._h5filename(),
                          self._tolfilename()]
        if features.get('nmag', 'clean', raw=True):
            # Existing data files should be deleted
            nsim.snippets.rename_old_files(data_filenames)

        elif features.get('nmag', 'restart', raw=True):
            log.info("Starting simulation in restart mode...")
            self._restarting = True

        else:
            # Check that no data files exist
            for filename in data_filenames:
                if os.path.exists(filename):
                    msg = ("Error: Found old file %s -- cannot proceed. "
                           "To start a simulation script with old data "
                           "files present you either need to use '--clean' "
                           "(and then the old files will be deleted), "
                           "or use '--restart' in which case the run "
                           "will be continued." % filename)
                    raise NmagUserError(msg)

        # See documentation for SimulationClock object
        self.clock = SimulationClock()

        # The advance_time method does not allow to carry on the simulation
        # up to t = infinite. Sometimes we want to simulate for n steps,
        # without any time limits. However we must give a time limit.
        # This is then how we approximate t = infinite.
        # For now, we do not provide any function to set or change it.
        # The user should just use:
        #   sim = Simulation()
        #   sim.max_time_reached = SI(1000, "s")
        self.max_time_reached = SI(1, "s")

        # Add abbreviations so that things can be saved just by giving
        # corresponding ID strings.
        # Example: hysteresis(..., save=[('averages', ...)])
        self.add_save_abbrev('save_averages',
                             lambda sim: sim.save_data(avoid_same_step=True))
        self.add_save_abbrev('save_fields',
                             lambda sim: sim.save_data(fields='all',
                                                       avoid_same_step=True))
        self.add_save_abbrev('save_field_m',
                             lambda sim: sim.save_data(fields=['m'],
                                                       avoid_same_step=True))
        self.add_save_abbrev('save_restart',
                             lambda sim: sim.save_restart_file())
        self.add_do_abbrev('do_next_stage',
                           SimulationCore.hysteresis_next_stage)
        self.add_do_abbrev('do_exit', SimulationCore.hysteresis_exit)

        # Used to write the ndt file
        self._ndt_writer = ColWriter(out=self._ndtfilename())
        self._ndt_writer.set_formats([( 'float', '% 25.13g'),
                                      (   'int', '% 25d'),
                                      (  'date', '% 25s'),
                                      ('pfield', '% 25.13g'),
                                      ( 'field', '% 25.13g')])

        # The following list contains a description of the physics components
        # which are included in the physical model For example,
        # ["exch", "demag"] indicates that exchange and demag are included.
        # In this case, spin transfer torque is not. This information
        # is used to understand which fields are relevant and which are not
        # (so that we do not save empty fields). Following the previous
        # example, dm_dcurrent, current_density won't be saved.
        self._components = None

    def _get_id(self): return self.clock.id
    def _get_stage(self): return self.clock.stage
    def _get_step(self): return self.clock.step
    def _get_time(self): return self.clock.time
    def _get_stage_step(self): return self.clock.stage_step
    def _get_stage_time(self): return self.clock.stage_time
    def _get_real_time(self): return self.clock.real_time

    id = property(_get_id, doc="ID.")
    stage = property(_get_stage, doc="Stage number.")
    step = property(_get_step, doc="Global step number (always increases).")
    time = property(_get_time, doc="Global time reached (always increases).")
    stage_step = property(_get_stage_step,
                           doc="Step number counted from the beginning "
                               "of the current stage.")
    stage_time = property(_get_stage_time,
                          doc="Time reached counted from the beginning "
                              "of the current stage.")
    real_time = property(_get_real_time,
                         doc="Time passed in the 'real' world.")

    def get_components(self):
        if self._components != None:
            return self._components

        else:
            components = ["exch"]
            if self.do_demag:
                components.append("demag")
            self._components = components
            return components

    components = property(get_components, doc="Get the physical components "
                                              "included in the model.")

    def get_all_field_names(self):
        return [q.name for q in self.known_field_quantities
                if q.context == None
                or q.context in self.components]

    def hysteresis_next_stage(sim):
        """Terminate the current stage of the hysteresis computation
        and start the next one. This function is intended to be called
        by one of the functions passed to ``hysteresis`` using the ``save``
        or ``do`` optional arguments).
        """
        sim.clock.stage_end = True

    def hysteresis_exit(sim):
        """Exit from the running hysteresis computation. This function can be
        used to exit from an hysteresis loop computation and is intended to be
        called by one of the functions passed to ``hysteresis`` using the
        ``save`` or ``do`` optional arguments).
        """
        sim.clock.exit_hysteresis = True
        sim.clock.stage_end = True

    # The methods 'hysteresis', 'relax', 'hysteresis_next_stage' and
    # 'hysteresis_exit' of the class 'Simulation' are defined inside a separate
    # module: this helps to keep the sources cleaner.
    relax = hysteresis_m.simulation_relax

    hysteresis = hysteresis_m.simulation_hysteresis

    hysteresis.__argdoclong__ = \
      hysteresis_m.simulation_hysteresis.__argdoclong__


    def add_action_abbrev(self, abbreviation, function, prefix=None):
        if prefix == None:
            self.action_abbreviations[abbreviation] = function
            return

        else:
            valid_prefixes = ["save", "do"]
            if not (prefix in valid_prefixes):
                raise NmagUserError("Valid prefixes for action abbreviations "
                                    "are %s, you gave '%s'!"
                                    % (valid_prefixes, prefix))

            if abbreviation.startswith(prefix):
                self.action_abbreviations[abbreviation] = function
            else:
                full_abbreviation = prefix + '_' + abbreviation
                self.action_abbreviations[full_abbreviation] = function

    def add_save_abbrev(self, abbreviation, function):
        '''Add an abbreviation to be used in the 'save' argument of the
        hysteresis method. For example, if you use the following:

            def funky_function(sim): print "Hello, I'm Funky!"
            sim.add_save_abbrev('funky', funky_function)

        Then you can call:

            sim.hysteresis(Hs, save=[('funky', at('convergence'))])

        and this will be equivalent to:

            sim.hysteresis(Hs, save=[(funky_function, at('convergence')])
        '''
        self.add_action_abbrev(abbreviation, function, prefix='save')

    def add_do_abbrev(self, abbreviation, function):
        '''Similar to add_save_abbrev, but affects the 'do' argument of the
        hysteresis method.'''
        self.add_action_abbrev(abbreviation, function, prefix='do')

    def do_next_stage(self, stage=None):
        self.clock.inc_stage(stage=stage)

    def _get_filename(self, ext):
        basename = self.name + ext
        return nsim.snippets.output_file_location(basename)

    def _ndtfilename(self):
        return self._get_filename("_dat.ndt")

    def _h5filename(self):
        return self._get_filename("_dat.h5")

    def _statfilename(self):
        return self._get_filename("_cvode.log")

    def _tolfilename(self):
        return self._get_filename("_tol.log")

    def get_restart_file_name(self):
        """Return the default name for the restart file."""
        return self.name + "_restart.h5"

    def is_converged(self):
        """
        Returns True when convergence has been reached.
        """
        return self.clock.convergence

    def get_materials_of_field(self, field_name):
        '''Returns all the materials for which the field with the given name
        is defined. Returns None if the field is not a per-material defined
        field.'''

        quantity = self.known_quantities_by_name[field_name]
        if '?' in quantity.signature:
            return self.materials
        else:
            return None

    def get_ndt_columns(self):
        '''This function returns the data that normally should go into the NDT
        file together with a corresponding description (a Quantity object).
        The function returns a pair (columns, quantities), where:

        - columns is a list of pairs (data_name, data_value), where data_name
          is the name of the data (such as 'time' or 'M_Py_0'), while
          data_value is the corresponding value including the units, as an SI
          object, if possible. such as SI(1e6, 'A/m').

        - quantities is a list having the same size of ``columns``.
          quantities[i] is a Quantity object describing the entry columns[i].
        '''
        # These quantities are not dependent on the discretisation
        lt = time.localtime()
        lt_str = ("%04d/%02d/%02d-%02d:%02d:%02d"
                  % (lt[0], lt[1], lt[2], lt[3], lt[4], lt[5]))

        columns = [('id', self.id),
                   ('step', self.step),
                   ('stage_step', self.stage_step),
                   ('stage', self.stage),
                   ('time', self.time),
                   ('stage_time', self.stage_time),
                   ('real_time', self.real_time),
                   ('unixtime', SI(time.time(), 's')),
                   ('localtime', lt_str)]

        quantities = [self.known_quantities_by_name[name]
                      for name, _ in columns]

        # Now we need to add the averages of all fields

        # This function appends to columns the averages for each component
        # of the specified subfield.
        def process_subfield(field_name, prefix, quantity, mat_name=None):
            if True:
                avg = self.get_subfield_average(field_name, mat_name)
                if avg == None:
                    return
            else: # except:
                return

            if type(avg) == list:
                for i, comp_value in enumerate(avg):
                    comp_name = "%s_%s" % (prefix, i)
                    columns.append((comp_name, comp_value))
                    quantities.append(quantity.sub_quantity(comp_name))

            else:
                columns.append((prefix, avg))
                quantities.append(quantity.sub_quantity(prefix))

        # Loop over all the fields and add averages for all their components
        for quantity in self.known_quantities:
            field_name = quantity.name
            if quantity.type in ['field', 'pfield']:
                if '?' in quantity.signature:
                    for material in self.get_materials_of_field(field_name):
                        prefix = "%s_%s" % (field_name, material.name)
                        process_subfield(field_name, prefix, quantity,
                                         mat_name=material.name)
                else:
                    process_subfield(field_name, field_name, quantity)

        return (columns, quantities)

    def _save_fields(self, filename=None, fieldnames=[]):
        self._raise_not_implemented("load_m_from_file")

    def _save_averages(self, fields=None, avoid_same_step=False):
        '''Save the averages of all available fields into the NDT file.
        If fields is a list of fields name, then these fields will be saved
        into the h5 file. If fields == 'all', then all the available fields
        will be saved in the h5 file.'''

        # Get the data
        columns, quantities = self.get_ndt_columns()

        # Define the columns available for output in the _ndt_writer object
        # (if this wasn't done before)
        if not self._ndt_writer.has_columns():
            # Check that all columns correspond to known quantities
            for quantity in quantities:
                if not (quantity in self.known_quantities
                        or quantity.parent in self.known_quantities):
                    raise NmagInternalError("The quantity '%s' is not listed "
                                            "as a known quantity and cannot "
                                            "be saved!" % col_name)

            # Define a column for every available data: note that the first
            # write determines which columns are available and which are not!
            # (this means that a column cannot become available later)
            for kq in self.known_quantities:
                selected = [q for q in quantities
                              if q == kq or q.parent == kq]
                for quantity in selected:
                    units = quantity.units
                    units_str = None
                    if units != None:
                        units_str = units.dens_str()
                    column_desc = ColDescriptor(name=quantity.name,
                                                type=quantity.type,
                                                units=units,
                                                units_str=units_str)
                    self._ndt_writer.define_column(column_desc)

        # Write finally the data to file
        self._ndt_writer.write_row(columns)

    def save_data(self, fields=None, avoid_same_step=False):
        """
        Save the *averages* of all defined (subfields) into a ascii
        data file. The filename is composed of the simulation name
        and the extension ``_dat.ndt``. The
        extension ``ndt`` stands for Nmag Data Table (analog to OOMMFs
        ``.odt`` extension for this kind of data file.

        If ``fields`` is provided, then it will also save the spatially
        resolved fields to a file with extensions ``_dat.h5``.

        :Parameters:
          `fields` : None, 'all' or list of fieldnames

            If None, then only spatially averaged data is saved into ``*ndt``
            and ``*h5`` files.

            If ``all`` (i.e. the string containing 'all'), then all fields are
            saved.

            If a list of fieldnames is given, then only the selected
            fieldnames will be saved (i.e. ['m', 'H_demag']).

          `avoid_same_step` : bool

            If ``True``, then the data will only be saved if the
            current ``clock.step`` counter is different from the
            step counter of the last saved configuration. If
            ``False``, then the data will be saved in any
            case. Default is ```False```. This is internally used by
            the hysteresis command (which uses ``avoid_same_step ==
            True``) to avoid saving the same data twice.

            The only situation where the step counter may not have
            changed from the last saved configuration is if the user
            is modifying the magnetisation or external field manually
            (otherwise the call of the time integrator to advance or
            relax the system will automatically increase the step
            counter).

        """
        self._save_averages(fields=fields, avoid_same_step=avoid_same_step)

    def _raise_not_implemented(self, fn_name):
        #raise NotImplementedError("%s is not implemented by %s"
        #                          % (fn_name, self.class_id))
        print "%s is not implemented by %s" % (fn_name, self.class_id)

    def save_mesh(self, filename):
        self._raise_not_implemented("save_mesh")

    def load_mesh(self, filename, region_names_and_mag_mats, unit_length,
                  do_reorder=False, manual_distribution=None):
        self._raise_not_implemented("load_mesh")

    def create_mesh(self, cell_nums, cell_sizes, materials,
                    regions=None, origin=(0.0, 0.0, 0.0),):
        """Specify the FD mesh to use.

        - ``cell_nums`` if a list containing the number of cells per each
          dimension.

        - ``cell_sizes`` is a list the sizes of the cells for each dimension
          (the FD mesh is made by cells having all the same size).

        - ``materials`` is the material (in case of single material
          simulation) or a list of tuples (region, material), in case of
          multi-material simulation.

        - ``regions`` is a function which takes the coordinates of a point as
          input and returns the region to which the point belongs to
          (a string). If ``regions`` is not given, then single-material
          simulation is assumed and the region in which the magnetisation
          is defined is assumed to be the whole mesh space (a cubic sample
          is simulated).

        - ``origin`` is the coordinate of the first cell, all the other cells
          occupy the cubic region of space whose corners are ``origin`` itself
          and ``origin[i] + cell_size[i]*cell_nums[i]`` for running index i.
        """
        self._raise_not_implemented("create_mesh")

    def set_params(self, stopping_dm_dt=None,
                   ts_rel_tol=None, ts_abs_tol=None):
        """
        Set the parameters which control the accuracy and performance
        of the simulation.

        :Parameters:

          `ts_rel_tol` : float
            the relative error tolerance (default is 1e-6) for the timestepper

          `ts_abs_tol` : float
            the absolute error tolerance (default is 1e-6) for the timestepper

          `stopping_dm_dt` : SI_ object
            the value used in the hysteresis_ and relax_ functions to decide
            whether convergence has been reached. If the largest value for dm/dt drops
            below ``stopping_dm_dt``, then convergence has been reached.

            The default value for ``stopping_dm_dt`` this is that the
            magnetisation changes less than one degree per nanosecond,
            i.e. ``stopping_dm_dt = SI(17453292.519943293,['s',-1])``.

          `exact_tstop` : bool
            the value of exact_tstop which is used by the advance_time method
            when the optional argument is not given. This is also the value
            used by the relax and hysteresis methods. See the documentation
            of advance_time for further details.

        Note that this command has to be issued *after* having created
        an m-field with the set_m_ command.
        """
        self._raise_not_implemented("set_params")

    def reinitialise(self, initial_time=None):
        self._raise_not_implemented("reinitialise")

    def set_local_magnetic_coupling(self, mat1, mat2, coupling):
        self._raise_not_implemented("set_local_magnetic_coupling")

    def set_H_ext(self, values, unit=None):
        self._raise_not_implemented("set_H_ext")

    def set_m(self, values, subfieldname=None):
        self._raise_not_implemented("set_m")

    def set_pinning(self, values):
        self._raise_not_implemented("set_pinning")

    def set_current_density(self, values, unit=None):
        self._raise_not_implemented("set_current_density")

    def advance_time(self, target_time, max_it=-1, exact_tstop=None):
        """
        This method carries out the time integration of the
        Landau-Lifshitz and Gilbert equation.

        :Parameters:

          `target_time` : SI Object
            The simulation will run until this time is reached. If the target_time is
            zero, this will simply update all fields.

          `max_it` : integer
            The maximum number of iterations (steps) to be carried out
            in this time integration call. If set to ``-1``, then there is no limit.

          `exact_tstop` : boolean
            When exact_tstop is True, the time integration is advanced exactly
            up to the given target_time. When False, the time integration ends
            "close" to the target_time. The latter option can result in better
            performance, since the time integrator is free to choose time
            steps which are as wide as possible. When exact_tstop is not
            given, or is None, the default value for this option will be used.
            The default value can be set using the method set_params, which
            should hence be used to control the behaviour of the hysteresis
            and relax methods.
        """
        self._raise_not_implemented("advance_time")

    def save_restart_file(self, filename=None, fieldnames=['m'], all=False):
        self._raise_not_implemented("save_restart_file")

    def save_m_to_file(self, filename, format=None):
        '''Save the magnetisation to file, so that it can be reloaded later
        with the method load_m_from_file. ``format`` is a string specifying
        the format to be used. For example, ``format='h5'`` will produce an h5
        file, while ``format='omf'`` will produce an OMF file (OOMMF).
        If format is not given, then the file format will be deduced
        from the extension or, if no extension is used, the predefined file
        format will be used.
        '''
        self._raise_not_implemented("save_m_to_file")

    def load_m_from_file(self, filename, format=None):
        '''Load a magnetisation configuration stored in the file with the
        given name. Read the documentation of the method ``save_m_to_file``
        for more information.'''
        self._raise_not_implemented("load_m_from_file")

    def probe_subfield(self,subfieldname,pos,unit=None):
        """for a given subfield name and position (SI object), return data (as SI object).

        Note that ``get_subfield_siv`` has the same functionality but
        takes a list of floats for the position (instead of an SI
        object) and returns (a list of) float(s) which is just the
        :ref:`SI-value <SI object>` of that physical entity.

        If the subfield is not defined at that part of space, ``None``
        is returned.

        If the subfield does generally not exist, then a ``KeyError`` exception
        is thrown.

        :Parameters:
          `subfieldname` : string
            The name of the subfield

          `pos` : SI object
            The position for which the data should be returned

          `unit` : SI object
            If you request the value for a subfield of a field that is
            part of |nmag| (i.e. fields M, m, H_demag, etc), then you do
            not need to provide this object.

            If you request data of any other (multi-physics) fields,
            then this function needs to know the SI dimensions of that
            field (for the correct conversion from simulation units to
            SI units).

            If incorrect dimensions are provided, the returned data is
            likely to be wrongly scaled.

        :Returns:
          `data` : [list [of list[ of ...]]] SI objects
            The returned object is an SI object for scalar subfields,
            a list of SI objects for vector fields, a list of list of
            SI objects for (rank 2) tensor fields, etc.

        """
        self._raise_not_implemented("probe_subfield")

    def probe_subfield_siv(self, subfieldname, pos, unit=None):
        """
        The same behaviour as ``get_subfield`` but the ``pos`` and return
        data are :ref:`SI-value <SI object>`\ s (not SI objects).

        If the subfield is not defined at that part of space, ``None``
        is returned.

        If the subfield does generally not exist, then a ``KeyError``
        exception is thrown.

        The input (position) and returned data is expressed in SI
        units but of type float.

        :Parameters:
          `subfieldname` : string
            The name of the subfield

          `pos` : list of floats
            The position for which the data should be returned (in meters)

          `unit` : SI object
            If you request the value for a subfield of a field that is part of nmag (i.e.
            fields M, m, H_demag, etc), then you do not need to provide this object.

            If you request data of any other (multi-physics) fields,
            then this function needs to know the SI dimensions of that
            field (for the correct conversion from simulation units to
            SI units).

            If incorrect dimensions are provided, the returned data is
            likely to be wrongly scaled.

        :Returns:
          `data` : [list [of list[ of ...]]] float
            The returned object is a float for scalar subfields,
            a list of floats for vector fields, a list of list of
            floats for (rank 2) tensor fields, etc.

        """
        self._raise_not_implemented("probe_subfield_siv")

    def get_subfield(self, subfieldname, units=None):
        """
        Given a subfieldname, this will return a numpy-array
        containing all the data (one element for each site).

        :Parameters:
          `subfieldname` : string
            The name of the subfield, for example ``m_Py`` or ``H_demag``.

          `units` : SI object

            Optional parameter. If it is provided, then the entity is
            expressed in these units. If it is not provided, then the
            correct SI dimensions for this subfield are looked up, and
            :ref:`SI-value <SI object>`\ s are returned.

            If you would like to see simulation units in the output,
            then use ``SI(1)``.

            In short: if you omit the second parameter, you will
            obtain SI values.

        :Returns:
          `data` : numpy-array

        """
        self._raise_not_implemented("get_subfield")
Пример #4
0
class SimulationCore(object):
    def __init__(self,
                 name=None,
                 do_demag=True,
                 id="Generic Simulation class"):
        self.class_id = id  # String identifying the kind of Simulation
        # class
        self.units = None  # Simulation units used by this class
        self.do_demag = do_demag  # Whether we should include the demag field

        # List of all the materials used by the Simulation object
        self.materials = None

        # Dictionary used by the hysteresis method to find abbreviations for
        # frequently used things to save or do.
        # Example: for ``sim.hysteresis(..., save=[('averages', at(...))])``
        # the string 'averages' needs to be a key in this dictionary.
        # The corresponding value is the function to call.
        self.action_abbreviations = {}

        # Every quantity the user may want to save needs to be listed here
        self.known_quantities = known_quantities
        self.known_quantities_by_name = known_quantities_by_name
        self.known_field_quantities = known_field_quantities

        ### Set the simulation name
        if name == None:
            self.name = features.get('etc', 'runid')
        else:
            self.name = name
        log.info("Simulation(name=%s) object created" % self.name)

        ### Check whether the files we are going to write do already exist.
        # if this is the case we should stop, unless the --clean option was
        # given: we do not want to overwrite data as a default!
        self._restarting = False
        data_filenames = [
            self._ndtfilename(),
            self._h5filename(),
            self._tolfilename()
        ]
        if features.get('nmag', 'clean', raw=True):
            # Existing data files should be deleted
            nsim.snippets.rename_old_files(data_filenames)

        elif features.get('nmag', 'restart', raw=True):
            log.info("Starting simulation in restart mode...")
            self._restarting = True

        else:
            # Check that no data files exist
            for filename in data_filenames:
                if os.path.exists(filename):
                    msg = ("Error: Found old file %s -- cannot proceed. "
                           "To start a simulation script with old data "
                           "files present you either need to use '--clean' "
                           "(and then the old files will be deleted), "
                           "or use '--restart' in which case the run "
                           "will be continued." % filename)
                    raise NmagUserError(msg)

        # See documentation for SimulationClock object
        self.clock = SimulationClock()

        # The advance_time method does not allow to carry on the simulation
        # up to t = infinite. Sometimes we want to simulate for n steps,
        # without any time limits. However we must give a time limit.
        # This is then how we approximate t = infinite.
        # For now, we do not provide any function to set or change it.
        # The user should just use:
        #   sim = Simulation()
        #   sim.max_time_reached = SI(1000, "s")
        self.max_time_reached = SI(1, "s")

        # Add abbreviations so that things can be saved just by giving
        # corresponding ID strings.
        # Example: hysteresis(..., save=[('averages', ...)])
        self.add_save_abbrev('save_averages',
                             lambda sim: sim.save_data(avoid_same_step=True))
        self.add_save_abbrev(
            'save_fields',
            lambda sim: sim.save_data(fields='all', avoid_same_step=True))
        self.add_save_abbrev(
            'save_field_m',
            lambda sim: sim.save_data(fields=['m'], avoid_same_step=True))
        self.add_save_abbrev('save_restart',
                             lambda sim: sim.save_restart_file())
        self.add_do_abbrev('do_next_stage',
                           SimulationCore.hysteresis_next_stage)
        self.add_do_abbrev('do_exit', SimulationCore.hysteresis_exit)

        # Used to write the ndt file
        self._ndt_writer = ColWriter(out=self._ndtfilename())
        self._ndt_writer.set_formats([('float', '% 25.13g'), ('int', '% 25d'),
                                      ('date', '% 25s'),
                                      ('pfield', '% 25.13g'),
                                      ('field', '% 25.13g')])

        # The following list contains a description of the physics components
        # which are included in the physical model For example,
        # ["exch", "demag"] indicates that exchange and demag are included.
        # In this case, spin transfer torque is not. This information
        # is used to understand which fields are relevant and which are not
        # (so that we do not save empty fields). Following the previous
        # example, dm_dcurrent, current_density won't be saved.
        self._components = None

    def _get_id(self):
        return self.clock.id

    def _get_stage(self):
        return self.clock.stage

    def _get_step(self):
        return self.clock.step

    def _get_time(self):
        return self.clock.time

    def _get_stage_step(self):
        return self.clock.stage_step

    def _get_stage_time(self):
        return self.clock.stage_time

    def _get_real_time(self):
        return self.clock.real_time

    id = property(_get_id, doc="ID.")
    stage = property(_get_stage, doc="Stage number.")
    step = property(_get_step, doc="Global step number (always increases).")
    time = property(_get_time, doc="Global time reached (always increases).")
    stage_step = property(_get_stage_step,
                          doc="Step number counted from the beginning "
                          "of the current stage.")
    stage_time = property(_get_stage_time,
                          doc="Time reached counted from the beginning "
                          "of the current stage.")
    real_time = property(_get_real_time,
                         doc="Time passed in the 'real' world.")

    def get_components(self):
        if self._components != None:
            return self._components

        else:
            components = ["exch"]
            if self.do_demag:
                components.append("demag")
            self._components = components
            return components

    components = property(get_components,
                          doc="Get the physical components "
                          "included in the model.")

    def get_all_field_names(self):
        return [
            q.name for q in self.known_field_quantities
            if q.context == None or q.context in self.components
        ]

    def hysteresis_next_stage(sim):
        """Terminate the current stage of the hysteresis computation
        and start the next one. This function is intended to be called
        by one of the functions passed to ``hysteresis`` using the ``save``
        or ``do`` optional arguments).
        """
        sim.clock.stage_end = True

    def hysteresis_exit(sim):
        """Exit from the running hysteresis computation. This function can be
        used to exit from an hysteresis loop computation and is intended to be
        called by one of the functions passed to ``hysteresis`` using the
        ``save`` or ``do`` optional arguments).
        """
        sim.clock.exit_hysteresis = True
        sim.clock.stage_end = True

    # The methods 'hysteresis', 'relax', 'hysteresis_next_stage' and
    # 'hysteresis_exit' of the class 'Simulation' are defined inside a separate
    # module: this helps to keep the sources cleaner.
    relax = hysteresis_m.simulation_relax

    hysteresis = hysteresis_m.simulation_hysteresis

    hysteresis.__argdoclong__ = \
      hysteresis_m.simulation_hysteresis.__argdoclong__

    def add_action_abbrev(self, abbreviation, function, prefix=None):
        if prefix == None:
            self.action_abbreviations[abbreviation] = function
            return

        else:
            valid_prefixes = ["save", "do"]
            if not (prefix in valid_prefixes):
                raise NmagUserError("Valid prefixes for action abbreviations "
                                    "are %s, you gave '%s'!" %
                                    (valid_prefixes, prefix))

            if abbreviation.startswith(prefix):
                self.action_abbreviations[abbreviation] = function
            else:
                full_abbreviation = prefix + '_' + abbreviation
                self.action_abbreviations[full_abbreviation] = function

    def add_save_abbrev(self, abbreviation, function):
        '''Add an abbreviation to be used in the 'save' argument of the
        hysteresis method. For example, if you use the following:

            def funky_function(sim): print "Hello, I'm Funky!"
            sim.add_save_abbrev('funky', funky_function)

        Then you can call:

            sim.hysteresis(Hs, save=[('funky', at('convergence'))])

        and this will be equivalent to:

            sim.hysteresis(Hs, save=[(funky_function, at('convergence')])
        '''
        self.add_action_abbrev(abbreviation, function, prefix='save')

    def add_do_abbrev(self, abbreviation, function):
        '''Similar to add_save_abbrev, but affects the 'do' argument of the
        hysteresis method.'''
        self.add_action_abbrev(abbreviation, function, prefix='do')

    def do_next_stage(self, stage=None):
        self.clock.inc_stage(stage=stage)

    def _get_filename(self, ext):
        basename = self.name + ext
        return nsim.snippets.output_file_location(basename)

    def _ndtfilename(self):
        return self._get_filename("_dat.ndt")

    def _h5filename(self):
        return self._get_filename("_dat.h5")

    def _statfilename(self):
        return self._get_filename("_cvode.log")

    def _tolfilename(self):
        return self._get_filename("_tol.log")

    def get_restart_file_name(self):
        """Return the default name for the restart file."""
        return self.name + "_restart.h5"

    def is_converged(self):
        """
        Returns True when convergence has been reached.
        """
        return self.clock.convergence

    def get_materials_of_field(self, field_name):
        '''Returns all the materials for which the field with the given name
        is defined. Returns None if the field is not a per-material defined
        field.'''

        quantity = self.known_quantities_by_name[field_name]
        if '?' in quantity.signature:
            return self.materials
        else:
            return None

    def get_ndt_columns(self):
        '''This function returns the data that normally should go into the NDT
        file together with a corresponding description (a Quantity object).
        The function returns a pair (columns, quantities), where:

        - columns is a list of pairs (data_name, data_value), where data_name
          is the name of the data (such as 'time' or 'M_Py_0'), while
          data_value is the corresponding value including the units, as an SI
          object, if possible. such as SI(1e6, 'A/m').

        - quantities is a list having the same size of ``columns``.
          quantities[i] is a Quantity object describing the entry columns[i].
        '''
        # These quantities are not dependent on the discretisation
        lt = time.localtime()
        lt_str = ("%04d/%02d/%02d-%02d:%02d:%02d" %
                  (lt[0], lt[1], lt[2], lt[3], lt[4], lt[5]))

        columns = [('id', self.id), ('step', self.step),
                   ('stage_step', self.stage_step), ('stage', self.stage),
                   ('time', self.time), ('stage_time', self.stage_time),
                   ('real_time', self.real_time),
                   ('unixtime', SI(time.time(), 's')), ('localtime', lt_str)]

        quantities = [
            self.known_quantities_by_name[name] for name, _ in columns
        ]

        # Now we need to add the averages of all fields

        # This function appends to columns the averages for each component
        # of the specified subfield.
        def process_subfield(field_name, prefix, quantity, mat_name=None):
            if True:
                avg = self.get_subfield_average(field_name, mat_name)
                if avg == None:
                    return
            else:  # except:
                return

            if type(avg) == list:
                for i, comp_value in enumerate(avg):
                    comp_name = "%s_%s" % (prefix, i)
                    columns.append((comp_name, comp_value))
                    quantities.append(quantity.sub_quantity(comp_name))

            else:
                columns.append((prefix, avg))
                quantities.append(quantity.sub_quantity(prefix))

        # Loop over all the fields and add averages for all their components
        for quantity in self.known_quantities:
            field_name = quantity.name
            if quantity.type in ['field', 'pfield']:
                if '?' in quantity.signature:
                    for material in self.get_materials_of_field(field_name):
                        prefix = "%s_%s" % (field_name, material.name)
                        process_subfield(field_name,
                                         prefix,
                                         quantity,
                                         mat_name=material.name)
                else:
                    process_subfield(field_name, field_name, quantity)

        return (columns, quantities)

    def _save_fields(self, filename=None, fieldnames=[]):
        self._raise_not_implemented("load_m_from_file")

    def _save_averages(self, fields=None, avoid_same_step=False):
        '''Save the averages of all available fields into the NDT file.
        If fields is a list of fields name, then these fields will be saved
        into the h5 file. If fields == 'all', then all the available fields
        will be saved in the h5 file.'''

        # Get the data
        columns, quantities = self.get_ndt_columns()

        # Define the columns available for output in the _ndt_writer object
        # (if this wasn't done before)
        if not self._ndt_writer.has_columns():
            # Check that all columns correspond to known quantities
            for quantity in quantities:
                if not (quantity in self.known_quantities
                        or quantity.parent in self.known_quantities):
                    raise NmagInternalError("The quantity '%s' is not listed "
                                            "as a known quantity and cannot "
                                            "be saved!" % col_name)

            # Define a column for every available data: note that the first
            # write determines which columns are available and which are not!
            # (this means that a column cannot become available later)
            for kq in self.known_quantities:
                selected = [q for q in quantities if q == kq or q.parent == kq]
                for quantity in selected:
                    units = quantity.units
                    units_str = None
                    if units != None:
                        units_str = units.dens_str()
                    column_desc = ColDescriptor(name=quantity.name,
                                                type=quantity.type,
                                                units=units,
                                                units_str=units_str)
                    self._ndt_writer.define_column(column_desc)

        # Write finally the data to file
        self._ndt_writer.write_row(columns)

    def save_data(self, fields=None, avoid_same_step=False):
        """
        Save the *averages* of all defined (subfields) into a ascii
        data file. The filename is composed of the simulation name
        and the extension ``_dat.ndt``. The
        extension ``ndt`` stands for Nmag Data Table (analog to OOMMFs
        ``.odt`` extension for this kind of data file.

        If ``fields`` is provided, then it will also save the spatially
        resolved fields to a file with extensions ``_dat.h5``.

        :Parameters:
          `fields` : None, 'all' or list of fieldnames

            If None, then only spatially averaged data is saved into ``*ndt``
            and ``*h5`` files.

            If ``all`` (i.e. the string containing 'all'), then all fields are
            saved.

            If a list of fieldnames is given, then only the selected
            fieldnames will be saved (i.e. ['m', 'H_demag']).

          `avoid_same_step` : bool

            If ``True``, then the data will only be saved if the
            current ``clock.step`` counter is different from the
            step counter of the last saved configuration. If
            ``False``, then the data will be saved in any
            case. Default is ```False```. This is internally used by
            the hysteresis command (which uses ``avoid_same_step ==
            True``) to avoid saving the same data twice.

            The only situation where the step counter may not have
            changed from the last saved configuration is if the user
            is modifying the magnetisation or external field manually
            (otherwise the call of the time integrator to advance or
            relax the system will automatically increase the step
            counter).

        """
        self._save_averages(fields=fields, avoid_same_step=avoid_same_step)

    def _raise_not_implemented(self, fn_name):
        #raise NotImplementedError("%s is not implemented by %s"
        #                          % (fn_name, self.class_id))
        print "%s is not implemented by %s" % (fn_name, self.class_id)

    def save_mesh(self, filename):
        self._raise_not_implemented("save_mesh")

    def load_mesh(self,
                  filename,
                  region_names_and_mag_mats,
                  unit_length,
                  do_reorder=False,
                  manual_distribution=None):
        self._raise_not_implemented("load_mesh")

    def create_mesh(
            self,
            cell_nums,
            cell_sizes,
            materials,
            regions=None,
            origin=(0.0, 0.0, 0.0),
    ):
        """Specify the FD mesh to use.

        - ``cell_nums`` if a list containing the number of cells per each
          dimension.

        - ``cell_sizes`` is a list the sizes of the cells for each dimension
          (the FD mesh is made by cells having all the same size).

        - ``materials`` is the material (in case of single material
          simulation) or a list of tuples (region, material), in case of
          multi-material simulation.

        - ``regions`` is a function which takes the coordinates of a point as
          input and returns the region to which the point belongs to
          (a string). If ``regions`` is not given, then single-material
          simulation is assumed and the region in which the magnetisation
          is defined is assumed to be the whole mesh space (a cubic sample
          is simulated).

        - ``origin`` is the coordinate of the first cell, all the other cells
          occupy the cubic region of space whose corners are ``origin`` itself
          and ``origin[i] + cell_size[i]*cell_nums[i]`` for running index i.
        """
        self._raise_not_implemented("create_mesh")

    def set_params(self,
                   stopping_dm_dt=None,
                   ts_rel_tol=None,
                   ts_abs_tol=None):
        """
        Set the parameters which control the accuracy and performance
        of the simulation.

        :Parameters:

          `ts_rel_tol` : float
            the relative error tolerance (default is 1e-6) for the timestepper

          `ts_abs_tol` : float
            the absolute error tolerance (default is 1e-6) for the timestepper

          `stopping_dm_dt` : SI_ object
            the value used in the hysteresis_ and relax_ functions to decide
            whether convergence has been reached. If the largest value for dm/dt drops
            below ``stopping_dm_dt``, then convergence has been reached.

            The default value for ``stopping_dm_dt`` this is that the
            magnetisation changes less than one degree per nanosecond,
            i.e. ``stopping_dm_dt = SI(17453292.519943293,['s',-1])``.

          `exact_tstop` : bool
            the value of exact_tstop which is used by the advance_time method
            when the optional argument is not given. This is also the value
            used by the relax and hysteresis methods. See the documentation
            of advance_time for further details.

        Note that this command has to be issued *after* having created
        an m-field with the set_m_ command.
        """
        self._raise_not_implemented("set_params")

    def reinitialise(self, initial_time=None):
        self._raise_not_implemented("reinitialise")

    def set_local_magnetic_coupling(self, mat1, mat2, coupling):
        self._raise_not_implemented("set_local_magnetic_coupling")

    def set_H_ext(self, values, unit=None):
        self._raise_not_implemented("set_H_ext")

    def set_m(self, values, subfieldname=None):
        self._raise_not_implemented("set_m")

    def set_pinning(self, values):
        self._raise_not_implemented("set_pinning")

    def set_current_density(self, values, unit=None):
        self._raise_not_implemented("set_current_density")

    def advance_time(self, target_time, max_it=-1, exact_tstop=None):
        """
        This method carries out the time integration of the
        Landau-Lifshitz and Gilbert equation.

        :Parameters:

          `target_time` : SI Object
            The simulation will run until this time is reached. If the target_time is
            zero, this will simply update all fields.

          `max_it` : integer
            The maximum number of iterations (steps) to be carried out
            in this time integration call. If set to ``-1``, then there is no limit.

          `exact_tstop` : boolean
            When exact_tstop is True, the time integration is advanced exactly
            up to the given target_time. When False, the time integration ends
            "close" to the target_time. The latter option can result in better
            performance, since the time integrator is free to choose time
            steps which are as wide as possible. When exact_tstop is not
            given, or is None, the default value for this option will be used.
            The default value can be set using the method set_params, which
            should hence be used to control the behaviour of the hysteresis
            and relax methods.
        """
        self._raise_not_implemented("advance_time")

    def save_restart_file(self, filename=None, fieldnames=['m'], all=False):
        self._raise_not_implemented("save_restart_file")

    def save_m_to_file(self, filename, format=None):
        '''Save the magnetisation to file, so that it can be reloaded later
        with the method load_m_from_file. ``format`` is a string specifying
        the format to be used. For example, ``format='h5'`` will produce an h5
        file, while ``format='omf'`` will produce an OMF file (OOMMF).
        If format is not given, then the file format will be deduced
        from the extension or, if no extension is used, the predefined file
        format will be used.
        '''
        self._raise_not_implemented("save_m_to_file")

    def load_m_from_file(self, filename, format=None):
        '''Load a magnetisation configuration stored in the file with the
        given name. Read the documentation of the method ``save_m_to_file``
        for more information.'''
        self._raise_not_implemented("load_m_from_file")

    def probe_subfield(self, subfieldname, pos, unit=None):
        """for a given subfield name and position (SI object), return data (as SI object).

        Note that ``get_subfield_siv`` has the same functionality but
        takes a list of floats for the position (instead of an SI
        object) and returns (a list of) float(s) which is just the
        :ref:`SI-value <SI object>` of that physical entity.

        If the subfield is not defined at that part of space, ``None``
        is returned.

        If the subfield does generally not exist, then a ``KeyError`` exception
        is thrown.

        :Parameters:
          `subfieldname` : string
            The name of the subfield

          `pos` : SI object
            The position for which the data should be returned

          `unit` : SI object
            If you request the value for a subfield of a field that is
            part of |nmag| (i.e. fields M, m, H_demag, etc), then you do
            not need to provide this object.

            If you request data of any other (multi-physics) fields,
            then this function needs to know the SI dimensions of that
            field (for the correct conversion from simulation units to
            SI units).

            If incorrect dimensions are provided, the returned data is
            likely to be wrongly scaled.

        :Returns:
          `data` : [list [of list[ of ...]]] SI objects
            The returned object is an SI object for scalar subfields,
            a list of SI objects for vector fields, a list of list of
            SI objects for (rank 2) tensor fields, etc.

        """
        self._raise_not_implemented("probe_subfield")

    def probe_subfield_siv(self, subfieldname, pos, unit=None):
        """
        The same behaviour as ``get_subfield`` but the ``pos`` and return
        data are :ref:`SI-value <SI object>`\ s (not SI objects).

        If the subfield is not defined at that part of space, ``None``
        is returned.

        If the subfield does generally not exist, then a ``KeyError``
        exception is thrown.

        The input (position) and returned data is expressed in SI
        units but of type float.

        :Parameters:
          `subfieldname` : string
            The name of the subfield

          `pos` : list of floats
            The position for which the data should be returned (in meters)

          `unit` : SI object
            If you request the value for a subfield of a field that is part of nmag (i.e.
            fields M, m, H_demag, etc), then you do not need to provide this object.

            If you request data of any other (multi-physics) fields,
            then this function needs to know the SI dimensions of that
            field (for the correct conversion from simulation units to
            SI units).

            If incorrect dimensions are provided, the returned data is
            likely to be wrongly scaled.

        :Returns:
          `data` : [list [of list[ of ...]]] float
            The returned object is a float for scalar subfields,
            a list of floats for vector fields, a list of list of
            floats for (rank 2) tensor fields, etc.

        """
        self._raise_not_implemented("probe_subfield_siv")

    def get_subfield(self, subfieldname, units=None):
        """
        Given a subfieldname, this will return a numpy-array
        containing all the data (one element for each site).

        :Parameters:
          `subfieldname` : string
            The name of the subfield, for example ``m_Py`` or ``H_demag``.

          `units` : SI object

            Optional parameter. If it is provided, then the entity is
            expressed in these units. If it is not provided, then the
            correct SI dimensions for this subfield are looked up, and
            :ref:`SI-value <SI object>`\ s are returned.

            If you would like to see simulation units in the output,
            then use ``SI(1)``.

            In short: if you omit the second parameter, you will
            obtain SI values.

        :Returns:
          `data` : numpy-array

        """
        self._raise_not_implemented("get_subfield")