Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functionality for finding and reading data #150

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions oommfc/_output_collecting_util/oommfdrive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import micromagneticdata as mdata
import ubermagutil as uu


@uu.inherit_docs
class OOMMFDrive(mdata.Drive):
"""Drive class for OOMMFDrives.

This class provides utility for the analysis of individual OOMMF 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=0, dirname=dirname)

"""

def __init__(self, name, number, dirname="./", x=None, use_cache=False, **kwargs):
super().__init__(name, number, dirname, x, use_cache, **kwargs)

@mdata.AbstractDrive.x.setter
def x(self, value):
if value is None:
if self.info["driver"] == "TimeDriver":
self._x = "t"
elif self.info["driver"] == "MinDriver":
self._x = "iteration"
elif self.info["driver"] == "HysteresisDriver":
self._x = "B_hysteresis"
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.drive_path / f"{self.name}.odt"

@property
def _step_file_glob(self):
return self.drive_path.glob(f"{self.name}*.omf")

@property
def calculator_script(self):
with (self.drive_path / f"{self.name}.mif").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=0, dirname=dirname)
>>> drive
OOMMFDrive(name='system_name', number=0, dirname='...test_sample', x='t')

"""
return (
f"OOMMFDrive(name='{self.name}', number={self.number}, "
f"dirname='{self.dirname}', x='{self.x}')"
)
258 changes: 258 additions & 0 deletions oommfc/_output_collecting_util/read_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
import re

import pandas as pd
import ubermagtable


def table_from_file(filename, /, x=None, rename=True):
"""Convert an OOMMF ``.odt`` scalar data file into a ``ubermagtable.Table``.

Parameters
----------
filename : str

OOMMF ``.odt`` 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`` 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:
# COLUMN NAMES
while not (cline := f.readline()).startswith("# Columns"):
pass
columns = cline.lstrip("# Columns:").rstrip()
# Columns can e.g. look like:
# {Oxs_CGEvolve:evolver:Max mxHxm} {...} Oxs_MinDriver::Stage Oxs_MinDriver::mx
# - the first part of the regex finds column names with spaces inside {}
# - the second part finds column names without spaces and without {}
cols = re.findall(r"(?<={)[^}]+|[^ {}]+", columns)
# UNITS
uline = f.readline()
assert uline.startswith("# Units:")
units = uline.split()[2:] # [2:] to remove ["#", "Units:"]
units = [re.sub(r"[{}]", "", unit) for unit in units]

if rename:
cols = [_rename_column(col, _OOMMF_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 _OOMMF_DICT.
"""
name_split = name.split(":")
try:
group = cols_dict[name_split[0]]
attribute = group[name_split[-1]]
term_name = name_split[1]
if not attribute.endswith(term_name):
# - unique names if the same quantity is present multiple times
# e.g. multiple Zeeman fields
# - also required for changes in the exchange field in "old" and "new"
# OOMMF odt files
attribute = f"{attribute}_{term_name}"
return attribute
except KeyError:
return name


# The OOMMF columns are renamed according to this dictionary.
_OOMMF_DICT = {
"Oxs_RungeKuttaEvolve": {
"Total energy": "E",
"Energy calc count": "E_calc_count",
"Max dm/dt": "max_dm/dt",
"dE/dt": "dE/dt",
"Delta E": "delta_E",
},
"Oxs_EulerEvolve": {
"Total energy": "E",
"Energy calc count": "E_calc_count",
"Max dm/dt": "max_dmdt",
"dE/dt": "dE/dt",
"Delta E": "delta_E",
},
"Oxs_CGEvolve": {
"Max mxHxm": "max_mxHxm",
"Total energy": "E",
"Delta E": "delta_E",
"Bracket count": "bracket_count",
"Line min count": "line_min_count",
"Conjugate cycle count": "conjugate_cycle_count",
"Cycle count": "cycle_count",
"Cycle sub count": "cycle_sub_count",
"Energy calc count": "energy_calc_count",
},
"Anv_SpinTEvolve": {
"Total energy": "E",
"Energy calc count": "E_calc_count",
"Max dm/dt": "max_dmdt",
"dE/dt": "dE/dt",
"Delta E": "delta_E",
"average u": "average_u",
},
"Oxs_SpinXferEvolve": {
"Total energy": "E", # NO SAMPLE
"Energy calc count": "E_calc_count", # NO SAMPLE
"Max dm/dt": "max_dmdt", # NO SAMPLE
"dE/dt": "dE/dt", # NO SAMPLE
"Delta E": "delta_E", # NO SAMPLE
"average u": "average_u", # NO SAMPLE
"average J": "average_J", # NO SAMPLE
},
"UHH_ThetaEvolve": {
"Total energy": "E", # NO SAMPLE
"Energy calc count": "E_calc_count", # NO SAMPLE
"Max dm/dt": "max_dmdt", # NO SAMPLE
"dE/dt": "dE/dt", # NO SAMPLE
"Delta E": "delta_E", # NO SAMPLE
"Temperature": "T", # NO SAMPLE
},
"Xf_ThermHeunEvolve": {
"Total energy": "E", # NO SAMPLE
"Energy calc count": "E_calc_count", # NO SAMPLE
"Max dm/dt": "max_dmdt", # NO SAMPLE
"dE/dt": "dE/dt", # NO SAMPLE
"Delta E": "delta_E", # NO SAMPLE
"Temperature": "T", # NO SAMPLE
},
"Xf_ThermSpinXferEvolve": {
"Total energy": "E", # NO SAMPLE
"Energy calc count": "E_calc_count", # NO SAMPLE
"Max dm/dt": "max_dmdt", # NO SAMPLE
"dE/dt": "dE/dt", # NO SAMPLE
"Delta E": "delta_E", # NO SAMPLE
"Temperature": "T", # NO SAMPLE
},
"Oxs_MinDriver": {
"Iteration": "iteration",
"Stage iteration": "stage_iteration",
"Stage": "stage",
"mx": "mx",
"my": "my",
"mz": "mz",
},
"Oxs_TimeDriver": {
"Iteration": "iteration",
"Stage iteration": "stage_iteration",
"Stage": "stage",
"mx": "mx",
"my": "my",
"mz": "mz",
"Last time step": "last_time_step",
"Simulation time": "t",
},
"Oxs_UniformExchange": {
"Max Spin Ang": "max_spin_ang",
"Stage Max Spin Ang": "stage_max_spin_ang",
"Run Max Spin Ang": "run_max_spin_ang",
"Energy": "E_exchange",
},
"Oxs_Exchange6Ngbr": {
"Energy": "E_exchange6ngbr",
"Max Spin Ang": "max_spin_ang",
"Stage Max Spin Ang": "stage_max_spin_ang",
"Run Max Spin Ang": "run_max_spin_ang",
},
"Oxs_ExchangePtwise": {
"Energy": "E_exchange_ptwise", # NO SAMPLE
"Max Spin Ang": "max_spin_ang", # NO SAMPLE
"Stage Max Spin Ang": "stage_max_spin_ang", # NO SAMPLE
"Run Max Spin Ang": "run_max_spin_ang", # NO SAMPLE
},
"Oxs_TwoSurfaceExchange": {"Energy": "E_two_surface_exchange"}, # NO SAMPLE
"Oxs_Demag": {"Energy": "E_demag"},
"Oxs_DMExchange6Ngbr": {"Energy": "E_DM_exchange6ngbr"}, # NO SAMPLE
"Oxs_DMI_Cnv": {"Energy": "E_DMI_Cnv"}, # TODO: PREFIX
"Oxs_DMI_T": {"Energy": "E_DMI_T"}, # NO SAMPLE, TODO: PREFIX
"Oxs_DMI_D2d": {"Energy": "E_DMI_Dd"}, # NO SAMPLE, TODO: PREFIX
"Oxs_FixedZeeman": {"Energy": "E_zeeman"},
"Oxs_UZeeman": {
"Energy": "E_zeeman",
"B": "B",
"Bx": "Bx",
"By": "By",
"Bz": "Bz",
},
"Oxs_ScriptUZeeman": {
"Energy": "E_zeeman", # NO SAMPLE
"B": "B", # NO SAMPLE
"Bx": "Bx", # NO SAMPLE
"By": "By", # NO SAMPLE
"Bz": "Bz", # NO SAMPLE
},
"Oxs_TransformZeeman": {"Energy": "E_zeeman"}, # NO SAMPLE
"Oxs_CubicAnisotropy": {"Energy": "E_zeeman"},
"Oxs_UniaxialAnisotropy": {"Energy": "E_zeeman"},
"Southampton_UniaxialAnisotropy4": {"Energy": "E_zeeman"}, # NO SAMPLE
"YY_FixedMEL": {"Energy": "MEL_E"},
}
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ homepage = "https://ubermag.github.io"
documentation = "https://ubermag.github.io/documentation/oommfc"
repository = "https://github.com/ubermag/oommfc"


[project.entry-points."micromagneticdata.output_collecting.Drive"]
oommfc = "oommfc._output_collecting_util.oommfdrive:OOMMFDrive"
[project.entry-points."micromagneticdata.output_collecting.read_table"]
oommfc = "oommfc._output_collecting_util.read_table:table_from_file"

[tool.black]
experimental-string-processing = true
Expand Down
Loading