Skip to content
Open
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
2 changes: 0 additions & 2 deletions micromagneticdata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
from .combined_drive import CombinedDrive as CombinedDrive
from .data import Data as Data
from .drive import Drive as Drive
from .mumax3drive import Mumax3Drive as Mumax3Drive
from .oommfdrive import OOMMFDrive as OOMMFDrive

__version__ = importlib.metadata.version(__package__)

Expand Down
5 changes: 0 additions & 5 deletions micromagneticdata/abstract_drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ class AbstractDrive(abc.ABC):
def __init__(self, callbacks=None):
self._callbacks = callbacks or []

@abc.abstractmethod
def __repr__(self):
"""Representation string."""
pass # pragma: no cover

@property
def x(self):
"""Independent variable name.
Expand Down
93 changes: 77 additions & 16 deletions micromagneticdata/drive.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import abc
import copy
import importlib.metadata
import json
import numbers
import pathlib

import discretisedfield as df
import ipywidgets
import ubermagtable as ut
import ubermagutil as uu
import ubermagutil.typesystem as ts

Expand Down Expand Up @@ -76,34 +76,81 @@ class Drive(md.AbstractDrive):
"""

def __new__(cls, name, number, dirname=".", x=None, use_cache=False, **kwargs):
"""Create a new OOMMFDrive or Mumax3Drive depending on the directory structure.
"""Create a new drive depending on the calculator.

If a subdirectory <name>.out exists a Mumax3Drive is created else an
OOMMFDrive.
Details of a drive can be calculator specific. Therefore, the individual adapter
classes have to provide the implementation for finding the relevant data. The
information about the adapter is inferred from the 'info.json' file created by
the driver, e.g. when using OOMMF via oommfc, the json file contains:::

"adapter": "oommfc"

A suitable adapter can also be passed explicitly.

Simulations computed with ubermag <= 2023.11 did not write 'adapter' in the
'info.json'. For those the legacy behaviour is kept to determine whether it is
an OOMMF or a Mumax3 simulation, which were the only supported calculators at
that time.

"""
if pathlib.Path(f"{dirname}/{name}/drive-{number}/{name}.out").exists():
return super().__new__(md.Mumax3Drive)
if "adapter" in kwargs:
adapter = kwargs["adapter"]
if not (drive_dir := pathlib.Path(f"{dirname}/{name}/drive-{number}")).is_dir():
msg = f"Directory {drive_dir!r} does not exist."
raise OSError(msg)

elif (f := pathlib.Path(f"{dirname}/{name}/drive-{number}/info.json")).exists():
info_json = json.loads(f.read_text())
if "adapter" in info_json:
adapter = info_json["adapter"]
else:
# info files written with ubermag.__version__ <= 2023.11 do not contain
# 'adapter'; legacy data loading
if pathlib.Path(f"{dirname}/{name}/drive-{number}/{name}.out").exists():
adapter = "mumax3c"
else:
adapter = "oommfc"
else:
return super().__new__(md.OOMMFDrive)
raise RuntimeError(
"No 'adapter' has been passed and the adapter could not be determined"
" automatically because no 'info.json' was found."
)

drive_entry_points = importlib.metadata.entry_points(
group="micromagneticdata.plugins.CalculatorDrive"
)

try:
CalculatorDrive = drive_entry_points[adapter].load()
except KeyError:
raise RuntimeError(
f"'{adapter}' must be installed to read the drive."
) from None

return super().__new__(CalculatorDrive)

def __init__(self, name, number, dirname="./", x=None, use_cache=False, **kwargs):
# use kwargs to not expose the following additional internal arguments to users
self._step_file_list = kwargs.pop("step_files", [])
self._table = kwargs.pop("table", None)

kwargs.pop("adapter", None)

super().__init__(**kwargs)
self.dirname = dirname
self.drive_path = pathlib.Path(f"{dirname}/{name}/drive-{number}")
if not self.drive_path.exists():
msg = f"Directory {self.drive_path!r} does not exist."
raise OSError(msg)

self.use_cache = use_cache
self.name = name
self.number = number
self.x = x

def __repr__(self):
return (
f"{self.__class__.__name__}(name='{self.name}', number={self.number}, "
f"dirname='{self.dirname}', x='{self.x}')"
)

@property
def dirname(self):
"""Directory containing the system's data."""
Expand All @@ -117,6 +164,15 @@ def dirname(self, dirname):
)
self._dirname = str(dirname)

@property
def _adapter(self):
"""
Name of the adapter package, e.g. oommfc, used to read the drive.
The information can be used to load additional entry points from that package.
"""
# extract package name
return self.__class__.__module__.split(".")[0]

@property
def use_cache(self):
"""Use caching for scalar data and the list of magnetisation files.
Expand Down Expand Up @@ -153,12 +209,18 @@ def _table_path(self):

@property
def table(self):
if not self.use_cache:
return ut.Table.fromfile(str(self._table_path), x=self.x)
if self.use_cache and self._table is not None:
return self._table

read_table = importlib.metadata.entry_points(
group="micromagneticdata.plugins.read_table"
)[self._adapter].load()
table = read_table(str(self._table_path), x=self.x)

if self.use_cache:
self._table = table

if self._table is None:
self._table = ut.Table.fromfile(str(self._table_path), x=self.x)
return self._table
return table

@property
@abc.abstractmethod
Expand Down Expand Up @@ -362,7 +424,6 @@ def slider(self, description="step", **kwargs):

def __lshift__(self, other):
if isinstance(other, md.Drive):
# no use of self.__class__ to allow combining Mumax3 and OOMMF runs
return md.CombinedDrive(self, other)
elif isinstance(other, md.CombinedDrive):
return md.CombinedDrive(self, *other.drives)
Expand Down
127 changes: 0 additions & 127 deletions micromagneticdata/mumax3drive.py

This file was deleted.

Loading
Loading