diff --git a/.coverage b/.coverage
new file mode 100644
index 0000000..c9a1e04
Binary files /dev/null and b/.coverage differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7c038d8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+__pycache__
+
+# Build
+build
+dist
+**/*.egg-info
+
+# VSCode settings
+.vscode
+.conda
+.pytest_cache
+
+# Docs
+docs/make.bat
+docs/build
+docs/source/api/generated
+*generated
+
+
+# Debug
+tmp*
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..8969772
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,37 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+- repo: https://github.com/adrybakov/pre-commit-hooks
+ rev: 0.2.0
+ hooks:
+ - id: license-headers
+ args:
+ - --license-file
+ - L-HEADER
+ - --update-year
+ - --verbose
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.6.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-yaml
+ - id: check-added-large-files
+- repo: https://github.com/pycqa/isort
+ rev: 5.13.2
+ hooks:
+ - id: isort
+ args: ["--profile", "black", "--filter-files"]
+- repo: https://github.com/psf/black-pre-commit-mirror
+ rev: 24.4.2
+ hooks:
+ - id: black
+ language_version: python3.11
+- repo: local
+ hooks:
+ - id: pytest-check
+ name: pytest-check
+ entry: pytest
+ language: system
+ pass_filenames: false
+ always_run: true
diff --git a/L-HEADER b/L-HEADER
new file mode 100644
index 0000000..2c1c62f
--- /dev/null
+++ b/L-HEADER
@@ -0,0 +1,19 @@
+Copyright (c) [2024] [Daniel Pozsar]
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..00582b3
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) [2024] [Daniel Pozsar]
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index e69de29..5e1a4b7 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1 @@
+# Relativistic magnetic interactions from non-orthogonal basis sets
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..1422923
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,4 @@
+sphinx
+sphinx_rtd_theme
+numpydoc
+doctest
diff --git a/docs/source/.coverage b/docs/source/.coverage
new file mode 100644
index 0000000..4ce7682
Binary files /dev/null and b/docs/source/.coverage differ
diff --git a/docs/source/Makefile b/docs/source/Makefile
new file mode 100644
index 0000000..d4bb2cb
--- /dev/null
+++ b/docs/source/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/source/_build/doctrees/environment.pickle b/docs/source/_build/doctrees/environment.pickle
new file mode 100644
index 0000000..74e6848
Binary files /dev/null and b/docs/source/_build/doctrees/environment.pickle differ
diff --git a/docs/source/_build/doctrees/grogu.doctree b/docs/source/_build/doctrees/grogu.doctree
new file mode 100644
index 0000000..a099c16
Binary files /dev/null and b/docs/source/_build/doctrees/grogu.doctree differ
diff --git a/docs/source/_build/doctrees/index.doctree b/docs/source/_build/doctrees/index.doctree
new file mode 100644
index 0000000..e84e375
Binary files /dev/null and b/docs/source/_build/doctrees/index.doctree differ
diff --git a/docs/source/_build/doctrees/modules.doctree b/docs/source/_build/doctrees/modules.doctree
new file mode 100644
index 0000000..93edda6
Binary files /dev/null and b/docs/source/_build/doctrees/modules.doctree differ
diff --git a/docs/source/_build/html/.buildinfo b/docs/source/_build/html/.buildinfo
new file mode 100644
index 0000000..ed21c1e
--- /dev/null
+++ b/docs/source/_build/html/.buildinfo
@@ -0,0 +1,4 @@
+# Sphinx build info version 1
+# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
+config: 0f8ff1598a5b65221fc2ef8f29fdbeac
+tags: 645f666f9bcd5a90fca523b33c5a78b7
diff --git a/docs/source/_build/html/_modules/grogu/useful.html b/docs/source/_build/html/_modules/grogu/useful.html
new file mode 100644
index 0000000..15af420
--- /dev/null
+++ b/docs/source/_build/html/_modules/grogu/useful.html
@@ -0,0 +1,282 @@
+
+
+
+
+
+# Copyright (c) [2024] [Daniel Pozsar]
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+importnumpyasnp
+fromscipy.specialimportroots_legendre
+fromitertoolsimportpermutations,product
+
+
+# define some useful functions
+
+[docs]
+defhsk(dh,k=(0,0,0)):
+"""
+ One way to speed up Hk and Sk generation
+ """
+ k=np.asarray(k,np.float64)# this two conversion lines
+ k.shape=(-1,)# are from the sisl source
+
+ # this generates the list of phases
+ phases=np.exp(-1j*np.dot(np.dot(np.dot(dh.rcell,k),dh.cell),dh.sc.sc_off.T))
+
+ HKU=np.einsum("abc,c->ab",dh.hup,phases)
+ HKD=np.einsum("abc,c->ab",dh.hdo,phases)
+ SK=np.einsum("abc,c->ab",dh.sov,phases)
+
+ returnHKU,HKD,SK
+[docs]
+defmake_kset(dirs="xyz",NUMK=20):
+"""
+ Simple k-grid generator. Depending on the value of the dirs
+ argument k sampling in 1,2 or 3 dimensions is generated.
+ If dirs argument does not contain either of x,y or z
+ a kset of a single k-pont at the origin is returend.
+ """
+ ifnot(sum([dindirsfordin"xyz"])):
+ returnnp.array([[0,0,0]])
+
+ kran=len(dirs)*[np.linspace(0,1,NUMK,endpoint=False)]
+ mg=np.meshgrid(*kran)
+ dirsdict=dict()
+
+ fordinenumerate(dirs):
+ dirsdict[d[1]]=mg[d[0]].flatten()
+ fordin"xyz":
+ ifnot(dindirs):
+ dirsdict[d]=0*dirsdict[dirs[0]]
+ kset=np.array([dirsdict[d]fordin"xyz"]).T
+
+ returnkset
+
+
+
+
+[docs]
+defmake_atran(nauc,dirs="xyz",dist=1):
+"""
+ Simple pair generator. Depending on the value of the dirs
+ argument sampling in 1,2 or 3 dimensions is generated.
+ If dirs argument does not contain either of x,y or z
+ a single pair is returend.
+ """
+ ifnot(sum([dindirsfordin"xyz"])):
+ return(0,0,[1,0,0])
+
+ dran=len(dirs)*[np.arange(-dist,dist+1)]
+ mg=np.meshgrid(*dran)
+ dirsdict=dict()
+
+ fordinenumerate(dirs):
+ dirsdict[d[1]]=mg[d[0]].flatten()
+ fordin"xyz":
+ ifnot(dindirs):
+ dirsdict[d]=0*dirsdict[dirs[0]]
+
+ ucran=np.array([dirsdict[d]fordin"xyz"]).T
+ atran=[]
+ fori,jinlist(product(range(nauc),repeat=2)):
+ foruinucran:
+ if(abs(i-j)+sum(abs(u)))>0:
+ atran.append((i,j,list(u)))
+
+ returnatran
+
+
+
+
+[docs]
+defadd(x,y):
+"""The sum of two numbers for testing.
+
+ This function adds to numbers together. I only created this for testing documentation and examples.
+
+ Parameters
+ ----------
+ x : float
+ First number
+ y : float
+ Second number added to `x`
+
+ Returns
+ -------
+ sum : int
+ The sum of the inputs
+
+ See Also
+ --------
+ numpy.add : Adds more than two numbers.
+
+ Notes
+ -----
+ We can create some latex notes here [1]_ :
+
+ .. math:: a + b = c
+
+ References
+ ----------
+ .. [1] https://numpydoc.readthedocs.io/en/latest/format.html
+
+
+ Examples
+ --------
+ >>> add(1, 2)
+ 3
+
+ """
+ returnx+y
Simple pair generator. Depending on the value of the dirs
+argument sampling in 1,2 or 3 dimensions is generated.
+If dirs argument does not contain either of x,y or z
+a single pair is returend.
Simple k-grid generator. Depending on the value of the dirs
+argument k sampling in 1,2 or 3 dimensions is generated.
+If dirs argument does not contain either of x,y or z
+a kset of a single k-pont at the origin is returend.
+
+
+
+
+
+
+
+
+
diff --git a/docs/source/_build/html/searchindex.js b/docs/source/_build/html/searchindex.js
new file mode 100644
index 0000000..3d96987
--- /dev/null
+++ b/docs/source/_build/html/searchindex.js
@@ -0,0 +1 @@
+Search.setIndex({"alltitles": {"Contents:": [[1, null]], "Indices and tables": [[1, "indices-and-tables"]], "Module contents": [[0, "module-grogu"]], "Submodules": [[0, "submodules"]], "asd documentation": [[1, null]], "grogu package": [[0, null]], "grogu.example module": [[0, "module-grogu.example"]], "grogu.jij module": [[0, "module-grogu.jij"]], "grogu.useful module": [[0, "module-grogu.useful"]], "src": [[2, null]]}, "docnames": ["grogu", "index", "modules"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1}, "filenames": ["grogu.rst", "index.rst", "modules.rst"], "indexentries": {"add() (in module grogu.useful)": [[0, "grogu.useful.add", false]], "grogu": [[0, "module-grogu", false]], "grogu.example": [[0, "module-grogu.example", false]], "grogu.jij": [[0, "module-grogu.jij", false]], "grogu.useful": [[0, "module-grogu.useful", false]], "hsk() (in module grogu.useful)": [[0, "grogu.useful.hsk", false]], "make_atran() (in module grogu.useful)": [[0, "grogu.useful.make_atran", false]], "make_contour() (in module grogu.useful)": [[0, "grogu.useful.make_contour", false]], "make_kset() (in module grogu.useful)": [[0, "grogu.useful.make_kset", false]], "module": [[0, "module-grogu", false], [0, "module-grogu.example", false], [0, "module-grogu.jij", false], [0, "module-grogu.useful", false]]}, "objects": {"": [[0, 0, 0, "-", "grogu"]], "grogu": [[0, 0, 0, "-", "example"], [0, 0, 0, "-", "jij"], [0, 0, 0, "-", "useful"]], "grogu.useful": [[0, 1, 1, "", "add"], [0, 1, 1, "", "hsk"], [0, 1, 1, "", "make_atran"], [0, 1, 1, "", "make_contour"], [0, 1, 1, "", "make_kset"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"]}, "objtypes": {"0": "py:module", "1": "py:function"}, "terms": {"0": 0, "1": 0, "150": 0, "2": 0, "20": 0, "3": 0, "42": 0, "A": 0, "If": 0, "One": 0, "The": 0, "ad": 0, "add": [0, 1, 2], "argument": 0, "b": 0, "c": 0, "can": 0, "contain": 0, "content": 2, "contour": 0, "creat": 0, "depend": 0, "detail": 1, "dh": 0, "dimens": 0, "dir": 0, "dist": 0, "document": 0, "doe": 0, "either": 0, "emax": 0, "emin": 0, "en": 0, "enum": 0, "exampl": 2, "first": 0, "float": 0, "format": 0, "function": 0, "gener": 0, "grid": 0, "grogu": [1, 2], "here": 0, "hk": 0, "hsk": [0, 2], "html": 0, "http": 0, "i": 0, "index": 1, "input": 0, "int": 0, "io": 0, "jij": 2, "k": 0, "kset": 0, "latest": 0, "latex": 0, "make_atran": [0, 2], "make_contour": [0, 2], "make_kset": [0, 2], "modul": [1, 2], "more": 0, "nauc": 0, "note": 0, "number": 0, "numk": 0, "numpi": 0, "numpydoc": 0, "onli": 0, "origin": 0, "p": 0, "packag": [1, 2], "page": 1, "pair": 0, "paramet": 0, "pont": 0, "readthedoc": 0, "refer": 0, "restructuredtext": 1, "returend": 0, "return": 0, "sampl": 0, "search": 1, "second": 0, "see": 1, "simpl": 0, "singl": 0, "sk": 0, "some": 0, "sophist": 0, "sourc": 0, "speed": 0, "src": 1, "submodul": 2, "sum": 0, "syntax": 1, "test": 0, "than": 0, "thi": 0, "togeth": 0, "two": 0, "type": 0, "up": 0, "us": [1, 2], "valu": 0, "wai": 0, "we": 0, "x": 0, "xyz": 0, "y": 0, "your": 1, "z": 0}, "titles": ["grogu package", "asd documentation", "src"], "titleterms": {"asd": 1, "content": [0, 1], "document": 1, "exampl": 0, "grogu": 0, "indic": 1, "jij": 0, "modul": 0, "packag": 0, "src": 2, "submodul": 0, "tabl": 1, "us": 0}})
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000..e36971e
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,52 @@
+# Copyright (c) [2024] [Daniel Pozsar]
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath("../../src"))
+
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = "Grogu"
+copyright = "2024, Grogu"
+author = "Laszlo Oroszlany, Daniel Pozsar"
+release = "0.0.1"
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = [
+ "sphinx.ext.autodoc",
+ "sphinx.ext.viewcode",
+ "sphinx.ext.napoleon",
+]
+
+templates_path = ["_templates"]
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = "sphinx_rtd_theme"
+html_static_path = ["_static"]
diff --git a/docs/source/grogu.rst b/docs/source/grogu.rst
new file mode 100644
index 0000000..e8b3bc1
--- /dev/null
+++ b/docs/source/grogu.rst
@@ -0,0 +1,37 @@
+grogu package
+=============
+
+Submodules
+----------
+
+grogu.example module
+--------------------
+
+.. automodule:: grogu.example
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+grogu.jij module
+----------------
+
+.. automodule:: grogu.jij
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+grogu.useful module
+-------------------
+
+.. automodule:: grogu.useful
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: grogu
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000..7b9c75d
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,25 @@
+.. asd documentation master file, created by
+ sphinx-quickstart on Thu Oct 10 17:10:03 2024.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+asd documentation
+=================
+
+Add your content using ``reStructuredText`` syntax. See the
+`reStructuredText `_
+documentation for details.
+
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+ modules
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/source/make.bat b/docs/source/make.bat
new file mode 100644
index 0000000..32bb245
--- /dev/null
+++ b/docs/source/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.https://www.sphinx-doc.org/
+ exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/source/modules.rst b/docs/source/modules.rst
new file mode 100644
index 0000000..34386df
--- /dev/null
+++ b/docs/source/modules.rst
@@ -0,0 +1,7 @@
+src
+===
+
+.. toctree::
+ :maxdepth: 4
+
+ grogu
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..8668515
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,52 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "grogu"
+version = "0.0.1"
+authors = [
+ { name="Laszlo Oroszlany", email="laszlo.oroszlany@ttk.elte.hu" },
+ { name="Daniel Pozsar", email="danielpozsar@student.elte.hu" },
+]
+description = "Relativistic magnetic interactions from non-orthogonal basis sets"
+readme = "README.md"
+kewords = [
+ "DFT",
+ "physics",
+]
+requires-python = ">=3.8"
+dependencies = [
+ "numpy",
+ "scipy",
+ "sisl",
+ "mpi4py",
+ "argparse",
+]
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3 :: Only",
+ "Topic :: Scientific/Engineering :: Physics",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+]
+
+[project.urls]
+Homepage = "https://github.com/pypa/sampleproject"
+Documentation = "https://readthedocs.org"
+Repository = "https://github.com/me/spam.git"
+Issues = "https://github.com/pypa/sampleproject/issues"
+
+[tool.pytest.ini_options]
+addopts = [
+ "--import-mode=importlib",
+# "--cov",
+ "--doctest-modules",
+]
+pythonpath = [
+ "src/grogu/",
+]
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..bd831e6
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,17 @@
+# PyPI
+wheel
+setuptools
+build
+
+# code-linters
+black
+license-headers
+
+# dev-tools
+pre-commit
+pytest
+pytest-randomly
+pytest-cov
+doctest
+sphinx
+git
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..539e9e5
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,6 @@
+# Package dependencies
+numpy
+scipy
+sisl
+mpi4py
+argparse
diff --git a/src/grogu/__init__.py b/src/grogu/__init__.py
new file mode 100644
index 0000000..39a4a19
--- /dev/null
+++ b/src/grogu/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (c) [2024] [Daniel Pozsar]
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from jij import *
+from useful import *
diff --git a/src/grogu/example.py b/src/grogu/example.py
new file mode 100644
index 0000000..93d0ca4
--- /dev/null
+++ b/src/grogu/example.py
@@ -0,0 +1,19 @@
+# Copyright (c) [2024] [Daniel Pozsar]
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
diff --git a/src/grogu/jij.py b/src/grogu/jij.py
new file mode 100644
index 0000000..3de5d32
--- /dev/null
+++ b/src/grogu/jij.py
@@ -0,0 +1,294 @@
+# Copyright (c) [2024] [Daniel Pozsar]
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+if __name__ == "__main__":
+ import argparse
+ import sys
+ from itertools import permutations, product
+ from timeit import default_timer as timer
+
+ import numpy as np
+ import numpy.linalg as nl
+ import sisl
+ import tqdm
+ from mpi4py import MPI
+ from scipy.special import roots_legendre
+
+ from grogu.useful import hsk, make_atran, make_contour, make_kset
+
+ start = timer()
+
+ # Some input parsing
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--kset",
+ dest="kset",
+ default=2,
+ type=int,
+ help="k-space resolution of Jij calculation",
+ )
+ parser.add_argument(
+ "--kdirs",
+ dest="kdirs",
+ default="xyz",
+ help="Definition of k-space dimensionality",
+ )
+ parser.add_argument(
+ "--eset",
+ dest="eset",
+ default=42,
+ type=int,
+ help="Number of energy points on the contour",
+ )
+ parser.add_argument(
+ "--eset-p",
+ dest="esetp",
+ default=10,
+ type=int,
+ help="Parameter tuning the distribution on the contour",
+ )
+ parser.add_argument("--input", dest="infile", required=True, help="Input file name")
+ parser.add_argument(
+ "--output", dest="outfile", required=True, help="Output file name"
+ )
+ parser.add_argument(
+ "--Ebot",
+ dest="Ebot",
+ default=-20.0,
+ type=float,
+ help="Bottom energy of the contour",
+ )
+ parser.add_argument(
+ "--npairs",
+ dest="npairs",
+ default=1,
+ type=int,
+ help="Number of unitcell pairs in each direction for Jij calculation",
+ )
+ parser.add_argument(
+ "--adirs", dest="adirs", default=False, help="Definition of pair directions"
+ )
+ parser.add_argument(
+ "--use-tqdm",
+ dest="usetqdm",
+ default="not",
+ help="Use tqdm for progressbars or not",
+ )
+ parser.add_argument(
+ "--cutoff",
+ dest="cutoff",
+ default=100.0,
+ type=float,
+ help="Real space cutoff for pair generation in Angs",
+ )
+ parser.add_argument(
+ "--pairfile",
+ dest="pairfile",
+ default=False,
+ help="File to read pair information",
+ )
+ args = parser.parse_args()
+ # ----------------------------------------------------------------------
+
+ # MPI init
+ comm = MPI.COMM_WORLD
+ size = comm.Get_size()
+ rank = comm.Get_rank()
+ root_node = 0
+ if rank == root_node:
+ print("Number of nodes in the parallel cluster: ", size)
+ # ----------------------------------------------------------------------
+
+ # importing the necessary structures from SIESTA output
+ dat = sisl.get_sile(args.infile)
+ dh = dat.read_hamiltonian()
+ # update datastructure of the hamiltonian
+ # this is needed for quick Hk building
+ dh.hup = (
+ dh.tocsr(0)
+ .toarray()
+ .reshape(dh.no, dh.n_s, dh.no)
+ .transpose(0, 2, 1)
+ .astype("complex128")
+ )
+ dh.hdo = (
+ dh.tocsr(1)
+ .toarray()
+ .reshape(dh.no, dh.n_s, dh.no)
+ .transpose(0, 2, 1)
+ .astype("complex128")
+ )
+ dh.sov = (
+ dh.tocsr(2)
+ .toarray()
+ .reshape(dh.no, dh.n_s, dh.no)
+ .transpose(0, 2, 1)
+ .astype("complex128")
+ )
+ # ----------------------------------------------------------------------
+
+ # generate k space sampling
+ kset = make_kset(dirs=args.kdirs, NUMK=args.kset)
+ wk = 1 / len(kset) # weight of a kpoint in BZ integral
+ kpcs = np.array_split(kset, size)
+ if "k" in args.usetqdm:
+ kpcs[root_node] = tqdm.tqdm(kpcs[root_node], desc="k loop")
+ # ----------------------------------------------------------------------
+ # define pairs
+
+ if args.pairfile:
+ # if pair file is specified read in pair information from pairfile
+ if rank == root_node:
+ # read in pair on root node file in a format of five integer columns
+ # first two integers define sublattice
+ # second three define distance vectors of unitcells
+ dummy = np.loadtxt(args.pairfile, dtype=int)
+ atran = [
+ (dummy[p, 0], dummy[p, 1], [dummy[p, 2], dummy[p, 3], dummy[p, 4]])
+ for p in range(len(dummy))
+ ]
+ else:
+ atran = None
+ # syncronize atran over all nodes
+ atran = comm.bcast(atran, root=root_node)
+ else:
+ # if pairfile is not specified generate pair as defined by npairs and adirs
+ # Take pair directions for k directions if adirs is not set
+ args.adirs = args.adirs if args.adirs else args.kdirs
+ # definition of pairs in terms of integer coordinates refering
+ # to unicell distances and atomic positions
+ atran = make_atran(len(dh.atoms), args.adirs, dist=args.npairs)
+ pairs = []
+ for i, j, uc in atran:
+ if nl.norm(np.dot(uc, dh.cell)) < args.cutoff:
+ pairs.append(
+ dict(
+ offset=uc, # lattice vector offset between the unitcells the two atoms are
+ aiij=[i, j], # indecies of the atoms in the unitcell
+ noij=[
+ dh.atoms.orbitals[i],
+ dh.atoms.orbitals[j],
+ ], # number of orbitals on the appropriate atoms
+ slij=[
+ slice(
+ *(lambda x: [min(x), max(x) + 1])(dh.a2o(i, all=True))
+ ), # slices for
+ slice(*(lambda x: [min(x), max(x) + 1])(dh.a2o(j, all=True))),
+ ], # appropriate orbitals
+ rirj=[
+ dh.axyz()[i],
+ dh.axyz()[j],
+ ], # real space vectors of atoms in the unit cell
+ Rij=np.dot(
+ uc, dh.cell
+ ), # real space distance vector between unit cells
+ rij=np.dot(uc, dh.cell)
+ - dh.axyz()[i]
+ + dh.axyz()[j], # real space vector between atoms
+ Jijz=[], # in this empty list are we going to gather the integrad of the energy integral
+ Jij=0, # the final results of the calculation are going to be here on the root node
+ )
+ )
+
+ if rank == root_node:
+ print("Number of pairs beeing calculated: ", len(pairs))
+
+ comm.Barrier()
+ # ----------------------------------------------------------------------
+
+ # make energy contour
+ # we are working in eV now !
+ # and sisil shifts E_F to 0 !
+ cont = make_contour(emin=args.Ebot, enum=args.eset, p=args.esetp)
+ eran = cont.ze
+ # ----------------------------------------------------------------------
+
+ # generating onsite matrix and overalp elements of all the atoms in the unitcell
+ # onsite of the origin supercell
+ orig_indx = np.arange(0, dh.no) + dh.sc_index([0, 0, 0]) * dh.no
+ # spin up
+ uc_up = dh.tocsr(dh.UP)[:, orig_indx].toarray()
+ # spin down
+ uc_down = dh.tocsr(dh.DOWN)[:, orig_indx].toarray()
+ Hs = []
+ # get number of atoms in the unit cell
+ for i in range(len(dh.atoms)):
+ at_indx = dh.a2o(i, all=True)
+ Hs.append(uc_up[:, at_indx][at_indx, :] - uc_down[:, at_indx][at_indx, :])
+
+ # ----------------------------------------------------------------------
+
+ # sampling the integrand on the contour and the BZ
+
+ for pair in pairs:
+ noi, noj = pair["noij"]
+ pair["Guij"] = np.zeros((args.eset, noi, noj), dtype="complex128")
+ pair["Gdji"] = np.zeros((args.eset, noj, noi), dtype="complex128")
+ pair["Guij_tmp"] = np.zeros((args.eset, noi, noj), dtype="complex128")
+ pair["Gdji_tmp"] = np.zeros((args.eset, noj, noi), dtype="complex128")
+
+ for k in kpcs[rank]:
+ HKU, HKD, SK = hsk(dh, k)
+ Gku = nl.inv(SK * eran.reshape(args.eset, 1, 1) - HKU)
+ Gkd = nl.inv(SK * eran.reshape(args.eset, 1, 1) - HKD)
+ for pair in pairs:
+ phase = np.exp(1j * np.dot(np.dot(k, dh.rcell), pair["Rij"]))
+ si, sj = pair["slij"]
+ pair["Guij_tmp"] += Gku[:, si, sj] * phase * wk
+ pair["Gdji_tmp"] += Gkd[:, sj, si] / phase * wk
+
+ # summ reduce partial results of mpi nodes
+ for pair in pairs:
+ comm.Reduce(pair["Guij_tmp"], pair["Guij"], root=root_node)
+ comm.Reduce(pair["Gdji_tmp"], pair["Gdji"], root=root_node)
+
+ if rank == root_node:
+ for pair in pairs:
+ i, j = pair["aiij"]
+ # The Szunyogh-Lichtenstein formula
+ pair["Jijz"] = np.trace(
+ (Hs[i] @ pair["Guij"]) @ (Hs[j] @ pair["Gdji"]), axis1=1, axis2=2
+ )
+ # evaluation of the contour integral
+ pair["Jij"] = np.trapz(np.imag(pair["Jijz"] * cont.we) / (2 * np.pi))
+ end = timer()
+
+ # ----------------------------------------------------------------------
+ # and saveing output of the calculation
+ np.savetxt(
+ args.outfile,
+ np.array(
+ [
+ [nl.norm(p["rij"]), p["Jij"] * sisl.unit_convert("eV", "Ry") * 1000]
+ + p["aiij"]
+ + list(p["offset"])
+ + list(p["rij"])
+ for p in pairs
+ ],
+ dtype=object,
+ ),
+ header=str(args)
+ + "\nnumber of cores = "
+ + str(size)
+ + "\ntime of calculation = "
+ + str(end - start)
+ + "\nnorm(rij),Jij[mRy],aiij,offset,rij",
+ fmt="%s",
+ )
diff --git a/src/grogu/useful.py b/src/grogu/useful.py
new file mode 100644
index 0000000..a898a7e
--- /dev/null
+++ b/src/grogu/useful.py
@@ -0,0 +1,167 @@
+# Copyright (c) [2024] [Daniel Pozsar]
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from itertools import permutations, product
+
+import numpy as np
+from scipy.special import roots_legendre
+
+
+# define some useful functions
+def hsk(dh, k=(0, 0, 0)):
+ """
+ One way to speed up Hk and Sk generation
+ """
+ k = np.asarray(k, np.float64) # this two conversion lines
+ k.shape = (-1,) # are from the sisl source
+
+ # this generates the list of phases
+ phases = np.exp(-1j * np.dot(np.dot(np.dot(dh.rcell, k), dh.cell), dh.sc.sc_off.T))
+
+ HKU = np.einsum("abc,c->ab", dh.hup, phases)
+ HKD = np.einsum("abc,c->ab", dh.hdo, phases)
+ SK = np.einsum("abc,c->ab", dh.sov, phases)
+
+ return HKU, HKD, SK
+
+
+def make_contour(emin=-20, emax=0.0, enum=42, p=150):
+ """
+ A more sophisticated contour generator
+ """
+
+ x, wl = roots_legendre(enum)
+ R = (emax - emin) / 2
+ z0 = (emax + emin) / 2
+ y1 = -np.log(1 + np.pi * p)
+ y2 = 0
+
+ y = (y2 - y1) / 2 * x + (y2 + y1) / 2
+ phi = (np.exp(-y) - 1) / p
+ ze = z0 + R * np.exp(1j * phi)
+ we = -(y2 - y1) / 2 * np.exp(-y) / p * 1j * (ze - z0) * wl
+
+ class ccont:
+ # just an empty container class
+ pass
+
+ cont = ccont()
+ cont.R = R
+ cont.z0 = z0
+ cont.ze = ze
+ cont.we = we
+ cont.enum = enum
+
+ return cont
+
+
+def make_kset(dirs="xyz", NUMK=20):
+ """
+ Simple k-grid generator. Depending on the value of the dirs
+ argument k sampling in 1,2 or 3 dimensions is generated.
+ If dirs argument does not contain either of x,y or z
+ a kset of a single k-pont at the origin is returend.
+ """
+ if not (sum([d in dirs for d in "xyz"])):
+ return np.array([[0, 0, 0]])
+
+ kran = len(dirs) * [np.linspace(0, 1, NUMK, endpoint=False)]
+ mg = np.meshgrid(*kran)
+ dirsdict = dict()
+
+ for d in enumerate(dirs):
+ dirsdict[d[1]] = mg[d[0]].flatten()
+ for d in "xyz":
+ if not (d in dirs):
+ dirsdict[d] = 0 * dirsdict[dirs[0]]
+ kset = np.array([dirsdict[d] for d in "xyz"]).T
+
+ return kset
+
+
+def make_atran(nauc, dirs="xyz", dist=1):
+ """
+ Simple pair generator. Depending on the value of the dirs
+ argument sampling in 1,2 or 3 dimensions is generated.
+ If dirs argument does not contain either of x,y or z
+ a single pair is returend.
+ """
+ if not (sum([d in dirs for d in "xyz"])):
+ return (0, 0, [1, 0, 0])
+
+ dran = len(dirs) * [np.arange(-dist, dist + 1)]
+ mg = np.meshgrid(*dran)
+ dirsdict = dict()
+
+ for d in enumerate(dirs):
+ dirsdict[d[1]] = mg[d[0]].flatten()
+ for d in "xyz":
+ if not (d in dirs):
+ dirsdict[d] = 0 * dirsdict[dirs[0]]
+
+ ucran = np.array([dirsdict[d] for d in "xyz"]).T
+ atran = []
+ for i, j in list(product(range(nauc), repeat=2)):
+ for u in ucran:
+ if (abs(i - j) + sum(abs(u))) > 0:
+ atran.append((i, j, list(u)))
+
+ return atran
+
+
+def add(x, y):
+ """The sum of two numbers for testing.
+
+ This function adds to numbers together. I only created this for testing documentation and examples.
+
+ Parameters
+ ----------
+ x : float
+ First number
+ y : float
+ Second number added to `x`
+
+ Returns
+ -------
+ sum : int
+ The sum of the inputs
+
+ See Also
+ --------
+ numpy.add : Adds more than two numbers.
+
+ Notes
+ -----
+ We can create some latex notes here [1]_ :
+
+ .. math:: a + b = c
+
+ References
+ ----------
+ .. [1] https://numpydoc.readthedocs.io/en/latest/format.html
+
+
+ Examples
+ --------
+ >>> add(1, 2)
+ 3
+
+ """
+ return x + y
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..93d0ca4
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,19 @@
+# Copyright (c) [2024] [Daniel Pozsar]
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
diff --git a/tests/test_jij.py b/tests/test_jij.py
new file mode 100644
index 0000000..93d0ca4
--- /dev/null
+++ b/tests/test_jij.py
@@ -0,0 +1,19 @@
+# Copyright (c) [2024] [Daniel Pozsar]
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
diff --git a/tests/test_useful.py b/tests/test_useful.py
new file mode 100644
index 0000000..56c5d99
--- /dev/null
+++ b/tests/test_useful.py
@@ -0,0 +1,29 @@
+# Copyright (c) [2024] [Daniel Pozsar]
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import pytest
+from useful import add
+
+
+@pytest.mark.parametrize(
+ "a, b, c, expected_out", [(1, 2, 3, True), (6, 7, 13, True), (1, 2, 10, False)]
+)
+def test_add(a, b, c, expected_out):
+ assert (add(a, b) == c) == expected_out