diff --git a/doc/make.py b/doc/make.py new file mode 100644 index 00000000..ca8dfd4a --- /dev/null +++ b/doc/make.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +""" +Python script for building documentation. + +To build the docs you must have all optional dependencies for pyscfad +installed. See the installation instructions for a list of these. + +Usage +----- + $ python make.py clean + $ python make.py html + $ python make.py latex +""" + +import argparse +import importlib +import os +import shutil +import subprocess +import sys + +import docutils +import docutils.parsers.rst + +DOC_PATH = os.path.dirname(os.path.abspath(__file__)) +SOURCE_PATH = os.path.join(DOC_PATH, "source") +BUILD_PATH = os.path.join(DOC_PATH, "build") + + +class DocBuilder: + """ + Class to wrap the different commands of this script. + + All public methods of this class can be called as parameters of the + script. + """ + + def __init__( + self, + num_jobs="auto", + verbosity=0, + warnings_are_errors=False, + ) -> None: + self.num_jobs = num_jobs + self.verbosity = verbosity + self.warnings_are_errors = warnings_are_errors + + @staticmethod + def _run_os(*args) -> None: + """ + Execute a command as a OS terminal. + + Parameters + ---------- + *args : list of str + Command and parameters to be executed + + Examples + -------- + >>> DocBuilder()._run_os("python", "--version") + """ + subprocess.check_call(args, stdout=sys.stdout, stderr=sys.stderr) + + def _sphinx_build(self, kind: str): + """ + Call sphinx to build documentation. + + Attribute `num_jobs` from the class is used. + + Parameters + ---------- + kind : {'html', 'latex', 'linkcheck'} + + Examples + -------- + >>> DocBuilder(num_jobs=4)._sphinx_build("html") + """ + if kind not in ("html", "latex", "linkcheck"): + raise ValueError(f"kind must be html, latex or linkcheck, not {kind}") + + cmd = ["sphinx-build", "-b", kind] + if self.num_jobs: + cmd += ["-j", self.num_jobs] + if self.warnings_are_errors: + cmd += ["-W", "--keep-going"] + if self.verbosity: + cmd.append(f"-{'v' * self.verbosity}") + cmd += [ + "-d", + os.path.join(BUILD_PATH, "doctrees"), + SOURCE_PATH, + os.path.join(BUILD_PATH, kind), + ] + return subprocess.call(cmd) + + def _open_browser(self, single_doc_html) -> None: + """ + Open a browser tab showing single + """ + url = os.path.join("file://", DOC_PATH, "build", "html", single_doc_html) + webbrowser.open(url, new=2) + + def _get_page_title(self, page): + """ + Open the rst file `page` and extract its title. + """ + fname = os.path.join(SOURCE_PATH, f"{page}.rst") + doc = docutils.utils.new_document( + "", + docutils.frontend.get_default_settings(docutils.parsers.rst.Parser), + ) + with open(fname, encoding="utf-8") as f: + data = f.read() + + parser = docutils.parsers.rst.Parser() + # do not generate any warning when parsing the rst + with open(os.devnull, "a", encoding="utf-8") as f: + doc.reporter.stream = f + parser.parse(data, doc) + + section = next( + node for node in doc.children if isinstance(node, docutils.nodes.section) + ) + title = next( + node for node in section.children if isinstance(node, docutils.nodes.title) + ) + + return title.astext() + + def html(self): + """ + Build HTML documentation. + """ + ret_code = self._sphinx_build("html") + zip_fname = os.path.join(BUILD_PATH, "html", "pyscfad.zip") + if os.path.exists(zip_fname): + os.remove(zip_fname) + return ret_code + + def latex(self, force=False): + """ + Build PDF documentation. + """ + if sys.platform == "win32": + sys.stderr.write("latex build has not been tested on windows\n") + else: + ret_code = self._sphinx_build("latex") + os.chdir(os.path.join(BUILD_PATH, "latex")) + if force: + for i in range(3): + self._run_os("pdflatex", "-interaction=nonstopmode", "pyscfad.tex") + raise SystemExit( + "You should check the file " + '"build/latex/pyscfad.pdf" for problems.' + ) + self._run_os("make") + return ret_code + + def latex_forced(self): + """ + Build PDF documentation with retries to find missing references. + """ + return self.latex(force=True) + + @staticmethod + def clean() -> None: + """ + Clean documentation generated files. + """ + shutil.rmtree(BUILD_PATH, ignore_errors=True) + shutil.rmtree(os.path.join(SOURCE_PATH, "reference", "api"), ignore_errors=True) + + def zip_html(self) -> None: + """ + Compress HTML documentation into a zip file. + """ + zip_fname = os.path.join(BUILD_PATH, "html", "pyscfad.zip") + if os.path.exists(zip_fname): + os.remove(zip_fname) + dirname = os.path.join(BUILD_PATH, "html") + fnames = os.listdir(dirname) + os.chdir(dirname) + self._run_os("zip", zip_fname, "-r", "-q", *fnames) + + def linkcheck(self): + """ + Check for broken links in the documentation. + """ + return self._sphinx_build("linkcheck") + + +def main(): + cmds = [method for method in dir(DocBuilder) if not method.startswith("_")] + + joined = ",".join(cmds) + argparser = argparse.ArgumentParser( + description="pyscfad documentation builder", epilog=f"Commands: {joined}" + ) + + joined = ", ".join(cmds) + argparser.add_argument( + "command", nargs="?", default="html", help=f"command to run: {joined}" + ) + argparser.add_argument( + "--num-jobs", default="auto", help="number of jobs used by sphinx-build" + ) + argparser.add_argument( + "--python-path", type=str, default=os.path.dirname(DOC_PATH), help="path" + ) + argparser.add_argument( + "-v", + action="count", + dest="verbosity", + default=0, + help=( + "increase verbosity (can be repeated), " + "passed to the sphinx build command" + ), + ) + argparser.add_argument( + "--warnings-are-errors", + "-W", + action="store_true", + help="fail if warnings are raised", + ) + args = argparser.parse_args() + + if args.command not in cmds: + joined = ", ".join(cmds) + raise ValueError(f"Unknown command {args.command}. Available options: {joined}") + + # Below we update both os.environ and sys.path. The former is used by + # external libraries (namely Sphinx) to compile this module and resolve + # the import of `python_path` correctly. The latter is used to resolve + # the import within the module, injecting it into the global namespace + os.environ["PYTHONPATH"] = args.python_path + sys.path.insert(0, args.python_path) + globals()["pyscfad"] = importlib.import_module("pyscfad") + + # Set the matplotlib backend to the non-interactive Agg backend for all + # child processes. + #os.environ["MPLBACKEND"] = "module://matplotlib.backends.backend_agg" + + builder = DocBuilder( + args.num_jobs, + args.verbosity, + args.warnings_are_errors, + ) + return getattr(builder, args.command)() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/doc/source/_static/css/custom.css b/doc/source/_static/css/custom.css new file mode 100644 index 00000000..53d86ec4 --- /dev/null +++ b/doc/source/_static/css/custom.css @@ -0,0 +1,47 @@ +/* custom CSS classes (used in docs/user_guide/extending.rst) NOTE: the begin + * and end markers are necessary for partial file includes! don't remove them. + */ + +/* begin-custom-color/* /custom.css */ + +div.admonition.admonition-olive { + border-color: hsl(60, 100%, 25%); +} +div.admonition.admonition-olive > .admonition-title { + background-color: hsl(60, 100%, 14%); + color: white; +} +div.admonition.admonition-olive > .admonition-title:after { + color: hsl(60, 100%, 25%); +} +/* end-custom-color */ + +/* begin-custom-icon/* /custom.css */ + +div.admonition.admonition-icon > .admonition-title:after { + content: "\f24e"; /* the fa-scale icon */ +} +/* end-custom-icon */ + +/* begin-custom-youtube/* /custom.css */ + +div.admonition.admonition-youtube { + border-color: hsl(0, 100%, 50%); /* YouTube red */ +} +div.admonition.admonition-youtube > .admonition-title { + background-color: hsl(0, 99%, 18%); + color: white; +} +div.admonition.admonition-youtube > .admonition-title:after { + color: hsl(0, 100%, 50%); + content: "\f26c"; /* fa-solid fa-tv */ +} +/* end-custom-youtube */ + +/* fix for project names that are parsed as links: the whole card is a link so + * don't format the project name itself like a text link + */ +div.downstream-project-links a { + text-decoration: none !important; + color: inherit !important; +} diff --git a/doc/source/_static/custom-icon.js b/doc/source/_static/custom-icon.js new file mode 100644 index 00000000..cd949b3b --- /dev/null +++ b/doc/source/_static/custom-icon.js @@ -0,0 +1,16 @@ +/******************************************************************************* + * Set a custom icon for pypi as it's not available in the fa built-in brands + */ +FontAwesome.library.add( + (faListOldStyle = { + prefix: "fa-custom", + iconName: "pypi", + icon: [ + 17.313, // viewBox width + 19.807, // viewBox height + [], // ligature + "e001", // unicode codepoint - private use area + "m10.383 0.2-3.239 1.1769 3.1883 1.1614 3.239-1.1798zm-3.4152 1.2411-3.2362 1.1769 3.1855 1.1614 3.2369-1.1769zm6.7177 0.00281-3.2947 1.2009v3.8254l3.2947-1.1988zm-3.4145 1.2439-3.2926 1.1981v3.8254l0.17548-0.064132 3.1171-1.1347zm-6.6564 0.018325v3.8247l3.244 1.1805v-3.8254zm10.191 0.20931v2.3137l3.1777-1.1558zm3.2947 1.2425-3.2947 1.1988v3.8254l3.2947-1.1988zm-8.7058 0.45739c0.00929-1.931e-4 0.018327-2.977e-4 0.027485 0 0.25633 0.00851 0.4263 0.20713 0.42638 0.49826 1.953e-4 0.38532-0.29327 0.80469-0.65542 0.93662-0.36226 0.13215-0.65608-0.073306-0.65613-0.4588-6.28e-5 -0.38556 0.2938-0.80504 0.65613-0.93662 0.068422-0.024919 0.13655-0.038114 0.20156-0.039466zm5.2913 0.78369-3.2947 1.1988v3.8247l3.2947-1.1981zm-10.132 1.239-3.2362 1.1769 3.1883 1.1614 3.2362-1.1769zm6.7177 0.00213-3.2926 1.2016v3.8247l3.2926-1.2009zm-3.4124 1.2439-3.2947 1.1988v3.8254l3.2947-1.1988zm-6.6585 0.016195v3.8275l3.244 1.1805v-3.8254zm16.9 0.21143-3.2947 1.1988v3.8247l3.2947-1.1981zm-3.4145 1.2411-3.2926 1.2016v3.8247l3.2926-1.2009zm-3.4145 1.2411-3.2926 1.2016v3.8247l3.2926-1.2009zm-3.4124 1.2432-3.2947 1.1988v3.8254l3.2947-1.1988zm-6.6585 0.019027v3.8247l3.244 1.1805v-3.8254zm13.485 1.4497-3.2947 1.1988v3.8247l3.2947-1.1981zm-3.4145 1.2411-3.2926 1.2016v3.8247l3.2926-1.2009zm2.4018 0.38127c0.0093-1.83e-4 0.01833-3.16e-4 0.02749 0 0.25633 0.0085 0.4263 0.20713 0.42638 0.49826 1.97e-4 0.38532-0.29327 0.80469-0.65542 0.93662-0.36188 0.1316-0.65525-0.07375-0.65542-0.4588-1.95e-4 -0.38532 0.29328-0.80469 0.65542-0.93662 0.06842-0.02494 0.13655-0.03819 0.20156-0.03947zm-5.8142 0.86403-3.244 1.1805v1.4201l3.244 1.1805z", // svg path (https://simpleicons.org/icons/pypi.svg) + ], + }) +); diff --git a/doc/source/_static/index_api.svg b/doc/source/_static/index_api.svg new file mode 100644 index 00000000..69f7ba1d --- /dev/null +++ b/doc/source/_static/index_api.svg @@ -0,0 +1,97 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/doc/source/_static/index_api.svg:Zone.Identifier b/doc/source/_static/index_api.svg:Zone.Identifier new file mode 100644 index 00000000..1bf0b28e --- /dev/null +++ b/doc/source/_static/index_api.svg:Zone.Identifier @@ -0,0 +1,3 @@ +[ZoneTransfer] +ZoneId=3 +HostUrl=https://github.com/ diff --git a/doc/source/_static/index_contribute.svg b/doc/source/_static/index_contribute.svg new file mode 100644 index 00000000..de3d9023 --- /dev/null +++ b/doc/source/_static/index_contribute.svg @@ -0,0 +1,76 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/doc/source/_static/index_contribute.svg:Zone.Identifier b/doc/source/_static/index_contribute.svg:Zone.Identifier new file mode 100644 index 00000000..1bf0b28e --- /dev/null +++ b/doc/source/_static/index_contribute.svg:Zone.Identifier @@ -0,0 +1,3 @@ +[ZoneTransfer] +ZoneId=3 +HostUrl=https://github.com/ diff --git a/doc/source/_static/index_getting_started.svg b/doc/source/_static/index_getting_started.svg new file mode 100644 index 00000000..2d36622c --- /dev/null +++ b/doc/source/_static/index_getting_started.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/doc/source/_static/index_getting_started.svg:Zone.Identifier b/doc/source/_static/index_getting_started.svg:Zone.Identifier new file mode 100644 index 00000000..1bf0b28e --- /dev/null +++ b/doc/source/_static/index_getting_started.svg:Zone.Identifier @@ -0,0 +1,3 @@ +[ZoneTransfer] +ZoneId=3 +HostUrl=https://github.com/ diff --git a/doc/source/_static/index_user_guide.svg b/doc/source/_static/index_user_guide.svg new file mode 100644 index 00000000..bd170535 --- /dev/null +++ b/doc/source/_static/index_user_guide.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/doc/source/_static/index_user_guide.svg:Zone.Identifier b/doc/source/_static/index_user_guide.svg:Zone.Identifier new file mode 100644 index 00000000..1bf0b28e --- /dev/null +++ b/doc/source/_static/index_user_guide.svg:Zone.Identifier @@ -0,0 +1,3 @@ +[ZoneTransfer] +ZoneId=3 +HostUrl=https://github.com/ diff --git a/doc/source/_static/logo.svg b/doc/source/_static/logo.svg new file mode 100644 index 00000000..5d0ca25b --- /dev/null +++ b/doc/source/_static/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/doc/source/_static/pyscfad_logo.svg b/doc/source/_static/pyscfad_logo.svg new file mode 100644 index 00000000..903cf92e --- /dev/null +++ b/doc/source/_static/pyscfad_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/api_reference/index.rst b/doc/source/api_reference/index.rst new file mode 100644 index 00000000..452a44c0 --- /dev/null +++ b/doc/source/api_reference/index.rst @@ -0,0 +1,5 @@ +.. _api_reference: + +============= +API reference +============= diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 00000000..52e9bd88 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,157 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import os +import sys +import warnings +import inspect + +import pyscfad + +project = 'PySCFAD' +copyright = '2021-2024, Xing Zhang' +author = 'Xing Zhang' + +version = str(pyscfad.__version__) +release = version + +language = 'en' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "IPython.sphinxext.ipython_directive", + "IPython.sphinxext.ipython_console_highlighting", + "matplotlib.sphinxext.plot_directive", + "numpydoc", + "sphinx_copybutton", + "sphinx_design", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.coverage", + "sphinx.ext.doctest", + "sphinx.ext.extlinks", + "sphinx.ext.ifconfig", + "sphinx.ext.intersphinx", + "sphinx.ext.linkcode", + "sphinx.ext.mathjax", + "sphinx.ext.todo", + "nbsphinx", + "sphinxemoji.sphinxemoji", # emoji +] + +templates_path = ['_templates'] +source_suffix = [".rst"] +source_encoding = "utf-8" +master_doc = "index" +exclude_patterns = [] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'pydata_sphinx_theme' +html_logo = "_static/pyscfad_logo.svg" +html_favicon = "_static/pyscfad_logo.svg" +html_sourcelink_suffix = "" +html_last_updated_fmt = "" + +html_context = { + "github_user": "fishjojo", + "github_repo": "pyscfad", + "github_version": "doc", + "doc_path": "doc", +} + +html_static_path = ['_static'] +#html_css_files = ["css/pyscfad.css"] +html_js_files = ["custom-icon.js"] + +#if ".dev" in version: +# switcher_version = "dev" +#else: +# # only keep major.minor version number to match versions.json +# switcher_version = ".".join(version.split(".")[:2]) + +html_theme_options = { + "logo": {"text": "pyscfad"}, + "use_edit_page_button": True, + "navbar_align": "left", + "github_url": "https://github.com/fishjojo/pyscfad", + "navbar_end": ["theme-switcher", "navbar-icon-links"], + "icon_links": [ + { + "name": "PyPI", + "url": "https://pypi.org/project/pyscfad", + "icon": "fa-custom fa-pypi", + }, + ], + #"switcher": { + # "json_url": "_static/version.json", + # "version_match": switcher_version, + #}, + "show_version_warning_banner": True, + "footer_start": ["copyright"], + "footer_center": ["sphinx-version"], +} + +# based on numpy doc/source/conf.py +def linkcode_resolve(domain, info) -> str | None: + """ + Determine the URL corresponding to Python object + """ + if domain != "py": + return None + + modname = info["module"] + fullname = info["fullname"] + + submod = sys.modules.get(modname) + if submod is None: + return None + + obj = submod + for part in fullname.split("."): + try: + with warnings.catch_warnings(): + # Accessing deprecated objects will generate noisy warnings + warnings.simplefilter("ignore", FutureWarning) + obj = getattr(obj, part) + except AttributeError: + return None + + try: + fn = inspect.getsourcefile(inspect.unwrap(obj)) + except TypeError: + try: # property + fn = inspect.getsourcefile(inspect.unwrap(obj.fget)) + except (AttributeError, TypeError): + fn = None + if not fn: + return None + + try: + source, lineno = inspect.getsourcelines(obj) + except TypeError: + try: # property + source, lineno = inspect.getsourcelines(obj.fget) + except (AttributeError, TypeError): + lineno = None + except OSError: + lineno = None + + if lineno: + linespec = f"#L{lineno}-L{lineno + len(source) - 1}" + else: + linespec = "" + + fn = os.path.relpath(fn, start=os.path.dirname(pyscfad.__file__)) + + return ( + f"https://github.com/fishjojo/pyscfad/blob/" + f"v{pyscfad.__version__}/pyscfad/{fn}{linespec}" + ) diff --git a/doc/source/development/index.rst b/doc/source/development/index.rst new file mode 100644 index 00000000..80cfbed6 --- /dev/null +++ b/doc/source/development/index.rst @@ -0,0 +1,5 @@ +.. _development: + +=========== +Development +=========== diff --git a/doc/source/getting_started/index.rst b/doc/source/getting_started/index.rst new file mode 100644 index 00000000..48142226 --- /dev/null +++ b/doc/source/getting_started/index.rst @@ -0,0 +1,5 @@ +.. _getting_started: + +=============== +Getting started +=============== diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 00000000..109cfc80 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,78 @@ +.. PySCFAD documentation master file + +.. module:: pyscfad + +********************* +PySCFAD documentation +********************* + +**Date**: |today| **Version**: |version| + +**Useful links**: +`Binary Installers `__ | +`Source Repository `__ | +`Issues & Ideas `__ + +:mod:`pyscfad` is an extension to the `PySCF `__ package, +providing automatic differentiation capabilities for various quantum chemistry +methods. It is at an early stage of development. Comments and contributions are welcome. + +.. grid:: 1 2 4 4 + :gutter: 3 + :padding: 2 2 0 0 + :class-container: sd-text-center + + .. grid-item-card:: + :shadow: md + :link: getting_started/index + :link-type: doc + :class-header: bg-light + + Getting started |:walking:| + ^^^ + Check out the tutorials. They contain an + introduction to PySCFAD's main concepts. + + .. grid-item-card:: + :shadow: md + :link: user_guide/index + :link-type: doc + :class-header: bg-light + + User guide |:book:| + ^^^ + The user guide provides in-depth information on the + key concepts of PySCFAD with useful background information. + + .. grid-item-card:: + :shadow: md + :link: api_reference/index + :link-type: doc + :class-header: bg-light + + API reference |:computer:| + ^^^ + The reference guide contains a detailed description of + the PySCFAD API. + + .. grid-item-card:: + :shadow: md + :link: development/index + :link-type: doc + :class-header: bg-light + + Developer guide |:pencil2:| + ^^^ + The contributing guidelines will guide + you through the process of improving PySCFAD. + + +.. toctree:: + :maxdepth: 1 + :hidden: + :titlesonly: + + getting_started/index + user_guide/index + api_reference/index + development/index diff --git a/doc/source/user_guide/index.rst b/doc/source/user_guide/index.rst new file mode 100644 index 00000000..c7acda2e --- /dev/null +++ b/doc/source/user_guide/index.rst @@ -0,0 +1,5 @@ +.. _user_guide: + +========== +User Guide +==========