diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 160104d1..34799a4f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,37 +1,23 @@ -default_language_version: - python: python3.8 +exclude: 'dev' repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-merge-conflict # checks for files that contain merge conflict strings - id: check-toml # checks toml files for parseable syntax - id: debug-statements # checks for debugger imports and py37+ `breakpoint()` calls in python source -# - id: trailing-whitespace # needs more checks -# args: [--markdown-linebreak-ext=md] -# exclude: 'oommfc/tests/test_sample/.*' -- repo: https://github.com/pycqa/isort - rev: 5.12.0 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.4 hooks: - - id: isort - -- repo: https://github.com/nbQA-dev/nbQA - rev: 1.7.0 - hooks: - - id: nbqa-isort # isort inside Jupyter notebooks - -- repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - additional_dependencies: [flake8-rst-docstrings] #, flake8-docstrings] - -- repo: https://github.com/psf/black - rev: 23.9.1 - hooks: - - id: black-jupyter + # Run the linter. + - id: ruff + types_or: [python, pyi, jupyter] + args: [--fix, --exit-non-zero-on-fix] + # Run the formatter. + - id: ruff-format + types_or: [python, pyi, jupyter] # - repo: https://github.com/codespell-project/codespell # rev: v2.1.0 diff --git a/oommfc/__init__.py b/oommfc/__init__.py index 1f066acb..c28273a9 100644 --- a/oommfc/__init__.py +++ b/oommfc/__init__.py @@ -1,24 +1,25 @@ """OOMMF calculator.""" + import importlib.metadata import pytest import oommfc.oommf import oommfc.scripts - -from .compute import compute -from .delete import delete -from .drivers import Driver, HysteresisDriver, MinDriver, TimeDriver -from .evolvers import ( - CGEvolver, - EulerEvolver, - RungeKuttaEvolver, - SpinTEvolver, - SpinXferEvolver, - UHH_ThetaEvolver, - Xf_ThermHeunEvolver, - Xf_ThermSpinXferEvolver, -) +from .compute import compute as compute +from .delete import delete as delete +from .drivers import Driver as Driver +from .drivers import HysteresisDriver as HysteresisDriver +from .drivers import MinDriver as MinDriver +from .drivers import TimeDriver as TimeDriver +from .evolvers import CGEvolver as CGEvolver +from .evolvers import EulerEvolver as EulerEvolver +from .evolvers import RungeKuttaEvolver as RungeKuttaEvolver +from .evolvers import SpinTEvolver as SpinTEvolver +from .evolvers import SpinXferEvolver as SpinXferEvolver +from .evolvers import UHH_ThetaEvolver as UHH_ThetaEvolver +from .evolvers import Xf_ThermHeunEvolver as Xf_ThermHeunEvolver +from .evolvers import Xf_ThermSpinXferEvolver as Xf_ThermSpinXferEvolver __version__ = importlib.metadata.version(__package__) diff --git a/oommfc/compute.py b/oommfc/compute.py index 9592eab1..76faf838 100644 --- a/oommfc/compute.py +++ b/oommfc/compute.py @@ -39,7 +39,7 @@ def schedule_script(func, system): msg = f"Computing the value of {func} is not supported." raise ValueError(msg) - return 'Schedule "{}" archive Step 1\n'.format(output) + return f'Schedule "{output}" archive Step 1\n' def compute( diff --git a/oommfc/drivers/__init__.py b/oommfc/drivers/__init__.py index ffcd1d38..3d99f8af 100644 --- a/oommfc/drivers/__init__.py +++ b/oommfc/drivers/__init__.py @@ -1,4 +1,4 @@ -from .driver import Driver -from .hysteresisdriver import HysteresisDriver -from .mindriver import MinDriver -from .timedriver import TimeDriver +from .driver import Driver as Driver +from .hysteresisdriver import HysteresisDriver as HysteresisDriver +from .mindriver import MinDriver as MinDriver +from .timedriver import TimeDriver as TimeDriver diff --git a/oommfc/drivers/driver.py b/oommfc/drivers/driver.py index a16284ae..3ffaf164 100644 --- a/oommfc/drivers/driver.py +++ b/oommfc/drivers/driver.py @@ -192,7 +192,7 @@ def write_mif( compute=compute, **kwargs, ) - with open(self._miffilename(system), "wt", encoding="utf-8") as miffile: + with open(self._miffilename(system), "w", encoding="utf-8") as miffile: miffile.write(mif) # Generate and save json info file for a drive (not compute). diff --git a/oommfc/evolvers/__init__.py b/oommfc/evolvers/__init__.py index 5b230dec..94656491 100644 --- a/oommfc/evolvers/__init__.py +++ b/oommfc/evolvers/__init__.py @@ -1,8 +1,8 @@ -from .cgevolver import CGEvolver -from .eulerevolver import EulerEvolver -from .rungekuttaevolver import RungeKuttaEvolver -from .spintevolver import SpinTEvolver -from .spinxferevolver import SpinXferEvolver -from .uhh_thetaevolver import UHH_ThetaEvolver -from .xf_thermheunevolver import Xf_ThermHeunEvolver -from .xf_thermspinxferevolver import Xf_ThermSpinXferEvolver +from .cgevolver import CGEvolver as CGEvolver +from .eulerevolver import EulerEvolver as EulerEvolver +from .rungekuttaevolver import RungeKuttaEvolver as RungeKuttaEvolver +from .spintevolver import SpinTEvolver as SpinTEvolver +from .spinxferevolver import SpinXferEvolver as SpinXferEvolver +from .uhh_thetaevolver import UHH_ThetaEvolver as UHH_ThetaEvolver +from .xf_thermheunevolver import Xf_ThermHeunEvolver as Xf_ThermHeunEvolver +from .xf_thermspinxferevolver import Xf_ThermSpinXferEvolver as Xf_ThermSpinXferEvolver diff --git a/oommfc/oommf/__init__.py b/oommfc/oommf/__init__.py index 206c09b4..666c5915 100644 --- a/oommfc/oommf/__init__.py +++ b/oommfc/oommf/__init__.py @@ -1,9 +1,8 @@ """OOMMF driving utility""" -from .oommf import ( - DockerOOMMFRunner, - ExeOOMMFRunner, - OOMMFRunner, - Runner, - TclOOMMFRunner, - overhead, -) + +from .oommf import DockerOOMMFRunner as DockerOOMMFRunner +from .oommf import ExeOOMMFRunner as ExeOOMMFRunner +from .oommf import OOMMFRunner as OOMMFRunner +from .oommf import Runner as Runner +from .oommf import TclOOMMFRunner as TclOOMMFRunner +from .oommf import overhead as overhead diff --git a/oommfc/oommf/oommf.py b/oommfc/oommf/oommf.py index 85fb43e2..f67cf00f 100644 --- a/oommfc/oommf/oommf.py +++ b/oommfc/oommf/oommf.py @@ -158,7 +158,7 @@ def status(self): td.drive(system, t=1e-12, n=1, runner=self) print("OOMMF found and running.") return 0 - except (EnvironmentError, RuntimeError): + except (OSError, RuntimeError): print("Cannot find OOMMF.") return 1 @@ -250,7 +250,7 @@ def __init__(self, oommf_tcl): def errors(self): errors_file = os.path.join(os.path.dirname(self.oommf_tcl), "boxsi.errors") - with open(errors_file, "r") as f: + with open(errors_file) as f: errors = f.read() return errors @@ -285,13 +285,13 @@ def errors(self): "oommf", "boxsi.errors", ) - with open(errors_file, "r") as f: + with open(errors_file) as f: errors = f.read() return errors except FileNotFoundError: msg = "boxsi.errors cannot be retrieved." - raise EnvironmentError(msg) + raise OSError(msg) from None def __repr__(self): return f"ExeOOMMFRunner({self.oommf_exe})" @@ -357,7 +357,7 @@ def _kill(self, targets=("all",), dry_run=False): def errors(self): msg = "boxsi.errors cannot be retrieved from Docker container." - raise EnvironmentError(msg) + raise OSError(msg) def __repr__(self): return f"DockerOOMMFRunner(docker_exe={self.docker_exe}, image={self.image})" @@ -569,7 +569,7 @@ def autoselect_runner(self): # If OOMMFRunner was not returned up to this point, we raise an # exception. - raise EnvironmentError("Cannot find OOMMF.") + raise OSError("Cannot find OOMMF.") def __repr__(self): # avoid selecting a runner when calling __repr__ @@ -600,22 +600,21 @@ def overhead(): True """ - with tempfile.TemporaryDirectory() as workingdir: - with uu.changedir(workingdir): - # Running OOMMF through oommfc. - system = mm.examples.macrospin() - td = oc.TimeDriver() - oommfc_start = time.time() - td.drive(system, t=1e-12, n=1) - oommfc_stop = time.time() - oommfc_time = oommfc_stop - oommfc_start - - # Running OOMMF directly. - oommf_runner = oc.runner.runner - mifpath = pathlib.Path(f"{system.name}/drive-0/macrospin.mif").resolve() - oommf_start = time.time() - oommf_runner.call(str(mifpath)) - oommf_stop = time.time() - oommf_time = oommf_stop - oommf_start + with tempfile.TemporaryDirectory() as workingdir, uu.changedir(workingdir): + # Running OOMMF through oommfc. + system = mm.examples.macrospin() + td = oc.TimeDriver() + oommfc_start = time.time() + td.drive(system, t=1e-12, n=1) + oommfc_stop = time.time() + oommfc_time = oommfc_stop - oommfc_start + + # Running OOMMF directly. + oommf_runner = oc.runner.runner + mifpath = pathlib.Path(f"{system.name}/drive-0/macrospin.mif").resolve() + oommf_start = time.time() + oommf_runner.call(str(mifpath)) + oommf_stop = time.time() + oommf_time = oommf_stop - oommf_start return oommfc_time - oommf_time diff --git a/oommfc/scripts/__init__.py b/oommfc/scripts/__init__.py index 1872d18e..9b54d2cc 100644 --- a/oommfc/scripts/__init__.py +++ b/oommfc/scripts/__init__.py @@ -1,13 +1,11 @@ -from .driver import driver_script -from .energy import energy_script -from .evolver import evolver_script -from .mesh import mesh_script -from .system import system_script -from .util import ( - box_atlas, - file_vector_field, - setup_m0, - setup_scalar_parameter, - setup_vector_parameter, - vector_norm_scalar_field, -) +from .driver import driver_script as driver_script +from .energy import energy_script as energy_script +from .evolver import evolver_script as evolver_script +from .mesh import mesh_script as mesh_script +from .system import system_script as system_script +from .util import box_atlas as box_atlas +from .util import file_vector_field as file_vector_field +from .util import setup_m0 as setup_m0 +from .util import setup_scalar_parameter as setup_scalar_parameter +from .util import setup_vector_parameter as setup_vector_parameter +from .util import vector_norm_scalar_field as vector_norm_scalar_field diff --git a/oommfc/scripts/driver.py b/oommfc/scripts/driver.py index 53a7b98a..a85a12b0 100644 --- a/oommfc/scripts/driver.py +++ b/oommfc/scripts/driver.py @@ -74,10 +74,7 @@ def driver_script( driver.evolver.fixed_spins = resstr # What is saved in output? - if output_step: - output_str = "Step" - else: - output_str = "Stage" + output_str = "Step" if output_step else "Stage" mif += oc.scripts.evolver_script(driver.evolver) diff --git a/oommfc/scripts/energy.py b/oommfc/scripts/energy.py index 25c1cc7d..dbf09507 100644 --- a/oommfc/scripts/energy.py +++ b/oommfc/scripts/energy.py @@ -1,3 +1,4 @@ +import contextlib import numbers import warnings @@ -22,10 +23,7 @@ def exchange_script(term, system): mif += "}\n\n" elif isinstance(term.A, dict): - if "default" in term.A.keys(): - default_value = term.A["default"] - else: - default_value = 0 + default_value = term.A.get("default", 0) mif = "# Exchange6Ngbr\n" mif += f"Specify Oxs_Exchange6Ngbr:{term.name} {{\n" mif += f" default_A {default_value}\n" @@ -61,7 +59,9 @@ def zeeman_script(term, system): if isinstance(term.wave, str) or isinstance(term.func, str): if isinstance(term.wave, str): warnings.warn( - "Parameter `wave` is deprecated; use `func` instead.", FutureWarning + "Parameter `wave` is deprecated; use `func` instead.", + FutureWarning, + stacklevel=2, ) if isinstance(term.H, (df.Field, dict)): if term.wave == "sin" or term.func == "sin": @@ -220,10 +220,8 @@ def zeeman_script(term, system): mif += f'Specify {term.tcl_strings["energy"]}:{term.name} {{\n' mif += f' script {term.tcl_strings["script_name"]}\n' for key in ["type", "script_args"]: - try: + with contextlib.suppress(KeyError): mif += f" {key} {term.tcl_strings[key]}\n" - except KeyError: - pass if term.tcl_strings["energy"] == "Oxs_TransformZeeman": mif += f" field {Hname}\n" mif += "}\n\n" @@ -264,14 +262,16 @@ def dmi_script(term, system): elif (tcc := term.crystalclass) in ["D2d_x", "D2d_y", "D2d_z", "D2d"]: if tcc == "D2d": warnings.warn( - "Use of `D2d` is deprecated; use `D2d_z` instead.", FutureWarning + "Use of `D2d` is deprecated; use `D2d_z` instead.", + FutureWarning, + stacklevel=2, ) tcc = "D2d_z" oxs = f"Oxs_DMI_{tcc}" elif (tcc := term.crystalclass) in ["Cnv_x", "Cnv_y", "Cnv_z", "Cnv"]: if tcc == "Cnv": msg = "Use of `Cnv` is deprecated; use `Cnv_z` instead." - warnings.warn(msg, FutureWarning) + warnings.warn(msg, FutureWarning, stacklevel=2) tcc = "Cnv_z" oxs = f"Oxs_DMI_{tcc}" @@ -290,10 +290,7 @@ def dmi_script(term, system): mif += "}\n\n" elif isinstance(term.D, dict): - if "default" in term.D.keys(): - default_value = term.D["default"] - else: - default_value = 0 + default_value = term.D.get("default", 0) mif += f" default_D {default_value}\n" mif += " atlas :main_atlas\n" mif += " D {\n" diff --git a/oommfc/scripts/evolver.py b/oommfc/scripts/evolver.py index 59174e0f..8962f674 100644 --- a/oommfc/scripts/evolver.py +++ b/oommfc/scripts/evolver.py @@ -70,20 +70,20 @@ def evolver_script(evolver, **kwargs): mif += "}\n\n" if isinstance(evolver, (oc.SpinXferEvolver, oc.Xf_ThermSpinXferEvolver)): - setattr(evolver, "J_profile", "TimeFunction") - setattr(evolver, "J_profile_args", "total_time") + evolver.J_profile = "TimeFunction" + evolver.J_profile_args = "total_time" elif isinstance(evolver, oc.SpinTEvolver): - setattr(evolver, "u_profile", "TimeFunction") - setattr(evolver, "u_profile_args", "total_time") + evolver.u_profile = "TimeFunction" + evolver.u_profile_args = "total_time" if hasattr(evolver, "tcl_strings") and isinstance(evolver.tcl_strings, dict): print(evolver.tcl_strings) mif += evolver.tcl_strings["script"] if isinstance(evolver, (oc.SpinXferEvolver, oc.Xf_ThermSpinXferEvolver)): - setattr(evolver, "J_profile", evolver.tcl_strings["script_name"]) - setattr(evolver, "J_profile_args", evolver.tcl_strings["script_args"]) + evolver.J_profile = evolver.tcl_strings["script_name"] + evolver.J_profile_args = evolver.tcl_strings["script_args"] elif isinstance(evolver, oc.SpinTEvolver): - setattr(evolver, "u_profile", evolver.tcl_strings["script_name"]) - setattr(evolver, "u_profile_args", evolver.tcl_strings["script_args"]) + evolver.u_profile = evolver.tcl_strings["script_name"] + evolver.u_profile_args = evolver.tcl_strings["script_args"] # temperature cannot spacially vary diff --git a/oommfc/scripts/mesh.py b/oommfc/scripts/mesh.py index 52f2b848..5a6504b6 100644 --- a/oommfc/scripts/mesh.py +++ b/oommfc/scripts/mesh.py @@ -12,7 +12,7 @@ def mesh_script(mesh): mif += oc.scripts.box_atlas(mesh.region.pmin, mesh.region.pmax, name="entire") mif += "# MultiAtlas\n" mif += "Specify Oxs_MultiAtlas:main_atlas {\n" - for name in mesh.subregions.keys(): + for name in mesh.subregions: mif += f" atlas :{name}_atlas\n" mif += " atlas :entire_atlas\n" mif += f" xrange {{ {mesh.region.pmin[0]} {mesh.region.pmax[0]} }}\n" diff --git a/oommfc/scripts/util.py b/oommfc/scripts/util.py index 0cd1215c..6db07abc 100644 --- a/oommfc/scripts/util.py +++ b/oommfc/scripts/util.py @@ -86,7 +86,7 @@ def setup_scalar_parameter(parameter, name): # to avoid changing the dictionary used in the respective term # in the system (from micromagneticmodel) param = parameter.copy() - if "default" not in param.keys(): + if "default" not in param: param["default"] = 0 mif = atlas_scalar_field(param, f"{name}") return mif, f"{name}" @@ -102,7 +102,7 @@ def setup_vector_parameter(parameter, name): return mif, f"{name}" elif isinstance(parameter, dict): - if "default" not in parameter.keys(): + if "default" not in parameter: parameter["default"] = (0, 0, 0) mif = atlas_vector_field(parameter, f"{name}") return mif, f"{name}" diff --git a/oommfc/tests/test_oommf.py b/oommfc/tests/test_oommf.py index 26329bcb..e1230967 100644 --- a/oommfc/tests/test_oommf.py +++ b/oommfc/tests/test_oommf.py @@ -27,9 +27,7 @@ def check_runner(runner): # Cleanup created files. for f in os.listdir(dirname): - if f.endswith(".odt"): - os.remove(os.path.join(dirname, f)) - elif f.endswith(".omf") and f.startswith("test_oommf-Oxs"): + if f.endswith(".odt") or f.endswith(".omf") and f.startswith("test_oommf-Oxs"): os.remove(os.path.join(dirname, f)) @@ -120,7 +118,7 @@ def test_missing_oommf(): oc.runner.oommf_exe = "wrong_name" oc.runner.docker_exe = "wrong_name" with pytest.raises(EnvironmentError): - oc.runner.runner + oc.runner.runner # noqa: B018 def test_get_cached_runner(reset_runner, monkeypatch): @@ -138,7 +136,7 @@ def test_get_cached_runner(reset_runner, monkeypatch): oc.runner.cache_runner = False oc.runner.docker_exe = "wrong_name" # ensure that we do not find docker with pytest.raises(EnvironmentError): - oc.runner.runner + oc.runner.runner # noqa: B018 oc.runner.envvar = "OOMMFTCL" if oommf_tcl_path(): diff --git a/pyproject.toml b/pyproject.toml index ab525f08..aeaa1018 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,15 +61,49 @@ repository = "https://github.com/ubermag/oommfc" -[tool.black] -experimental-string-processing = true [tool.coverage.run] omit = ["oommfc/tests/*"] -[tool.isort] -profile = "black" -skip_gitignore = true # ignores files listed in .gitignore +[tool.ruff.lint] +ignore-init-module-imports = true # do not remove unused imports in __init__ and warn instead +select = [ + "B", # flake8-bugbear + "E", # pycodestyle + "F", # Pyflakes + "I", # isort + "SIM", # flake8-simplify + "UP", # pyupgrade +] +ignore = [ + # conflict with other rules + "D203", # one-blank-line-before-class (conflicts with D204) + "D212", # multi-line-summary-first-line (conflicts with D213) + # conflict with formatter + "D206", # indent-with-spaces + "D300", # triple-single-quotes + "E111", # indentation-with-invalid-multiple + "E114", # indentation-with-invalid-multiple-comment + "E117", # over-indented + # conflict with Python 3.6 compatibility + "UP022", # replace-stdout-stderr +] + +[tool.ruff.lint.isort] +known-local-folder = ["oommfc"] + +[tool.ruff.lint.per-file-ignores] +"*.ipynb" = [ + "B018", # "Found useless expression. Either assign it to a variable or remove it."; false positives when using implicit __repr__ in the notebook + "E501", # line too long + "F811", # 'redefined-while-unused'; many false positives in notebooks because ipywidgets decorated functions are not recognised +] + +[tool.pytest.ini_options] +filterwarnings = [ + "error", + "ignore:((.|\n)*)Sentinel is not a public part of the traitlets API((.|\n)*)", # dependency of k3d +] [tool.setuptools.packages.find] include = ["oommfc*"] diff --git a/setup.cfg b/setup.cfg index 96139012..a73006a5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,30 +1,2 @@ -[flake8] -exclude = - .git, - __pycache__, - build, - dev, - dist, - setup.py -# black has a longer default line length -max-line-length = 88 -# D107: missing docstring in __init__ -# E203: withespace before ':', required for black -# RST210: Inline strong start-string without end-string. # complains about "**kwargs" in docstrings -extend-ignore = D107,RST210,E203 -per-file-ignores = - # imported but unused - __init__.py: F401 - # ignore missing docstrings in tests - test_*.py: D100,D101,D102,D103 -docstring-convention: numpy -# flake8-rst-docstrings: -rst-roles = - py:class, - py:func, -rst-directives = - seealso, - plot, - [codespell] skip = .*,build/*,dev/*,dist/* diff --git a/tasks.py b/tasks.py index f87b6800..d2d55c57 100644 --- a/tasks.py +++ b/tasks.py @@ -1,4 +1,5 @@ """Tasks to release the package.""" + import os import shutil