From c1cebd107672e8651d4fabac9991e3746493a432 Mon Sep 17 00:00:00 2001 From: Martin Lang Date: Wed, 29 Nov 2023 16:15:25 +0100 Subject: [PATCH 1/2] Add functionality for finding and reading data moved from micromagneticdata and ubermagtable, respectively --- mumax3c/_output_collecting_util/__init__.py | 1 + .../_output_collecting_util/mumax3drive.py | 129 ++++++++++++++++++ mumax3c/_output_collecting_util/read_table.py | 122 +++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 mumax3c/_output_collecting_util/__init__.py create mode 100644 mumax3c/_output_collecting_util/mumax3drive.py create mode 100644 mumax3c/_output_collecting_util/read_table.py diff --git a/mumax3c/_output_collecting_util/__init__.py b/mumax3c/_output_collecting_util/__init__.py new file mode 100644 index 0000000..a62a5cd --- /dev/null +++ b/mumax3c/_output_collecting_util/__init__.py @@ -0,0 +1 @@ +from .mumax3drive import Mumax3Drive as Drive diff --git a/mumax3c/_output_collecting_util/mumax3drive.py b/mumax3c/_output_collecting_util/mumax3drive.py new file mode 100644 index 0000000..fb18257 --- /dev/null +++ b/mumax3c/_output_collecting_util/mumax3drive.py @@ -0,0 +1,129 @@ +import pathlib + +import micromagneticdata as md +import ubermagutil as uu + +from .abstract_drive import AbstractDrive + + +@uu.inherit_docs +class Mumax3Drive(md.Drive): + """Drive class for Mumax3Drives (created automatically). + + This class provides utility for the analysis of individual mumax3 drives. It should + not be created explicitly. Instead, use ``micromagneticdata.Drive`` which + automatically creates a ``drive`` object of the correct sub-type. + + Parameters + ---------- + name : str + + System's name. + + number : int + + Drive number. + + dirname : str, optional + + Directory in which system's data is saved. Defults to ``'./'``. + + x : str, optional + + Independent variable column name. Defaults to ``None`` and depending on + the driver used, one is found automatically. + + use_cache : bool, optional + + If ``True`` the Drive object will read tabular data and the names and number of + magnetisation files only once. Note: this prevents Drive to detect new data when + looking at the output of a running simulation. If set to ``False`` the data is + read every time the user accesses it. Defaults to ``False``. + + Raises + ------ + IOError + + If the drive directory cannot be found. + + Examples + -------- + 1. Getting drive object. + + >>> import os + >>> import micromagneticdata as md + ... + >>> dirname = dirname=os.path.join(os.path.dirname(__file__), + ... 'tests', 'test_sample') + >>> drive = md.Drive(name='system_name', number=1, dirname=dirname) + >>> drive + Mumax3Drive(...) + + """ + + def __init__(self, name, number, dirname="./", x=None, use_cache=False, **kwargs): + self._mumax_output_path = pathlib.Path( + f"{dirname}/{name}/drive-{number}/{name}.out" + ) # required to initialise self.x in super + if not self._mumax_output_path.exists(): + raise IOError( + f"Output directory {self._mumax_output_path!r} does not exist." + ) + + super().__init__(name, number, dirname, x, use_cache, **kwargs) + + @AbstractDrive.x.setter + def x(self, value): + if value is None: + # self.info["driver"] in ["TimeDriver", "RelaxDriver", "MinDriver"]: + self._x = "t" + else: + # self.table reads self.x so self._x has to be defined first + if hasattr(self, "_x"): + # store old value to reset in case value is invalid + _x = self._x + self._x = value + if value not in self.table.data.columns: + self._x = _x + raise ValueError(f"Column {value=} does not exist in data.") + + @property + def _table_path(self): + return self._mumax_output_path / "table.txt" + + @property + def _step_file_glob(self): + return self._mumax_output_path.glob("*.ovf") + + @property + def calculator_script(self): + with (self.drive_path / f"{self.name}.mx3").open() as f: + return f.read() + + def __repr__(self): + """Representation string. + + Returns + ------- + str + + Representation string. + + Examples + -------- + 1. Representation string. + + >>> import os + >>> import micromagneticdata as md + ... + >>> dirname = dirname=os.path.join(os.path.dirname(__file__), + ... 'tests', 'test_sample') + >>> drive = md.Drive(name='system_name', number=1, dirname=dirname) + >>> drive + Mumax3Drive(name='system_name', number=1, dirname='...test_sample', x='t') + + """ + return ( + f"Mumax3Drive(name='{self.name}', number={self.number}, " + f"dirname='{self.dirname}', x='{self.x}')" + ) diff --git a/mumax3c/_output_collecting_util/read_table.py b/mumax3c/_output_collecting_util/read_table.py new file mode 100644 index 0000000..f7725b2 --- /dev/null +++ b/mumax3c/_output_collecting_util/read_table.py @@ -0,0 +1,122 @@ +import re + +import pandas as pd +import ubermagtable + + +def table_from_file(filename, /, x=None, rename=True): + """Convert a mumax3 ``.txt`` scalar data file into a ``ubermagtable.Table``. + + Parameters + ---------- + filename : str + + mumax3 ``.txt`` file. + + x : str, optional + + Independent variable name. Defaults to ``None``. + + rename : bool, optional + + If ``rename=True``, the column names are renamed with their shorter + versions. Defaults to ``True``. + + Returns + ------- + ubermagtable.Table + + Table object. + + TODO: update example + Examples + -------- + 1. Defining ``ubermagtable.Table`` by reading an OOMMF ``.odt`` file. + + >>> import os + >>> import ubermagtable as ut + ... + >>> odtfile = os.path.join(os.path.dirname(__file__), + ... 'tests', 'test_sample', + ... 'oommf-hysteresis1.odt') + >>> table = ut.Table.fromfile(odtfile, x='B_hysteresis') + + 2. Defining ``ubermagtable.Table`` by reading a mumax3 ``.txt`` file. + + >>> odtfile = os.path.join(os.path.dirname(__file__), + ... 'tests', 'test_sample', 'mumax3-file1.txt') + >>> table = ut.Table.fromfile(odtfile, x='t') + + """ + quantities = _read_header(filename, rename=rename) + data = pd.read_csv( + filename, + sep=r"\s+", + comment="#", + header=None, + names=list(quantities.keys()), + ) + return ubermagtable.Table(data=data, units=quantities, x=x) + + +def _read_header(filename, rename=True): + """Extract quantities for individual columns from a table file. + + This method extracts both column names and units and returns a dictionary, + where keys are column names and values are the units. + + Parameters + ---------- + filename : str + + OOMMF ``.odt`` or mumax3 ``.txt`` file. + + rename : bool + + If ``rename=True``, the column names are renamed with their shorter + versions. Defaults to ``True``. + + Returns + ------- + dict + + Dictionary of column names and units. + """ + + with open(filename) as f: + header_line_1 = f.readline() + + header_line_1 = header_line_1[len("# ") :].rstrip().split("\t") + # COLUMN NAMES + cols = [elem.split()[0] for elem in header_line_1] + # UNITS + units = [re.sub(r"[()]", "", elem.split()[1]) for elem in header_line_1] + + if rename: + cols = [_rename_column(col, _MUMAX3_DICT) for col in cols] + + return dict(zip(cols, units)) + + +def _rename_column(name, cols_dict): + """Rename columns to get shorter names without spaces. + + Renaming is based on _MUMAX3_DICT. + """ + return cols_dict.get(name, name) + + +# The mumax3 columns are renamed according to this dictionary. +_MUMAX3_DICT = { + "t": "t", + "mx": "mx", + "my": "my", + "mz": "mz", + "E_total": "E", + "E_exch": "E_totalexchange", + "E_demag": "E_demag", + "E_Zeeman": "E_zeeman", + "E_anis": "E_totalanisotropy", + "dt": "dt", + "maxTorque": "maxtorque", +} From b48aaf3ed2302293b5da4dfdb21e0a8e688c1bac Mon Sep 17 00:00:00 2001 From: Martin Lang Date: Sat, 23 Mar 2024 14:51:39 +0100 Subject: [PATCH 2/2] Implement micromagneticdata.output_collecting entry_points --- mumax3c/_output_collecting_util/__init__.py | 1 - mumax3c/_output_collecting_util/mumax3drive.py | 10 ++++------ pyproject.toml | 5 ++++- 3 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 mumax3c/_output_collecting_util/__init__.py diff --git a/mumax3c/_output_collecting_util/__init__.py b/mumax3c/_output_collecting_util/__init__.py deleted file mode 100644 index a62a5cd..0000000 --- a/mumax3c/_output_collecting_util/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .mumax3drive import Mumax3Drive as Drive diff --git a/mumax3c/_output_collecting_util/mumax3drive.py b/mumax3c/_output_collecting_util/mumax3drive.py index fb18257..720d44f 100644 --- a/mumax3c/_output_collecting_util/mumax3drive.py +++ b/mumax3c/_output_collecting_util/mumax3drive.py @@ -1,14 +1,12 @@ import pathlib -import micromagneticdata as md +import micromagneticdata as mdata import ubermagutil as uu -from .abstract_drive import AbstractDrive - @uu.inherit_docs -class Mumax3Drive(md.Drive): - """Drive class for Mumax3Drives (created automatically). +class Mumax3Drive(mdata.Drive): + """Drive class for Mumax3Drives. This class provides utility for the analysis of individual mumax3 drives. It should not be created explicitly. Instead, use ``micromagneticdata.Drive`` which @@ -72,7 +70,7 @@ def __init__(self, name, number, dirname="./", x=None, use_cache=False, **kwargs super().__init__(name, number, dirname, x, use_cache, **kwargs) - @AbstractDrive.x.setter + @mdata.AbstractDrive.x.setter def x(self, value): if value is None: # self.info["driver"] in ["TimeDriver", "RelaxDriver", "MinDriver"]: diff --git a/pyproject.toml b/pyproject.toml index 803e0a7..a34c944 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,10 @@ homepage = "https://ubermag.github.io" documentation = "https://ubermag.github.io/documentation/mumax3c" repository = "https://github.com/ubermag/mumax3c" - +[project.entry-points."micromagneticdata.output_collecting.Drive"] +mumax3c = "mumax3c._output_collecting_util.mumax3drive:Mumax3Drive" +[project.entry-points."micromagneticdata.output_collecting.read_table"] +mumax3c = "mumax3c._output_collecting_util.read_table:table_from_file" [tool.black] experimental-string-processing = true