diff --git a/.gitignore b/.gitignore index 3144e5fa1..d13cf1e1d 100644 --- a/.gitignore +++ b/.gitignore @@ -92,6 +92,7 @@ instance/ # Sphinx documentation docs/_build/ docs/source/_build/ +docs/source/user_guide/fleurxmlmodifier_usage_examples/examples # PyBuilder target/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 03428764d..d50f5c99a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -96,6 +96,7 @@ repos: masci_tools/io/fleurxmlmodifier.py| masci_tools/io/common_functions.py| masci_tools/util/xml/.*py| + masci_tools/util/sphinxext/.*py| masci_tools/util/lockable_containers.py| masci_tools/util/case_insensitive_dict.py| masci_tools/util/schema_dict_util.py| diff --git a/docs/Makefile b/docs/Makefile index d9c3654dd..3cc0be0e2 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -6,6 +6,7 @@ SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build +PYTHON3 ?= python3 # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 @@ -14,7 +15,7 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) sou # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext +.PHONY: help clean modifier-docstrings html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @@ -41,7 +42,10 @@ help: clean: -rm -r $(BUILDDIR) -html: +modifier-docstrings: + $(PYTHON3) ../utils/write_fleurxmlmodifier_docstrings.py + +html: modifier-docstrings $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." diff --git a/docs/source/conf.py b/docs/source/conf.py index a63b428a7..6774f9576 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -4,29 +4,13 @@ # # This file is execfile()d with the current directory set to its # containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. import masci_tools import os -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('../aiida_fleur')) # -- General configuration ------------------------------------------------ -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = ['myst_nb', 'sphinx.ext.autodoc', 'sphinx.ext.doctest', @@ -37,8 +21,10 @@ 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx', 'sphinx_autodoc_typehints', + 'sphinx_design', 'sphinx_copybutton', - 'sphinx_click'] + 'sphinx_click', + 'masci_tools.util.sphinxext'] intersphinx_mapping = {'numpy': ('https://numpy.org/doc/stable/', None), 'python': ('https://docs.python.org/3', None), @@ -57,8 +43,11 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -# The encoding of source files. -#source_encoding = 'utf-8-sig' +usage_examples_conf = { + 'template_dirs': ['user_guide/fleurxmlmodifier_usage_examples'] +} + +autodoc_mock_imports = ['bokeh', '_typeshed'] # The master toctree document. master_doc = 'index' @@ -67,189 +56,30 @@ project = 'Masci-tools' copyright = '2016-2021, Forschungszentrum Jülich GmbH, PGI-1/IAS-1 Quantum Theory of Materials' -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# - version = '.'.join(masci_tools.__version__.split('.')[:2]) # The full version, including alpha/beta/rc tags. release = masci_tools.__version__ author = 'The JuDFT team' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - -# -- Options for HTML output ---------------------------------------------- +# -- Options for HTML output --------------------------------------------- -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. htmlhelp_basename = 'masci-toolsdoc' +# These folders are copied to the documentation's HTML output +html_static_path = ['_static'] - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'masci-tools.tex', 'Masci-tools Documentation', - 'The JuDFT team', 'manual'), +# These paths are either relative to html_static_path +# or fully qualified paths (eg. https://...) +html_css_files = [ + 'theme_overrides.css', ] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - # on_rtd is whether we are on readthedocs.org, this line of code grabbed # from docs.readthedocs.org -# NOTE: it is needed to have these lines before load_dbenv() -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' # python 3 -#on_rtd = os.environ.read('READTHEDOCS', None) == 'True' # python 2 - +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' if not on_rtd: # only import and set the theme if we're building docs locally try: import sphinx_rtd_theme @@ -259,7 +89,16 @@ # No sphinx_rtd_theme installed pass -autodoc_mock_imports = ['bokeh', '_typeshed'] + +# -- Options for LaTeX output --------------------------------------------- + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'masci-tools.tex', 'Masci-tools Documentation', + 'The JuDFT team', 'manual'), +] # -- Options for manual page output --------------------------------------- @@ -271,10 +110,6 @@ ['The JuDFT team'], 1) ] -# If true, show URL addresses after external links. -#man_show_urls = False - - # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples @@ -286,18 +121,6 @@ 'Miscellaneous'), ] -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. @@ -306,39 +129,6 @@ epub_publisher = author epub_copyright = copyright -# The language of the text. It defaults to the language option -# or en if the language is not set. -#epub_language = '' - -# The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -#epub_identifier = '' - -# A unique identification for the text. -#epub_uid = '' - -# A tuple containing the cover image and cover page html template filenames. -#epub_cover = () - -# HTML files that should be inserted before the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_pre_files = [] - -# HTML files shat should be inserted after the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_post_files = [] - -# A list of files that should not be packed into the epub file. -#epub_exclude_files = [] - -# The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 - - - # Warnings to ignore when using the -n (nitpicky) option # We should ignore any python built-in exception, for instance nitpick_ignore = [ @@ -406,13 +196,3 @@ ('py:data', 'masci_tools.io.parsers.fleur.fleur_outxml_parser.F'), ('py:class', 'np.ndarray') ] - - -# These folders are copied to the documentation's HTML output -html_static_path = ['_static'] - -# These paths are either relative to html_static_path -# or fully qualified paths (eg. https://...) -html_css_files = [ - 'theme_overrides.css', -] diff --git a/docs/source/reference/module_guide/index.md b/docs/source/reference/module_guide/index.md index 019a03db9..23dd0b045 100644 --- a/docs/source/reference/module_guide/index.md +++ b/docs/source/reference/module_guide/index.md @@ -5,4 +5,5 @@ code tools +sphinxext ``` diff --git a/docs/source/reference/module_guide/sphinxext.md b/docs/source/reference/module_guide/sphinxext.md new file mode 100644 index 000000000..75cffb70a --- /dev/null +++ b/docs/source/reference/module_guide/sphinxext.md @@ -0,0 +1,6 @@ +# Extensions to Sphinx + +```{eval-rst} +.. automodule:: masci_tools.util.sphinxext + :members: +``` diff --git a/docs/source/user_guide/fleurxmlmodifier.md b/docs/source/user_guide/fleurxmlmodifier.md index 72a556a64..73ebfd2ef 100644 --- a/docs/source/user_guide/fleurxmlmodifier.md +++ b/docs/source/user_guide/fleurxmlmodifier.md @@ -38,65 +38,517 @@ new_xmltree, _ = fm.modify_xmlfile('/path/to/original/inp.xml') #Apply last task or all tasks from the list of changes. (modify-methods)= +### Modification methods + +::::::{dropdown} Simple changes +:open: +:color: info +:animate: fade-in-slide-down + +:::::{grid} 2 + +::::{grid-item-card} **set_inpchanges** +:text-align: center +Change the value of multiple text or attribute values at once. ++++ +:::{button-ref} set_inpchanges +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **shift_value** +:text-align: center +Shift or multiply the value of multiple text or attribute values at once. ++++ +:::{button-ref} shift_value +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: +::::: + +:::::: + +::::::{dropdown} Modifying atom species +:open: +:color: info +:animate: fade-in-slide-down + +:::::{grid} 2 + +::::{grid-item-card} **set_species** +:text-align: center +Change parameters or add new tags in atomic `species` elements. +For example changing the MT radius or adding DFT+U. ++++ +:::{button-ref} set_species +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **set_species_label** +:text-align: center +Change parameters or add new tags in atomic `species` of a +specific atom. The atom is identified by it's label. ++++ +:::{button-ref} set_species_label +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **clone_species** +:text-align: center +Duplicate a given species element with a different name. ++++ +:::{button-ref} clone_species +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: +::::: +:::::: + +::::::{dropdown} Modifying atom groups +:open: +:color: info +:animate: fade-in-slide-down + +:::::{grid} 2 + +::::{grid-item-card} **set_atomgroup** +:text-align: center +Change parameters or add new elements in `atomGroup` elements, +i.e. the elements containing the symmetry equivalent atoms. ++++ +:::{button-ref} set_atomgroup +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **set_atomgroup_label** +:text-align: center +Change parameters or add new elements in `atomGroup` elements, +i.e. the elements containing the symmetry equivalent atoms. +The group to modify is identified by a given label of an atom. ++++ +:::{button-ref} set_atomgroup_label +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **switch_species** +:text-align: center +Change the species of a given atom group. ++++ +:::{button-ref} switch_species +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **switch_species_label** +:text-align: center +Change the species of a atom group containing a given atom. ++++ +:::{button-ref} switch_species +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::: +:::::: + +::::::{dropdown} Modifying kpoint sets +:open: +:color: info +:animate: fade-in-slide-down + +:::::{grid} 2 + +::::{grid-item-card} **switch_kpointset** +:text-align: center +Switch the used kpoint set {bdg-success-line}`MaX 5.0 or newer` ++++ +:::{button-ref} switch_kpointset +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **set_kpointlist** +:text-align: center +Create a k-point list from a list of coordinates and weights. ++++ +:::{button-ref} set_kpointlist +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **set_nkpts** +:text-align: center +Set the number of kpts (`kpointCount`) {bdg-danger-line}`MaX 4.0 or older` ++++ +:::{button-ref} set_nkpts +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **set_kpath** +:text-align: center +Set a explicit path for bandstructure {bdg-danger-line}`MaX 4.0 or older` ++++ +:::{button-ref} set_kpath +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::: +:::::: + +::::::{dropdown} Setting generic XML elements and attributes +:open: +:color: info +:animate: fade-in-slide-down + +:::::{grid} 2 + +::::{grid-item-card} **set_attrib_value** +:text-align: center +Set abitrary XML attribute values ++++ +:::{button-ref} set_attrib_value +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **set_first_attrib_value** +:text-align: center +Set abitrary XML attribute values for the +first occurrence of the given attribute in the XML tree ++++ +:::{button-ref} set_first_attrib_value +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + + +::::{grid-item-card} **set_text** +:text-align: center +Set the text of arbitrary XML elements ++++ +:::{button-ref} set_text +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **set_first_text** +:text-align: center +Set the text of arbitrary XML elements for the first +occurrence of the given element in the XML tree ++++ +:::{button-ref} set_first_text +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **add_number_to_attrib** +:text-align: center +Add to or multiply the values of arbitrary XML +attributes ++++ +:::{button-ref} add_number_to_attrib +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **add_number_to_first_attrib** +:text-align: center +Add to or multiply the values of arbitrary XML +attributes for the first occurrence of the given attribute +in the XML tree ++++ +:::{button-ref} add_number_to_first_attrib +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **set_simple_tag** +:text-align: center +Create or change arbitrary simple XML elements, i.e. elements +without child elements ++++ +:::{button-ref} set_simple_tag +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **set_complex_tag** +:text-align: center +Create or change an arbitrary complex XML element, i.e. an element +with child elements. ++++ +:::{button-ref} set_complex_tag +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::: +:::::: + +::::::{dropdown} Manipulating the DFT+U density matrix +:color: info +:animate: fade-in-slide-down + +:::::{grid} 2 + +::::{grid-item-card} **set_nmmpmat** +:text-align: center +Initialize the `n_mmp_mat` file with a given density matrix ++++ +:::{button-ref} set_nmmpmat +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **rotate_nmmpmat** +:text-align: center +Rotate one or multiple block(s) of the `n_mmp_mat` with euler angles ++++ +:::{button-ref} rotate_nmmpmat +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **align_nmmpmat_to_sqa** +:text-align: center +Rotate one or multiple blocks of the `n_mmp_mat` with euler angles to align +with the spin-quantization axis specified. ++++ +:::{button-ref} align_nmmpmat_to_sqa +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::: +:::::: + + +::::::{dropdown} Using explicit XPath expressions for modifications +:color: warning +:icon: Alert +:animate: fade-in-slide-down + +These routines should be used with a lot of care, since they have +much less checks than all other setting functions + +:::::{grid} 2 + +::::{grid-item-card} **xml_create_tag** +:text-align: center +Create a XML element with the given name as a child of the +results of the XPath expression ++++ +:::{button-ref} xml_create_tag +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **xml_delete_tag** +:text-align: center +Delete the results of the XPath expression. ++++ +:::{button-ref} xml_delete_tag +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **xml_delete_att** +:text-align: center +Delete a give XML attribute from the results of the XPath expression. ++++ +:::{button-ref} xml_delete_att +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **xml_replace_tag** +:text-align: center +Replace the results of the XPath expression with a given XML element. ++++ +:::{button-ref} xml_replace_tag +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **xml_set_attrib_value_no_create** +:text-align: center +Set XML attribute values on the results of the XPath expression. ++++ +:::{button-ref} xml_set_attrib_value_no_create +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + +::::{grid-item-card} **xml_set_text_no_create** +:text-align: center +Set text on the results of the XPath expression. ++++ +:::{button-ref} xml_set_text_no_create +:ref-type: ref +:click-parent: +:expand: +:color: primary +:outline: +Show Examples +::: +:::: + + + +::::: +:::::: -### Modification registration methods - -The registration methods can be separated into two groups. First of all, -there are XML methods that require deeper knowledge about the structure of an `inp.xml` file. -All of them require an xpath input and start their method names start with `xml_`: - -- {py:meth}`FleurXMLModifier.xml_set_attrib_value_no_create()`: Set attributes on the result(s) of the given xpath -- {py:meth}`FleurXMLModifier.xml_set_text_no_create()`: Set text on the result(s) of the given xpath -- {py:meth}`FleurXMLModifier.xml_create_tag()`: Insert - an xml element in the xml tree on the result(s) of the given xpath. -- {py:meth}`FleurXMLModifier.xml_delete_tag()`: Delete - an xml element in the xml tree on the result(s) of the given xpath. -- {py:meth}`FleurXMLModifier.xml_delete_att()`: Delete - an attribute in the xml tree on the result(s) of the given xpath. -- {py:meth}`FleurXMLModifier.xml_replace_tag()`: Replace an xml element on the result(s) of the given xpath. - -On the other hand, there are shortcut methods that already know some paths: - -- {py:meth}`FleurXMLModifier.set_species()`: Specific - user-friendly method to change species parameters. -- {py:meth}`FleurXMLModifier.clone_species()`: Method to - create a clone of a given species with optional modifications -- {py:meth}`FleurXMLModifier.set_atomgroup()`: Specific - method to change atom group parameters. -- {py:meth}`FleurXMLModifier.set_species_label()`: Specific - user-friendly method to change a species of an atom with a certain label. -- {py:meth}`FleurXMLModifier.set_atomgroup_label()`: Specific - method to change atom group parameters of an atom with a certain label. -- {py:meth}`FleurXMLModifier.switch_species()`: user-friendly method for switching the atom species of a atom group -- {py:meth}`FleurXMLModifier.switch_species_label()`: user-friendly method for switching the atom species of a atom group with an atom with a certain label. -- {py:meth}`FleurXMLModifier.set_nkpts()`: user-friendly method for setting the `kPointCount` (**Only for MaX4 and older**) -- {py:meth}`FleurXMLModifier.set_kpath()`: user-friendly method for setting the path for a bandstructure calculations (**Only for MaX4 and older**) -- {py:meth}`FleurXMLModifier.set_kpointlist()`: user-friendly method for setting/creating a `kPointlist` from lists -- {py:meth}`FleurXMLModifier.switch_kpointset()`: user-friendly method for switching the used kpoint set in a calculation (**Only for MaX5 and newer**) -- {py:meth}`FleurXMLModifier.set_inpchanges()`: Specific - user-friendly method for easy changes of attribute key value type. -- {py:meth}`FleurXMLModifier.shift_value()`: Specific - user-friendly method to shift value of an attribute. -- {py:meth}`FleurXMLModifier.shift_value_species_label()`: Specific - user-friendly method to shift value of an attribute of an atom with a certain label. -- {py:meth}`FleurXMLModifier.set_attrib_value()`: user-friendly method for setting attributes in the xml file by specifying their name -- {py:meth}`FleurXMLModifier.set_first_attrib_value()`: user-friendly method for setting the first occurrence of an attribute in the xml file by specifying its name -- {py:meth}`FleurXMLModifier.add_number_to_attrib()`: user-friendly method for adding to or multiplying values of attributes in the xml file by specifying their name -- {py:meth}`FleurXMLModifier.add_number_to_first_attrib()`: user-friendly method for adding to or multiplying values of the first occurrence of the attribute in the xml file by specifying their name -- {py:meth}`FleurXMLModifier.set_text()`: user-friendly method for setting text on xml elements in the xml file by specifying their name -- {py:meth}`FleurXMLModifier.set_first_text()`: user-friendly method for setting the text on the first occurrence of an xml element in the xml file by specifying its name -- {py:meth}`FleurXMLModifier.set_simple_tag()`: user-friendly method for creating and setting attributes on simple xml elements (only attributes) in the xml file by specifying its name -- {py:meth}`FleurXMLModifier.set_complex_tag()`: user-friendly method for creating complex tags in the xml file by specifying its name -- {py:meth}`FleurXMLModifier.create_tag()`: User-friendly method for inserting a tag in the right place by specifying it's name -- {py:meth}`FleurXMLModifier.delete_tag()`: User-friendly method for delete a tag by specifying it's name -- {py:meth}`FleurXMLModifier.delete_att()`: User-friendly method for deleting an attribute from a tag by specifying it's name -- {py:meth}`FleurXMLModifier.replace_tag()`: User-friendly method for replacing a tag by another by specifying its name -- {py:meth}`FleurXMLModifier.set_nmmpmat()`: Specific - method for initializing or modifying the density matrix file for a LDA+U calculation (details see below) -- {py:meth}`FleurXMLModifier.rotate_nmmpmat()`: Specific - method for rotating a block/blocks of the density matrix file for a LDA+U calculation (details see below) in real space -- {py:meth}`FleurXMLModifier.align_nmmpmat_to_sqa()`: Specific - method for aligning a block/blocks of the density matrix file for a LDA+U calculation (details see below) in real space with the SQA already specified in the `inp.xml` % The figure below shows a comparison between the use of XML and shortcut methods. % diff --git a/docs/source/user_guide/fleurxmlmodifier_usage_examples/config.yml b/docs/source/user_guide/fleurxmlmodifier_usage_examples/config.yml new file mode 100644 index 000000000..2859f67e2 --- /dev/null +++ b/docs/source/user_guide/fleurxmlmodifier_usage_examples/config.yml @@ -0,0 +1,12 @@ +module-file: ../../../../masci_tools/io/fleurxmlmodifier.py +class-name: FleurXMLModifier +output-folder: examples +exclude-methods: + - fromList + - add_task_list + - task_list + - apply_modifications + - get_avail_actions + - undo + - changes + - modify_xmlfile diff --git a/docs/source/user_guide/fleurxmlmodifier_usage_examples/files/inp.xml b/docs/source/user_guide/fleurxmlmodifier_usage_examples/files/inp.xml new file mode 100644 index 000000000..6723e0d48 --- /dev/null +++ b/docs/source/user_guide/fleurxmlmodifier_usage_examples/files/inp.xml @@ -0,0 +1,105 @@ + + + + A Fleur input generator calculation with aiida + + + + + + + + .0000000000 .0000000000 .0000000000 + + + + + + + + + + + + + -0.250000 0.250000 0.000000 + 0.250000 0.250000 0.000000 + + + + + + 1 0 0 .0000000000 + 0 1 0 .0000000000 + 0 0 1 .0000000000 + + + 1 0 0 .0000000000 + 0 -1 0 .0000000000 + 0 0 1 .0000000000 + + + + + 5.301179702900000 .000000000000000 .000000000000000 + .000000000000000 7.497000033000000 .000000000000000 + .000000000000000 .000000000000000 7.992850008800000 + + + + + + + + + + + [Ne] + (3s1/2) (3p1/2) (3p3/2) (4s1/2) (3d3/2) (3d5/2) + + + + + + + + + + + + [Kr] (5s1/2) (4d3/2) (4d5/2) (4f5/2) (4f7/2) + (5p1/2) (5p3/2) (6s1/2) (5d3/2) (5d5/2) + + + + + + + + + + .0000000000 .0000000000 -.9964250044 + + + + + 1.000/2.000 1.000/2.000 .9964250044 + + + + + + + + + + + + + + 0.0 0.0 0.0 + 0.2 0.0 0.0 + + + + diff --git a/docs/source/user_guide/fleurxmlmodifier_usage_examples/files/inp_max4.xml b/docs/source/user_guide/fleurxmlmodifier_usage_examples/files/inp_max4.xml new file mode 100644 index 000000000..4e4b6ef2b --- /dev/null +++ b/docs/source/user_guide/fleurxmlmodifier_usage_examples/files/inp_max4.xml @@ -0,0 +1,101 @@ + + + A Fleur input generator calculation with aiida + + + + + + + + + + 0.0 0.0 0.0 + + + + + + + + + + + + + + + + 1 0 0 .0000000000 + 0 1 0 .0000000000 + 0 0 1 .0000000000 + + + 1 0 0 .0000000000 + 0 -1 0 .0000000000 + 0 0 1 .0000000000 + + + + + 5.301179702900000 .000000000000000 .000000000000000 + .000000000000000 7.497000033000000 .000000000000000 + .000000000000000 .000000000000000 7.992850008800000 + + + + + + + + + + + + + + [Ar] + (4s1/2) (3d3/2) (3d5/2) + + + + + + + + + + + [Xe] (4f5/2) (4f7/2) + (6s1/2) (5d3/2) (5d5/2) + + + + + + + + .0000000000 .0000000000 -.9964250044 + + + + + 1.000/2.000 1.000/2.000 .9964250044 + + + + + + + + + + + + + + + + + + diff --git a/docs/source/user_guide/fleurxmlmodifier_usage_examples/index.md b/docs/source/user_guide/fleurxmlmodifier_usage_examples/index.md new file mode 100644 index 000000000..f2c81668f --- /dev/null +++ b/docs/source/user_guide/fleurxmlmodifier_usage_examples/index.md @@ -0,0 +1,7 @@ +# Usage Examples + +```{toctree} +:glob: true + +examples/* +``` diff --git a/docs/source/user_guide/fleurxmlmodifier_usage_examples/template.md.jinja b/docs/source/user_guide/fleurxmlmodifier_usage_examples/template.md.jinja new file mode 100644 index 000000000..81d3a6f58 --- /dev/null +++ b/docs/source/user_guide/fleurxmlmodifier_usage_examples/template.md.jinja @@ -0,0 +1,73 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.11.4 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + + +```{code-cell} ipython3 +:tags: [remove-cell] +%xmode Minimal +%load_ext masci_tools +``` +({{ reference }})= +# {{ title }} + +{py:meth}`Go to the API reference ` +```{contents} Examples +:local: +``` + +{% for example in examples %} +## {{ example.title }} + +{{ example.description }} + +```{code-cell} ipython3 +:tags: [hide-cell] +:mystnb: +: code_prompt_show: "Show loading of initial XML file" +: code_prompt_hide: "Hide loading of initial XML file" + +from masci_tools.io.fleur_xml import load_inpxml +from lxml import etree + +xmltree, schema_dict = load_inpxml('../files/{{ example.inputfile }}') +xmltree +``` + +```{code-cell} ipython3 +{% if example.error %}:tags: [raises-exception]{% endif %} + +from masci_tools.io.fleurxmlmodifier import FleurXMLModifier + +fm = FleurXMLModifier() +{{ example.code }} +new_xmltree, additional_files = fm.modify_xmlfile(xmltree, validate_changes=False) +``` + +{% if not example.error %} +```{code-cell} ipython3 +:tags: [remove-input, hide-output] +:mystnb: +: code_prompt_show: "Show differences from initial XML file" +: code_prompt_hide: "Hide differences from initial XML file" +from masci_tools.util.ipython import xml_diff +xml_diff(xmltree, new_xmltree) +``` +```{code-cell} ipython3 +:tags: [remove-input, hide-output] +:mystnb: +: code_prompt_show: "Show final XML file" +: code_prompt_hide: "Hide final XML file" +new_xmltree +``` +{% endif %} +{% endfor %} diff --git a/docs/source/user_guide/index.md b/docs/source/user_guide/index.md index 810d3f54b..50084781e 100644 --- a/docs/source/user_guide/index.md +++ b/docs/source/user_guide/index.md @@ -7,6 +7,7 @@ This is the masci-tools user’s guide. fleur_parser fleurxmlmodifier +fleurxmlmodifier_usage_examples/index hdf5_parser fleur_plots kkr_plots diff --git a/masci_tools/io/fleurxmlmodifier.py b/masci_tools/io/fleurxmlmodifier.py index a973ce7dd..69433f047 100644 --- a/masci_tools/io/fleurxmlmodifier.py +++ b/masci_tools/io/fleurxmlmodifier.py @@ -412,11 +412,7 @@ def modify_xmlfile(self, return new_xmltree, additional_files def set_inpchanges(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_inpchanges()` to - the list of tasks that will be done on the xmltree. - - This method sets all the attribute and texts provided in the change_dict. + """Set the value of attributes/text occurring once in the XML tree The first occurrence of the attribute/tag is set @@ -431,6 +427,31 @@ def set_inpchanges(self, *args: Any, **kwargs: Any) -> None: 'ctail': False, 'l_ss': True } + + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_inpchanges({'itmax' : 1, 'kmax': 4.3}) + + .. usage-example:: + :title: Attribute selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_inpchanges({'itmax' : 1, 'theta': 1.57}) + + .. usage-example:: + :title: Attribute selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + in the ``path_spec`` argument + + fm.set_inpchanges({'itmax' : 1, 'theta': 1.57}, + path_spec={'theta': {'contains': 'soc'}}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_inpchanges()` to + the list of tasks that will be done on the xmltree. """ if 'change_dict' in kwargs: warnings.warn('The argument change_dict is deprecated. Use changes instead', DeprecationWarning) @@ -439,11 +460,7 @@ def set_inpchanges(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('set_inpchanges', args, kwargs)) def shift_value(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.shift_value()` to - the list of tasks that will be done on the xmltree. - - Shifts numerical values of attributes directly in the inp.xml file. + """Add/Multiply values of attributes occurring once in the XML tree The first occurrence of the attribute is shifted @@ -457,6 +474,23 @@ def shift_value(self, *args: Any, **kwargs: Any) -> None: An example of changes:: changes = {'itmax' : 1, 'dVac': -0.123} + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.shift_value({'itmax' : 5, 'mindistance': 1}) + + .. usage-example:: + :title: Relative shift + :description: Passing ``mode="rel"`` or ``mode="relative"`` will multiply + the initial attribute values with the provided number instead + of adding them + + fm.shift_value({'itmax' : 2, 'mindistance': 3}, mode="relative") + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.shift_value()` to + the list of tasks that will be done on the xmltree. """ if 'change_dict' in kwargs: warnings.warn('The argument change_dict is deprecated. Use changes instead', DeprecationWarning) @@ -465,11 +499,7 @@ def shift_value(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('shift_value', args, kwargs)) def set_species(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_species()` to - the list of tasks that will be done on the xmltree. - - Method to set parameters of a species tag of the fleur inp.xml file. + """Set the attributes of a species, specified by name position, etc. :param species_name: string, name of the specie you want to change Can be name of the species, 'all' or 'all-' (sets species with the string in the species name) @@ -485,18 +515,59 @@ def set_species(self, *args: Any, **kwargs: Any) -> None: **changes** is a python dictionary containing dictionaries that specify attributes to be set inside the certain specie. For example, if one wants to set a MT radius it - can be done via:: - - changes = {'mtSphere' : {'radius' : 2.2}} - - Another example:: - - 'changes': {'special': {'socscale': 0.0}} - - that switches SOC terms on a sertain specie. ``mtSphere``, ``atomicCutoffs``, - ``energyParameters``, ``lo``, ``electronConfig``, ``nocoParams``, ``ldaU`` and - ``special`` keys are supported. To find possible - keys of the inner dictionary please refer to the FLEUR documentation flapw.de + can be done via + + .. usage-example:: + + fm.set_species('Fe-1', {'mtSphere' : {'radius' : 2.7}}) + + .. usage-example:: + :title: Adding child elements + :description: The `changes` dictionary is not limited to setting attributes + Child elements can also be created. Notice here that if multiple + elements of a given name are allowed all previously existing elements + are deleted and replaced with the ones specified in `changes` + + fm.set_species('Fe-1', {'ldaU' : {'l' : 3, 'U': 4.0, 'J': 0.5, 'l_amf': True}, + 'lo': [{'l': 0, 'n': 6, 'type': 'SCLO'}, + {'l': 1, 'n': 6, 'type': 'SCLO'}]}) + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` as the first argument applies the changes + to all species + + fm.set_species('all', {'mtSphere' : {'radius' : 2.7}}) + + .. usage-example:: + :title: Modifying a subset of species + :description: Providing `'all-'` as the first argument applies the changes + to all species which contain the search string in it's name + + fm.set_species('all-Pt', {'mtSphere' : {'radius' : 2.7}, + 'lo': {'l': 0, 'n': 6, 'type': 'SCLO'},}) + + Example species. All these attributes tags can be modified. + Additional keys can for example be found in the FLEUR documentation flapw.de + + .. code-block:: xml + + + + + + [Ne] + (3s1/2) (3p1/2) (3p3/2) (4s1/2) (3d3/2) (3d5/2) + + + + + + + + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_species()` to + the list of tasks that will be done on the xmltree. """ if 'attributedict' in kwargs: warnings.warn('The argument attributedict is deprecated. Use changes instead', DeprecationWarning) @@ -505,9 +576,7 @@ def set_species(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('set_species', args, kwargs)) def set_species_label(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_species_label()` to - the list of tasks that will be done on the xmltree. + """Set the attributes of a species, given by an atom label occurring in it's atom type This method calls :func:`~masci_tools.util.xml.xml_setters_names.set_species()` method for a certain atom species that corresponds to an atom with a given label @@ -515,6 +584,34 @@ def set_species_label(self, *args: Any, **kwargs: Any) -> None: :param atom_label: string, a label of the atom which specie will be changed. 'all' to change all the species :param changes: a python dict specifying what you want to change. :param create: bool, if species does not exist create it and all subtags? + + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_species_label('222', {'mtSphere' : {'radius' : 2.7}}) + + .. usage-example:: + :title: Adding child elements + :description: The `changes` dictionary is not limited to setting attributes + Child elements can also be created. Notice here that if multiple + elements of a given name are allowed all previously existing elements + are deleted and replaced with the ones specified in `changes` + + fm.set_species_label('222', {'ldaU' : {'l' : 3, 'U': 4.0, 'J': 0.5, 'l_amf': True}, + 'lo': [{'l': 0, 'n': 6, 'type': 'SCLO'}, + {'l': 1, 'n': 6, 'type': 'SCLO'}]}) + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` as the first argument applies the changes + to all species + + fm.set_species_label('all', {'mtSphere' : {'radius' : 2.7}}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_species_label()` to + the list of tasks that will be done on the xmltree. """ if 'attributedict' in kwargs: warnings.warn('The argument attributedict is deprecated. Use changes instead', DeprecationWarning) @@ -523,11 +620,7 @@ def set_species_label(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('set_species_label', args, kwargs)) def clone_species(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.clone_species()` to - the list of tasks that will be done on the xmltree. - - Method to create a new species from an existing one with evtl. modifications + """Clone (and possibly change) a species in the XML tree For reference of the changes dictionary look at :py:func:`set_species()` @@ -535,16 +628,29 @@ def clone_species(self, *args: Any, **kwargs: Any) -> None: Has to correspond to one single species (no 'all'/'all-') :param new_name: new name of the cloned species :param changes: a optional python dict specifying what you want to change. + + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.clone_species('Fe-1', 'Fe-clone-1') + + .. usage-example:: + :title: Modifying the cloned species + :description: The `changes` dictionary is passed on to ``set_species`` + to modify the cloned species + + fm.clone_species('Fe-1', 'Fe-clone-1', {'mtSphere' : {'radius' : 2.7}}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.clone_species()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('clone_species', args, kwargs) self._tasks.append(ModifierTask('clone_species', args, kwargs)) def switch_species(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.switch_species()` to - the list of tasks that will be done on the xmltree. - - Method to switch the species of an atom group of the fleur inp.xml file. + """Change the species of an atom group, identified by species name, position, etc. :param new_species_name: name of the species to switch to :param position: position of an atom group to be changed. If equals to 'all', all species will be changed @@ -554,32 +660,61 @@ def switch_species(self, *args: Any, **kwargs: Any) -> None: :param changes: changes to do if the species is cloned :param filters: Dict specifying constraints to apply on the xpath. See :py:class:`~masci_tools.util.xml.xpathbuilder.XPathBuilder` for details + + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.switch_species('Pt-1', species='Fe-1') + + + .. usage-example:: + :title: Specifying the number of the atromgroup + :description: Providing the ``position`` argument will modify the n-th + atomgroup in the ``inp.xml`` file. (Indexing starts at 1) + + fm.switch_species('Pt-1', position=1) + + .. usage-example:: + :title: Create a clone of the old species + :description: Specifying a non-existent new species name and ``clone=True`` + will create a clone of the old species with that name. Additionally + the ``changes`` argument can then be used to apply changes to the new + clone + + fm.switch_species('Fe-clone-2', species='Fe-1', clone=True, changes={'mtSphere' : {'radius' : 2.7}}) + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` to either ``position`` or ``species``` + will modify the species of all atomgroups + + fm.switch_species('Fe-1', species='all') + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.switch_species()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('switch_species', args, kwargs) self._tasks.append(ModifierTask('switch_species', args, kwargs)) def switch_species_label(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.switch_species_label()` to - the list of tasks that will be done on the xmltree. - - Method to switch the species of an atom group of the fleur inp.xml file based on a label - of a contained atom + """Change the species of an atom group, identified by the lable of an atom in it's group :param atom_label: string, a label of the atom which group will be changed. 'all' to change all the groups :param new_species_name: name of the species to switch to :param clone: if True and the new species name does not exist and it corresponds to changing from one species the species will be cloned with :py:func:`clone_species()` :param changes: changes to do if the species is cloned + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.switch_species_label()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('switch_species_label', args, kwargs) self._tasks.append(ModifierTask('switch_species_label', args, kwargs)) def shift_value_species_label(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.shift_value_species_label()` to - the list of tasks that will be done on the xmltree. - + """Add/Multiply numeric attributes in a or multiple atom species Shifts the value of an attribute on a species by label if atom_label contains 'all' then applies to all species @@ -593,6 +728,31 @@ def shift_value_species_label(self, *args: Any, **kwargs: Any) -> None: Kwargs if the attribute_name does not correspond to a unique path: :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.shift_value_species_label('222', 'radius', 1) + + .. usage-example:: + :title: Relative shift + :description: Passing ``mode="rel"`` or ``mode="relative"`` will multiply + the initial attribute value with the provided number instead + of adding them + + fm.shift_value_species_label('222', 'radius', 1.2, mode="relative") + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` as the first argument applies the changes + to all atomgroups + + fm.shift_value_species_label('all', 'lmax', 2) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.shift_value_species_label()` to + the list of tasks that will be done on the xmltree. """ if 'attributename' in kwargs: warnings.warn('The argument attributename is deprecated. Use attribute_name instead', DeprecationWarning) @@ -604,11 +764,7 @@ def shift_value_species_label(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('shift_value_species_label', args, kwargs)) def set_atomgroup(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_atomgroup()` to - the list of tasks that will be done on the xmltree. - - Method to set parameters of an atom group of the fleur inp.xml file. + """Set the attributes of a atom group, specified by species name, position, etc. :param changes: a python dict specifying what you want to change. :param position: position of an atom group to be changed. If equals to 'all', all species will be changed @@ -622,6 +778,29 @@ def set_atomgroup(self, *args: Any, **kwargs: Any) -> None: can be done via:: 'changes': {'nocoParams': {'beta': val}} + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_atomgroup({'nocoParams': {'alpha': 1.57}}, species='Fe-1') + + .. usage-example:: + :title: Specifying the number of the atromgroup + :description: Providing the ``position`` argument will modify the n-th + atomgroup in the ``inp.xml`` file. (Indexing starts at 1) + + fm.set_atomgroup({'nocoParams': {'alpha': 1.57}}, position=1) + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` to either ``position`` or ``species``` + will modify all atomgroups + + fm.set_atomgroup({'nocoParams': {'alpha': 1.57}}, species='all') + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_atomgroup()` to + the list of tasks that will be done on the xmltree. """ if 'attributedict' in kwargs: warnings.warn('The argument attributedict is deprecated. Use changes instead', DeprecationWarning) @@ -633,11 +812,9 @@ def set_atomgroup(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('set_atomgroup', args, kwargs)) def set_atomgroup_label(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_atomgroup_label()` to - the list of tasks that will be done on the xmltree. + """Set the attributes of a atom group, given by an atom label occuring in it's definition - This method calls :func:`~masci_tools.util.xml.xml_setters_names.set_atomgroup()` + This method calls :py:func:`~masci_tools.util.xml.xml_setters_names.set_atomgroup()` method for a certain atom species that corresponds to an atom with a given label. :param atom_label: string, a label of the atom which specie will be changed. 'all' to change all the species @@ -649,6 +826,23 @@ def set_atomgroup_label(self, *args: Any, **kwargs: Any) -> None: can be done via:: 'changes': {'nocoParams': {'beta': val}} + + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_atomgroup_label('222', {'nocoParams': {'alpha': 1.57}}) + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` as the first argument applies the changes + to all species + + fm.set_atomgroup_label('all', {'nocoParams': {'alpha': 1.57}}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_atomgroup_label()` to + the list of tasks that will be done on the xmltree. """ if 'attributedict' in kwargs: warnings.warn('The argument attributedict is deprecated. Use changes instead', DeprecationWarning) @@ -660,10 +854,7 @@ def set_atomgroup_label(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('set_atomgroup_label', args, kwargs)) def create_tag(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.create_tag()` to - the list of tasks that will be done on the xmltree. - + """Create a tag with a given name in the XML tree This method creates a tag with a uniquely identified xpath under the nodes of its parent. If there are no nodes evaluated the subtags can be created with `create_parents=True` @@ -681,16 +872,62 @@ def create_tag(self, *args: Any, **kwargs: Any) -> None: Kwargs: :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.create_tag('mtnocoparams') + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.create_tag('lo') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.create_tag('lo', contains='species') + + .. usage-example:: + :title: Nested creation + :description: With ``create_parents=True`` evtl. missing parent tags are also created + + fm.create_tag('diagElements', + create_parents=True, + contains='species', + not_contains='torque') + + .. usage-example:: + :title: Creation of pre-created XML element + :description: Passing a XML element instead of a name will insert this element at the correct position + + fm.create_tag(etree.Element('lo', n='5', l='0', type='SCLO'), + contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.create_tag(etree.Element('lo', n='5', l='0', type='SCLO'), + contains='species', + filters={ + 'species': { + 'name': {'contains': 'Fe'} + } + }) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.create_tag()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('create_tag', args, kwargs) self._tasks.append(ModifierTask('create_tag', args, kwargs)) def delete_tag(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.delete_tag()` to - the list of tasks that will be done on the xmltree. - - This method deletes a tag with a uniquely identified xpath. + """Delete a tag with a given name from the XML tree :param tag: str of the tag to delete :param complex_xpath: an optional xpath to use instead of the simple xpath for the evaluation @@ -702,16 +939,46 @@ def delete_tag(self, *args: Any, **kwargs: Any) -> None: Kwargs: :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.delete_tag('expertModes') + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.delete_tag('lo') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.delete_tag('lo', contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.delete_tag('lo', + contains='species', + filters={ + 'species': { + 'name': {'contains': 'Fe'} + } + }) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.delete_tag()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('delete_tag', args, kwargs) self._tasks.append(ModifierTask('delete_tag', args, kwargs)) def delete_att(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.delete_att()` to - the list of tasks that will be done on the xmltree. - - This method deletes a attribute with a uniquely identified xpath. + """Delete an attribute with a given name from the XML tree :param name: str of the attribute to delete :param complex_xpath: an optional xpath to use instead of the simple xpath for the evaluation @@ -726,6 +993,41 @@ def delete_att(self, *args: Any, **kwargs: Any) -> None: :param not_contains: str, this string has to NOT be in the final path :param exclude: list of str, here specific types of attributes can be excluded valid values are: settable, settable_contains, other + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.delete_att('mixparam') + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.delete_att('alpha') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.delete_att('alpha', contains='nocoParams', not_contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.delete_att('alpha', + contains='nocoParams', + not_contains='species', + filters={ + 'atomGroup': { + 'species': {'not-contains': 'Fe'} + } + }) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.delete_att()` to + the list of tasks that will be done on the xmltree. """ if 'attrib_name' in kwargs: warnings.warn('The argument attrib_name is deprecated. Use name instead', DeprecationWarning) @@ -734,11 +1036,7 @@ def delete_att(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('delete_att', args, kwargs)) def replace_tag(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.replace_tag()` to - the list of tasks that will be done on the xmltree. - - This method deletes a tag with a uniquely identified xpath. + """Replace a tag with a given name with a different XML element :param tag: str of the tag to replace :param element: etree Element or string representing the XML element to replace the tag @@ -751,6 +1049,40 @@ def replace_tag(self, *args: Any, **kwargs: Any) -> None: Kwargs: :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.replace_tag('expertmodes', etree.Element('greensFunction')) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.replace_tag('lo', etree.Element('ldaU', l='3')) + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.replace_tag('lo', etree.Element('ldaU', l='3'), contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.replace_tag('lo', + etree.Element('ldaU', l='3'), + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.replace_tag()` to + the list of tasks that will be done on the xmltree. """ if 'newelement' in kwargs: warnings.warn('The argument newelement is deprecated. Use element instead', DeprecationWarning) @@ -759,9 +1091,7 @@ def replace_tag(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('replace_tag', args, kwargs)) def set_complex_tag(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_complex_tag()` to - the list of tasks that will be done on the xmltree. + """Setattributes, text and subtags of a given tag in the XML tree Function to correctly set tags/attributes for a given tag. Goes through the attributedict and decides based on the schema_dict, how the corresponding @@ -787,16 +1117,17 @@ def set_complex_tag(self, *args: Any, **kwargs: Any) -> None: Kwargs: :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_complex_tag()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('set_complex_tag', args, kwargs) self._tasks.append(ModifierTask('set_complex_tag', args, kwargs)) def set_simple_tag(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_simple_tag()` to - the list of tasks that will be done on the xmltree. + """Sets one or multiple ``simple`` tag(s) (no subtags/text) in an xmltree. - Sets one or multiple `simple` tag(s) in an xmltree. A simple tag can only hold attributes and has no + A simple tag can only hold attributes and has no subtags. The tag is specified by its name and further specification If the tag can occur multiple times all existing tags are DELETED and new ones are written. If the tag only occurs once it will automatically be created if its missing. @@ -813,17 +1144,58 @@ def set_simple_tag(self, *args: Any, **kwargs: Any) -> None: Kwargs: :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_simple_tag('soc', {'theta': 0.1, 'phi': 0.2, 'l_soc': True}) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_simple_tag('lo', [{'l': 0, 'n': 6, 'type': 'SCLO'}, + {'l': 1, 'n': 6, 'type': 'SCLO'}]) + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.set_simple_tag('lo', [{'l': 0, 'n': 6, 'type': 'SCLO'}, + {'l': 1, 'n': 6, 'type': 'SCLO'}], + contains='species') + + .. usage-example:: + :title: Nested creation + :description: With `create_parents=True` if the parents of the tag are missing they are + created + + fm.set_simple_tag('realAxis', {'ne': 1300, 'ellow': -1.0, 'elup': 1.0}, create_parents=True) + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.set_simple_tag('lo', [{'l': 0, 'n': 6, 'type': 'SCLO'}, + {'l': 1, 'n': 6, 'type': 'SCLO'}], + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_simple_tag()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('set_simple_tag', args, kwargs) self._tasks.append(ModifierTask('set_simple_tag', args, kwargs)) def set_text(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_text()` to - the list of tasks that will be done on the xmltree. + """Set the value of text of tags in the XML tree possibly occurring mutliple times - Sets the text on tags in a xmltree to a given value, specified by the name of the tag and - further specifications. By default the text will be set on all nodes returned for the specified xpath. + By default the text will be set on all nodes returned for the specified xpath. If there are no nodes under the specified xpath a tag can be created with `create=True`. The text values are converted automatically according to the types with :py:func:`~masci_tools.util.xml.converters.convert_to_xml()` if they @@ -840,17 +1212,55 @@ def set_text(self, *args: Any, **kwargs: Any) -> None: Kwargs: :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_text('kpoint', [[1,2,3], [4,5,6]]) + + .. usage-example:: + :title: Setting one value + :description: If only one value for the text is given all elements are set ot the same value + in this case only one list of three coordinates is set + + fm.set_text('kpoint', [4,4,4]) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_text('q', '1 1 1') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.set_text('q', '1 1 1', contains='spinSpiral') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.set_text('valenceConfig', + '(1s1/2)', + contains='species', + filters={ + 'species': { + './lo/@n': {'>': 4} + }}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_text()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('set_text', args, kwargs) self._tasks.append(ModifierTask('set_text', args, kwargs)) def set_first_text(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_first_text()` to - the list of tasks that will be done on the xmltree. + """Set the value of text of the first occurrence of a tag in the XML tree - Sets the text the first occurrence of a tag in a xmltree to a given value, specified by the name of the tag and - further specifications. By default the text will be set on all nodes returned for the specified xpath. + By default the text will be set on all nodes returned for the specified xpath. If there are no nodes under the specified xpath a tag can be created with `create=True`. The text values are converted automatically according to the types with :py:func:`~masci_tools.util.xml.converters.convert_to_xml()` if they @@ -866,17 +1276,47 @@ def set_first_text(self, *args: Any, **kwargs: Any) -> None: Kwargs: :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_first_text('kpoint', [1,2,3]) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_first_text('valenceConfig', '(1s1/2)') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.set_first_text('valenceConfig', '(1s1/2)', contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.set_first_text('valenceConfig', + '(1s1/2)', + contains='species', + filters={ + 'species': { + './lo/@n': {'>': 4} + }}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_first_text()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('set_first_text', args, kwargs) self._tasks.append(ModifierTask('set_first_text', args, kwargs)) def set_attrib_value(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_attrib_value()` to - the list of tasks that will be done on the xmltree. + """Set the value of an attribute possibly occurring multiple times - Sets an attribute in a xmltree to a given value, specified by its name and further - specifications. If there are no nodes under the specified xpath a tag can be created with `create=True`. The attribute values are converted automatically according to the types of the attribute with :py:func:`~masci_tools.util.xml.converters.convert_to_xml()` if they @@ -896,6 +1336,40 @@ def set_attrib_value(self, *args: Any, **kwargs: Any) -> None: :param not_contains: str, this string has to NOT be in the final path :param exclude: list of str, here specific types of attributes can be excluded valid values are: settable, settable_contains, other + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_attrib_value('itmax', 180) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_attrib_value('radius', 1.05) + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.set_attrib_value('radius', 1.05, contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.set_attrib_value('spinUp', + 1, + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_attrib_value()` to + the list of tasks that will be done on the xmltree. """ if 'attributename' in kwargs: warnings.warn('The argument attributename is deprecated. Use name instead', DeprecationWarning) @@ -907,12 +1381,8 @@ def set_attrib_value(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('set_attrib_value', args, kwargs)) def set_first_attrib_value(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_first_attrib_value()` to - the list of tasks that will be done on the xmltree. + """Set the value of the first occurrence of an attribute - Sets the first occurrence of an attribute in a xmltree to a given value, specified by its name and further - specifications. If there are no nodes under the specified xpath a tag can be created with `create=True`. The attribute values are converted automatically according to the types of the attribute with :py:func:`~masci_tools.util.xml.converters.convert_to_xml()` if they @@ -931,6 +1401,40 @@ def set_first_attrib_value(self, *args: Any, **kwargs: Any) -> None: :param not_contains: str, this string has to NOT be in the final path :param exclude: list of str, here specific types of attributes can be excluded valid values are: settable, settable_contains, other + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_first_attrib_value('itmax', 180) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_first_attrib_value('radius', 1.05) + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.set_first_attrib_value('radius', 1.05, contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.set_first_attrib_value('spinUp', + 1, + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_first_attrib_value()` to + the list of tasks that will be done on the xmltree. """ if 'attributename' in kwargs: warnings.warn('The argument attributename is deprecated. Use name instead', DeprecationWarning) @@ -942,13 +1446,7 @@ def set_first_attrib_value(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('set_first_attrib_value', args, kwargs)) def add_number_to_attrib(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.add_number_to_attrib()` to - the list of tasks that will be done on the xmltree. - - Adds a given number to the attribute value in a xmltree specified by the name of the attribute - and optional further specification - If there are no nodes under the specified xpath an error is raised + """Add/Multiply values for multiple occurrences in the XML tree :param name: the attribute name to change :param number_to_add: number to add/multiply with the old attribute value @@ -966,6 +1464,40 @@ def add_number_to_attrib(self, *args: Any, **kwargs: Any) -> None: :param not_contains: str, this string has to NOT be in the final path :param exclude: list of str, here specific types of attributes can be excluded valid values are: settable, settable_contains, other + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.add_number_to_attrib('itmax', 10) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.add_number_to_attrib('radius', 1.05, mode='rel') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.add_number_to_attrib('radius', 1.05, mode='rel', contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.add_number_to_attrib('spinUp', + -1, + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.add_number_to_attrib()` to + the list of tasks that will be done on the xmltree. """ if 'attributename' in kwargs: warnings.warn('The argument attributename is deprecated. Use name instead', DeprecationWarning) @@ -977,13 +1509,7 @@ def add_number_to_attrib(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('add_number_to_attrib', args, kwargs)) def add_number_to_first_attrib(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.add_number_to_first_attrib()` to - the list of tasks that will be done on the xmltree. - - Adds a given number to the first occurrence of an attribute value in a xmltree specified by the name of the attribute - and optional further specification - If there are no nodes under the specified xpath an error is raised + """Add/Multiply value for the first occurrences of an attribute in the XML tree :param name: the attribute name to change :param number_to_add: number to add/multiply with the old attribute value @@ -1000,6 +1526,40 @@ def add_number_to_first_attrib(self, *args: Any, **kwargs: Any) -> None: :param not_contains: str, this string has to NOT be in the final path :param exclude: list of str, here specific types of attributes can be excluded valid values are: settable, settable_contains, other + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.add_number_to_first_attrib('itmax', 10) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.add_number_to_first_attrib('radius', 1.05, mode='rel') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.add_number_to_first_attrib('radius', 1.05, mode='rel', contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.add_number_to_first_attrib('spinUp', + -1, + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.add_number_to_first_attrib()` to + the list of tasks that will be done on the xmltree. """ if 'attributename' in kwargs: warnings.warn('The argument attributename is deprecated. Use name instead', DeprecationWarning) @@ -1011,10 +1571,7 @@ def add_number_to_first_attrib(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('add_number_to_first_attrib', args, kwargs)) def xml_create_tag(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_create_tag()` to - the list of tasks that will be done on the xmltree. - + """Creates a tag under the given XPath This method evaluates an xpath expression and creates a tag in a xmltree under the returned nodes. If there are no nodes under the specified xpath an error is raised. @@ -1034,21 +1591,23 @@ def xml_create_tag(self, *args: Any, **kwargs: Any) -> None: :param several: bool, if True multiple tags od the given name are allowed :raises ValueError: If the insertion failed in any way (tag_order does not match, failed to insert, ...) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_create_tag()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('xml_create_tag', args, kwargs) self._tasks.append(ModifierTask('xml_create_tag', args, kwargs)) def xml_replace_tag(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_replace_tag()` to - the list of tasks that will be done on the xmltree. - - Replace XML tags by a given tag on the given XML tree + """Replace XML tags under the XPath by a given tag :param xpath: a path to the tag to be replaced :param element: an Element or string representing the Element to replace the found tags with :param occurrences: int or list of int. Which occurrence of the parent nodes to create a tag. By default all nodes are used. + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_replace_tag()` to + the list of tasks that will be done on the xmltree. """ if 'newelement' in kwargs: warnings.warn('The argument newelement is deprecated. Use element instead', DeprecationWarning) @@ -1058,30 +1617,28 @@ def xml_replace_tag(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('xml_replace_tag', args, kwargs)) def xml_delete_tag(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_delete_tag()` to - the list of tasks that will be done on the xmltree. - - Deletes a tag in the XML tree. + """Deletes a tag under the XPath in the XML tree. :param xpath: a path to the tag to be deleted :param occurrences: int or list of int. Which occurrence of the parent nodes to create a tag. By default all nodes are used. + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_delete_tag()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('xml_delete_tag', args, kwargs) self._tasks.append(ModifierTask('xml_delete_tag', args, kwargs)) def xml_delete_att(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_delete_att()` to - the list of tasks that will be done on the xmltree. - - Deletes an attribute in the XML tree + """Deletes an attribute on the tags of the given XPath in the XML tree :param xpath: a path to the attribute to be deleted :param name: the name of an attribute to delete :param occurrences: int or list of int. Which occurrence of the parent nodes to create a tag. By default all nodes are used. + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_delete_att()` to + the list of tasks that will be done on the xmltree. """ if 'attributename' in kwargs: warnings.warn('The argument attributename is deprecated. Use name instead', DeprecationWarning) @@ -1090,11 +1647,8 @@ def xml_delete_att(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('xml_delete_att', args, kwargs)) def xml_set_attrib_value_no_create(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_set_attrib_value_no_create()` to - the list of tasks that will be done on the xmltree. - - Sets an attribute in a xmltree to a given value. By default the attribute will be set + """Sets the value of an attribute under the XPath + By default the attribute will be set on all nodes returned for the specified xpath. :param xpath: a path where to set the attributes @@ -1103,6 +1657,9 @@ def xml_set_attrib_value_no_create(self, *args: Any, **kwargs: Any) -> None: :param occurrences: int or list of int. Which occurrence of the node to set. By default all are set. :raises ValueError: If the lengths of attribv or occurrences do not match number of nodes + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_set_attrib_value_no_create()` to + the list of tasks that will be done on the xmltree. """ if 'attributename' in kwargs: warnings.warn('The argument attributename is deprecated. Use name instead', DeprecationWarning) @@ -1114,11 +1671,7 @@ def xml_set_attrib_value_no_create(self, *args: Any, **kwargs: Any) -> None: self._tasks.append(ModifierTask('xml_set_attrib_value_no_create', args, kwargs)) def xml_set_text_no_create(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_set_text_no_create()` to - the list of tasks that will be done on the xmltree. - - Sets the text of a tag in a xmltree to a given value. + """Sets the text of a tag under the XPath By default the text will be set on all nodes returned for the specified xpath. :param xpath: a path where to set the text @@ -1126,16 +1679,15 @@ def xml_set_text_no_create(self, *args: Any, **kwargs: Any) -> None: :param occurrences: int or list of int. Which occurrence of the node to set. By default all are set. :raises ValueError: If the lengths of text or occurrences do not match number of nodes + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_basic.xml_set_text_no_create()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('xml_set_text_no_create', args, kwargs) self._tasks.append(ModifierTask('xml_set_text_no_create', args, kwargs)) def set_nmmpmat(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_nmmpmat.set_nmmpmat()` to - the list of tasks that will be done on the xmltree. - - Routine sets the block in the n_mmp_mat file specified by species_name, orbital and spin + """Routine sets the block in the n_mmp_mat file specified by species_name, orbital and spin to the desired density matrix :param species_name: string, name of the species you want to change @@ -1151,16 +1703,15 @@ def set_nmmpmat(self, *args: Any, **kwargs: Any) -> None: :raises ValueError: If something in the input is wrong :raises KeyError: If no LDA+U procedure is found on a species + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_nmmpmat.set_nmmpmat()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('set_nmmpmat', args, kwargs) self._tasks.append(ModifierTask('set_nmmpmat', args, kwargs)) def rotate_nmmpmat(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_nmmpmat.rotate_nmmpmat()` to - the list of tasks that will be done on the xmltree. - - Rotate the density matrix with the given angles phi and theta + """Rotate the density matrix with the given angles phi and theta :param species_name: string, name of the species you want to change :param orbital: integer or string ('all'), orbital quantum number of the LDA+U procedure to be modified @@ -1171,16 +1722,15 @@ def rotate_nmmpmat(self, *args: Any, **kwargs: Any) -> None: :raises ValueError: If something in the input is wrong :raises KeyError: If no LDA+U procedure is found on a species + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_nmmpmat.rotate_nmmpmat()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('rotate_nmmpmat', args, kwargs) self._tasks.append(ModifierTask('rotate_nmmpmat', args, kwargs)) def align_nmmpmat_to_sqa(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_nmmpmat.align_nmmpmat_to_sqa()` to - the list of tasks that will be done on the xmltree. - - Align the density matrix with the given SQA of the associated species + """Align the density matrix with the given SQA of the associated species :param species_name: string, name of the species you want to change :param orbital: integer or string ('all'), orbital quantum number of the LDA+U procedure to be modified @@ -1193,17 +1743,18 @@ def align_nmmpmat_to_sqa(self, *args: Any, **kwargs: Any) -> None: :raises ValueError: If something in the input is wrong :raises KeyError: If no LDA+U procedure is found on a species + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_nmmpmat.align_nmmpmat_to_sqa()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('align_nmmpmat_to_sqa', args, kwargs) self._tasks.append(ModifierTask('align_nmmpmat_to_sqa', args, kwargs)) def set_kpointlist(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_kpointlist()` to - the list of tasks that will be done on the xmltree. + """Create a k-point list with the given points and weights - Explicitly create a kPointList from the given kpoints and weights. This routine will add the - specified kPointList with the given name. + .. note:: + If no name is given, a name of the form ``default-`` is generated .. warning:: For input versions Max4 and older **all** keyword arguments are not valid (`name`, `kpoint_type`, @@ -1217,31 +1768,46 @@ def set_kpointlist(self, *args: Any, **kwargs: Any) -> None: corresponding to the given index :param switch: bool, if True the kPointlist will be used by Fleur when starting the next calculation :param overwrite: bool, if True and a kPointlist with the given name already exists it will be overwritten + + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_kpointlist([[0,0,0],[0.5,0.5,0.5]], + [1,1], switch=True) + + .. usage-example:: + :title: MaX 4 compatibility + :description: For input files before the MaX 5 release the previous kpoint list will always + be overwritten and the ``switch`` argument has no effect + :inputfile: inp_max4.xml + + fm.set_kpointlist([[0,0,0],[0.5,0.5,0.5]], + [1,1]) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_kpointlist()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('set_kpointlist', args, kwargs) self._tasks.append(ModifierTask('set_kpointlist', args, kwargs)) def switch_kpointset(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.switch_kpointset()` to - the list of tasks that will be done on the xmltree. - - Switch the used k-point set + """Switch the used k-point set .. warning:: This method is only supported for input versions after the Max5 release :param list_name: name of the kPoint set to use + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.switch_kpointset()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('switch_kpointset', args, kwargs) self._tasks.append(ModifierTask('switch_kpointset', args, kwargs)) def set_nkpts(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_nkpts()` to - the list of tasks that will be done on the xmltree. - - Sets a k-point mesh directly into inp.xml + """Sets a k-point mesh directly specific for inputs of version Max4 .. warning:: This method is only supported for input versions before the Max5 release @@ -1249,16 +1815,30 @@ def set_nkpts(self, *args: Any, **kwargs: Any) -> None: :param count: number of k-points :param gamma: bool that controls if the gamma-point should be included in the k-point mesh + + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + :inputfile: inp_max4.xml + + fm.set_nkpts(200) + + + .. usage-example:: + :title: Include Gamma point + :inputfile: inp_max4.xml + + fm.set_nkpts(200, gamma=True) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_nkpts()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('set_nkpts', args, kwargs) self._tasks.append(ModifierTask('set_nkpts', args, kwargs)) def set_kpath(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_kpath()` to - the list of tasks that will be done on the xmltree. - - Sets a k-path directly into inp.xml as a alternative kpoint set with purpose 'bands' + """Sets a k-path directly as an alternative kpoint set with purpose 'bands' .. warning:: This method is only supported for input versions before the Max5 release @@ -1267,16 +1847,23 @@ def set_kpath(self, *args: Any, **kwargs: Any) -> None: :param count: number of k-points :param gamma: bool that controls if the gamma-point should be included in the k-point mesh + + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + :inputfile: inp_max4.xml + + fm.set_kpath({'Point': [0, 0, 0], 'Point2': [1, 1, 1]}, 200) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_kpath()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('set_kpath', args, kwargs) self._tasks.append(ModifierTask('set_kpath', args, kwargs)) def set_kpointpath(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_kpointpath()` to - the list of tasks that will be done on the xmltree. - - Create a kpoint list for a bandstructure calculation (using ASE kpath generation) + """Create a kpoint list for a bandstructure calculation (using ASE kpath generation) The path can be defined explictly (see :py:func:`~ase.dft.kpoints.bandpath`) or derived from the unit cell @@ -1287,16 +1874,34 @@ def set_kpointpath(self, *args: Any, **kwargs: Any) -> None: :param switch: bool if True the kpoint list is direclty set as the used set :param overwrite: if True and a kpoint list of the given name already exists it will be overwritten :param special_points: dict mapping names to coordinates for special points to use + + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_kpointpath(nkpts=50, switch=True) + + .. usage-example:: + :title: Specifying custom points + :description: By default the path is chosen by ASE. By providing path + this can be modified + + fm.set_kpointpath(path='CA', + nkpts=25, + special_points={ + 'C': [0, 0, 0], + 'A': [0, 0, 0.5] + }) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_kpointpath()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('set_kpointpath', args, kwargs) self._tasks.append(ModifierTask('set_kpointpath', args, kwargs)) def set_kpointmesh(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_kpointmesh()` to - the list of tasks that will be done on the xmltree. - - Create a kpoint mesh using spglib + """Create a kpoint mesh using spglib for details see :py:func:`~spglib.get_stabilized_reciprocal_mesh` @@ -1309,32 +1914,44 @@ def set_kpointmesh(self, *args: Any, **kwargs: Any) -> None: :param shift: shift the center of the kpint set :param time_reversal: bool if True time reversal symmetry will be used to reduce the kpoint set :param map_to_first_bz: bool if True the kpoints are mapped into the [0,1] interval - """ - self._validate_arguments('set_kpointmesh', args, kwargs) - self._tasks.append(ModifierTask('set_kpointmesh', args, kwargs)) - def set_xcfunctional(self, *args: Any, **kwargs: Any) -> None: - """ - Appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_xcfunctional()` to - the list of tasks that will be done on the xmltree. - Set the Exchange Correlation potential tag + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) - Setting a inbuilt XC functional - .. code-block:: python + .. usage-example:: - set_xcfunctional(xmltree, schema_dict, 'vwn') + fm.set_kpointmesh([4, 4, 1], switch=True) - Setting a LibXC XC functional - .. code-block:: python + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_kpointmesh()` to + the list of tasks that will be done on the xmltree. + """ + self._validate_arguments('set_kpointmesh', args, kwargs) + self._tasks.append(ModifierTask('set_kpointmesh', args, kwargs)) - set_xcfunctional(xmltree, schema_dict, {'exchange': 'lda_x', 'correlation':"lda_c_xalpha"}, libxc=True) + def set_xcfunctional(self, *args: Any, **kwargs: Any) -> None: + """Set the Exchange Correlation potential tag :param xc_functional: str or dict. If str it is the name of a inbuilt XC functional. If it is a dict it specifies either the name or id for LibXC functionals for the keys `'exchange', 'correlation', 'etot_exchange' and 'etot_correlation'` :param xc_functional_options: dict with further general changes to the `xcFunctional` tag :param libxc: bool if True the functional is a LibXC functional + + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_xcfunctional('pbe') + + + .. usage-example:: + :title: Setting a LibXC functional + + fm.set_xcfunctional({'exchange': 'lda_x', 'correlation':"lda_c_xalpha"}, libxc=True) + + This registration method does not modify the file immediately but only appends a :py:func:`~masci_tools.util.xml.xml_setters_names.set_xcfunctional()` to + the list of tasks that will be done on the xmltree. """ self._validate_arguments('set_xcfunctional', args, kwargs) self._tasks.append(ModifierTask('set_xcfunctional', args, kwargs)) diff --git a/masci_tools/io/parsers/fleur_schema/schema_dict.py b/masci_tools/io/parsers/fleur_schema/schema_dict.py index b5551ef0c..a113e1d64 100644 --- a/masci_tools/io/parsers/fleur_schema/schema_dict.py +++ b/masci_tools/io/parsers/fleur_schema/schema_dict.py @@ -22,9 +22,8 @@ import copy from functools import update_wrapper, wraps from pathlib import Path +from collections.abc import Collection from typing import Callable, Iterable, TypeVar, Any, cast - -from .fleur_schema_parser_functions import TagInfo, convert_str_version_number try: from typing import Literal, Protocol except ImportError: @@ -39,6 +38,7 @@ from masci_tools.util.xml.common_functions import abs_to_rel_xpath, clear_xml, split_off_tag, contains_tag, validate_xml from .inpschema_todict import create_inpschema_dict, InputSchemaData from .outschema_todict import create_outschema_dict, merge_schema_dicts +from .fleur_schema_parser_functions import TagInfo, convert_str_version_number PACKAGE_DIRECTORY = Path(__file__).parent.resolve() @@ -123,7 +123,7 @@ def dispatch(version: tuple[int, int]) -> F: matches.append(default_match) if len(matches) > 2: - raise ValueError('Ambiguous possibilities for schema_dict_version_dispatch for version {version}') + raise ValueError(f'Ambiguous possibilities for schema_dict_version_dispatch for version {version}') return matches[0] @@ -225,6 +225,34 @@ def _add_condition(specification: str | Iterable[str] | None, condition: str) -> return specification +def _ensure_path_uniqueness(paths: Collection[str], + description: str, + path_type: str = '', + **specifications: Any) -> str: + """ + Ensure that the given iterable of paths consists of only one path + Otherwise an error is raised that either no or more than one path was found + + :param paths: sized iterable of paths to check + :param description: str for the kind of endpoint (tag name, etc.) that was searched (for errors) + :param path_type: optional str for the kind of path that was searched (for errors) + + Kwargs are entered in a formatted string in one line of the error messages + """ + if len(paths) == 1: + return next(iter(paths)) + + path_type = path_type + ' ' if path_type else path_type + specification_str = ', '.join([f'{key}: {value}' for key, value in specifications.items()]) + if len(paths) == 0: + raise NoPathFound(f'The {description} has no possible {path_type}paths with the current specification.\n' + + specification_str) + raise NoUniquePathFound( + f'The {description} has multiple possible {path_type}paths with the current specification.\n' + f'{specification_str}\n' + f'The following are possible:\n' + '\n'.join([f' - {path}' for path in paths])) + + class SchemaDict(LockableDict): """ Base class for schema dictionaries. Is locked on initialization with :py:meth:`~masci_tools.util.lockable_containers.LockableDict.freeze()`. @@ -316,6 +344,39 @@ def _find_paths(self, return path_list + def _find_attrib_via_tag_name(self, + attrib_name: str, + tag_name: str, + tag_func: Callable[..., str], + *args: Any, + contains: str | Iterable[str] | None = None, + not_contains: str | Iterable[str] | None = None, + **kwargs: Any) -> str: + """ + Find the path to an attribute by first finding the tag on which it sits + Then return the concatenated path + + :param attrib_name: name of the attribute + :param tag_name: name of the tag + :param tag_func: function to use to find the tag path (e.g. self.tag_xpath) + + Args and Kwargs are passed on to the ``tag_func``, ``contains`` and ``not_contains`` kwargs + are additionally passed to the :py:meth:`tag_info()` method + + :returns: path to the attribute on the given tag if the attribute is allowed to be on the tag + """ + + tag_xpath = tag_func(tag_name, *args, contains=contains, not_contains=not_contains, **kwargs) + + tag_info = self.tag_info(tag_name, contains=contains, not_contains=not_contains, **kwargs) + + if attrib_name not in tag_info['attribs']: + raise NoPathFound(f'No attribute {attrib_name} found at tag {tag_name}') + original_case = tag_info['attribs'].original_case[attrib_name] + if tag_xpath.endswith('/'): + return f'{tag_xpath}@{original_case}' + return f'{tag_xpath}/@{original_case}' + def tag_xpath(self, name: str, contains: str | Iterable[str] | None = None, @@ -339,15 +400,7 @@ def tag_xpath(self, ' since no tag entries are defined') paths = self._find_paths(name, self._tag_entries, contains=contains, not_contains=not_contains) - - if len(paths) == 1: - return paths[0] - if len(paths) == 0: - raise NoPathFound(f'The tag {name} has no possible paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}') - raise NoUniquePathFound(f'The tag {name} has multiple possible paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains} \n' - f'These are possible: {paths}') + return _ensure_path_uniqueness(paths, f'tag {name}', contains=contains, not_contains=not_contains) def relative_tag_xpath(self, name: str, @@ -382,15 +435,12 @@ def relative_tag_xpath(self, paths = [path for path in paths if contains_tag(path, root_tag)] relative_paths = {abs_to_rel_xpath(xpath, root_tag) for xpath in paths} - - if len(relative_paths) == 1: - return relative_paths.pop() - if len(relative_paths) == 0: - raise NoPathFound(f'The tag {name} has no possible relative paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag}') - raise NoUniquePathFound(f'The tag {name} has multiple possible relative paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag} \n' - f'These are possible: {relative_paths}') + return _ensure_path_uniqueness(relative_paths, + f'tag {name}', + path_type='relative', + contains=contains, + not_contains=not_contains, + root_tag=root_tag) def attrib_xpath(self, name: str, @@ -423,31 +473,20 @@ def attrib_xpath(self, exclude = [] if tag_name is not None: - tag_xpath = self.tag_xpath(tag_name, contains=contains, not_contains=not_contains) - - tag_info = self.tag_info( - tag_name, - contains=contains, - not_contains=not_contains, - ) - - if name not in tag_info['attribs']: - raise NoPathFound(f'No attribute {name} found at tag {tag_name}') - original_case = tag_info['attribs'].original_case[name] - return f'{tag_xpath}/@{original_case}' + return self._find_attrib_via_tag_name(name, + tag_name, + self.tag_xpath, + contains=contains, + not_contains=not_contains) entries = [entry for entry in self._attrib_entries if all(f'{excl}_attribs' not in entry for excl in exclude)] paths = self._find_paths(name, entries, contains=contains, not_contains=not_contains) - - if len(paths) == 1: - return paths[0] - if len(paths) == 0: - raise NoPathFound(f'The attrib {name} has no possible paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, exclude {exclude}') - raise NoUniquePathFound(f'The attrib {name} has multiple possible paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, exclude {exclude}\n' - f'These are possible: {paths}') + return _ensure_path_uniqueness(paths, + f'attribute {name}', + contains=contains, + not_contains=not_contains, + exclude=exclude) def relative_attrib_xpath(self, name: str, @@ -485,18 +524,12 @@ def relative_attrib_xpath(self, exclude = [] if tag_name is not None: - tag_xpath = self.relative_tag_xpath(tag_name, root_tag, contains=contains, not_contains=not_contains) - - tag_info = self.tag_info(tag_name, contains=contains, not_contains=not_contains) - - if name not in tag_info['attribs']: - raise NoPathFound(f'No attribute {name} found at tag {tag_name}') - - original_case = tag_info['attribs'].original_case[name] - - if tag_xpath.endswith('/'): - return f'{tag_xpath}@{original_case}' - return f'{tag_xpath}/@{original_case}' + return self._find_attrib_via_tag_name(name, + tag_name, + self.relative_tag_xpath, + root_tag, + contains=contains, + not_contains=not_contains) entries = [entry for entry in self._attrib_entries if all(f'{excl}_attribs' not in entry for excl in exclude)] @@ -508,16 +541,12 @@ def relative_attrib_xpath(self, #e.g. bravaisMatrix vs. bravaisMatrixFilm paths = [path for path in paths if contains_tag(path, root_tag)] relative_paths = {abs_to_rel_xpath(xpath, root_tag) for xpath in paths} - - if len(relative_paths) == 1: - return relative_paths.pop() - if len(relative_paths) == 0: - raise NoPathFound(f'The attrib {name} has no possible relative paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag}') - raise NoUniquePathFound( - f'The attrib {name} has multiple possible relative paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag} \n' - f'These are possible: {relative_paths}') + return _ensure_path_uniqueness(relative_paths, + f'attribute {name}', + path_type='relative', + contains=contains, + not_contains=not_contains, + exclude=exclude) def tag_info(self, name: str, @@ -556,8 +585,9 @@ def tag_info(self, if tag_info is not None and entry != tag_info: raise NoUniquePathFound(f'Differing tag_info for the found with the current specification\n' - f'contains: {contains}, not_contains: {not_contains}\n' - f'These are possible: {paths}') + f'contains: {contains}, not_contains: {not_contains}, parent: {parent}\n' + f'The following are possible:\n' + + '\n'.join([f' - {path}' for path in paths])) tag_info = entry if tag_info is None: @@ -899,16 +929,11 @@ def iteration_tag_xpath(self, paths = self._find_paths(name, ('iteration_tag_paths',), contains=contains, not_contains=not_contains) paths = [f"{iteration_path}{path.lstrip('.')}" for path in paths] - - if len(paths) == 1: - return paths[0] - if len(paths) == 0: - raise NoPathFound(f'The tag {name} has no possible iteration paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}') - raise NoUniquePathFound( - f'The tag {name} has multiple possible iteration paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains} \n' - f'These are possible: {paths}') + return _ensure_path_uniqueness(paths, + f'tag {name}', + path_type='iteration', + contains=contains, + not_contains=not_contains) def relative_iteration_tag_xpath(self, name: str, @@ -947,17 +972,12 @@ def relative_iteration_tag_xpath(self, paths = [path for path in paths if contains_tag(path, root_tag)] paths = [f"{iteration_path}{path.lstrip('.')}" for path in paths] relative_paths = {abs_to_rel_xpath(xpath, root_tag) for xpath in paths} - - if len(paths) == 1: - return relative_paths.pop() - if len(paths) == 0: - raise NoPathFound( - f'The tag {name} has no possible relative iteration paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag}') - raise NoUniquePathFound( - f'The tag {name} has multiple possible relative iteration paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag} \n' - f'These are possible: {relative_paths}') + return _ensure_path_uniqueness(relative_paths, + f'tag {name}', + path_type='relative iteration', + contains=contains, + not_contains=not_contains, + root_tag=root_tag) def iteration_attrib_xpath(self, name: str, @@ -999,18 +1019,11 @@ def iteration_attrib_xpath(self, exclude = [] if tag_name is not None: - tag_xpath = self.iteration_tag_xpath(tag_name, contains=contains, not_contains=not_contains) - - tag_info = self.tag_info( - tag_name, - contains=contains, - not_contains=not_contains, - ) - - if name not in tag_info['attribs']: - raise NoPathFound(f'No attribute {name} found at tag {tag_name}') - original_case = tag_info['attribs'].original_case[name] - return f'{tag_xpath}/@{original_case}' + return self._find_attrib_via_tag_name(name, + tag_name, + self.iteration_tag_xpath, + contains=contains, + not_contains=not_contains) entries = [ entry for entry in self._attrib_entries @@ -1019,16 +1032,12 @@ def iteration_attrib_xpath(self, paths = self._find_paths(name, entries, contains=contains, not_contains=not_contains) paths = [f"{iteration_path}{path.lstrip('.')}" for path in paths] - - if len(paths) == 1: - return paths[0] - if len(paths) == 0: - raise NoPathFound(f'The attrib {name} has no possible iteration paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, exclude {exclude}') - raise NoUniquePathFound( - f'The attrib {name} has multiple possible iteration paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, exclude {exclude}\n' - f'These are possible: {paths}') + return _ensure_path_uniqueness(paths, + f'attribute {name}', + path_type='iteration', + contains=contains, + not_contains=not_contains, + exclude=exclude) def relative_iteration_attrib_xpath(self, name: str, @@ -1072,21 +1081,12 @@ def relative_iteration_attrib_xpath(self, exclude = [] if tag_name is not None: - tag_xpath = self.relative_iteration_tag_xpath(tag_name, - root_tag, - contains=contains, - not_contains=not_contains) - - tag_info = self.tag_info(tag_name, contains=contains, not_contains=not_contains) - - if name not in tag_info['attribs']: - raise NoPathFound(f'No attribute {name} found at tag {tag_name}') - - original_case = tag_info['attribs'].original_case[name] - - if tag_xpath.endswith('/'): - return f'{tag_xpath}@{original_case}' - return f'{tag_xpath}/@{original_case}' + return self._find_attrib_via_tag_name(name, + tag_name, + self.relative_iteration_tag_xpath, + root_tag, + contains=contains, + not_contains=not_contains) entries = [ entry for entry in self._attrib_entries @@ -1103,14 +1103,10 @@ def relative_iteration_attrib_xpath(self, paths = [path for path in paths if contains_tag(path, root_tag)] paths = [f"{iteration_path}{path.lstrip('.')}" for path in paths] relative_paths = {abs_to_rel_xpath(xpath, root_tag) for xpath in paths} - - if len(relative_paths) == 1: - return relative_paths.pop() - if len(relative_paths) == 0: - raise NoPathFound( - f'The attrib {name} has no possible relative iteration paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag}') - raise NoUniquePathFound( - f'The attrib {name} has multiple possible relative iteration paths with the current specification.\n' - f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag} \n' - f'These are possible: {relative_paths}') + return _ensure_path_uniqueness(relative_paths, + f'attribute {name}', + path_type='relative iteration', + contains=contains, + not_contains=not_contains, + root_tag=root_tag, + exclude=exclude) diff --git a/masci_tools/util/ipython.py b/masci_tools/util/ipython.py index c8b0dd028..3f011cecc 100644 --- a/masci_tools/util/ipython.py +++ b/masci_tools/util/ipython.py @@ -38,10 +38,10 @@ def xml_diff(old: XMLLike, new: XMLLike, indent: bool = True) -> HTML: STYLES = { 'control': 'font-weight: bold', - 'delete': 'background-color: hsla(0, 100%, 74%, 0.5); color: #000000;', - 'delete-detail': 'background-color: hsla(0, 100%, 74%, 0.5); color: #000000;', - 'insert': 'background-color: hsla(102, 100%, 74%, 0.5); color: #000000;', - 'insert-detail': 'background-color: hsla(102, 100%, 74%, 0.5); color: #000000;', + 'delete': 'background-color: hsla(0, 100%, 74%, 0.5);', + 'delete-detail': 'background-color: hsla(0, 100%, 60%, 0.5);', + 'insert': 'background-color: hsla(102, 100%, 74%, 0.5);', + 'insert-detail': 'background-color: hsla(102, 100%, 60%, 0.5);', } if indent: @@ -53,6 +53,9 @@ def xml_diff(old: XMLLike, new: XMLLike, indent: bool = True) -> HTML: lines = list(difflib.unified_diff(old_lines, new_lines)) + if not lines: + return '' + for index, line in enumerate(lines): if line.startswith('@@'): first_block = index diff --git a/masci_tools/util/sphinxext/__init__.py b/masci_tools/util/sphinxext/__init__.py new file mode 100644 index 000000000..90e7299a8 --- /dev/null +++ b/masci_tools/util/sphinxext/__init__.py @@ -0,0 +1,33 @@ +""" +Spinx extension for masci-tools + +This adds the ability for giving usage examples atm specific +to the FleurXMLModifier. A template can be given, which is filled +in with the options and content from `usage-example` directives in +the docstrings of the specified methods + +Example of the usage-example directive without a connected template. + +.. usage-example:: + :title: This is how it's done + :description: Simply do the stuff + + print('Hello World') + +""" +from .usage_examples import UsageExampleBlock, generate_usage_example_files, DEFAULT_CONF +from sphinx.application import Sphinx + + +def setup(app: Sphinx) -> None: + """ + This function sets up the Sphinx extension. It is called, when + ``masci_tools.util.sphinxext`` is added to the extensions in a conf.py file + """ + + app.add_config_value('usage_examples_conf', DEFAULT_CONF, 'html') + + app.add_directive('usage-example', UsageExampleBlock) + # app.add_directive('usage-example-gallery', UsageExampleGallery) + + app.connect('builder-inited', generate_usage_example_files) diff --git a/masci_tools/util/sphinxext/usage_examples.py b/masci_tools/util/sphinxext/usage_examples.py new file mode 100644 index 000000000..716e21426 --- /dev/null +++ b/masci_tools/util/sphinxext/usage_examples.py @@ -0,0 +1,330 @@ +""" +Spinx extension for masci-tools + +This adds the ability for giving usage examples atm specific +to the FleurXMLModifier. A template can be given, which is filled +in with the options and content from `usage-example` directives in +the docstrings of the specified methods +""" +from __future__ import annotations +import os +from pathlib import Path +import copy +import ast +from contextlib import redirect_stderr, contextmanager +from typing import Any, TypedDict, Generator, cast +import yaml +import io + +from sphinx.application import Sphinx +from sphinx.util.logging import getLogger +from sphinx.errors import ConfigError, ExtensionError +from sphinx.util.docutils import register_directive +from sphinx.util.docutils import SphinxDirective + +from docutils.core import publish_doctree +from docutils.parsers.rst.directives.body import CodeBlock +from docutils.parsers.rst import directives, Directive +from docutils import nodes +from docutils.parsers.rst.roles import set_classes +from docutils.utils.code_analyzer import Lexer, LexerError + +from jinja2 import Environment, FileSystemLoader + +logger = getLogger('masci-tools-usage-examples') + +# yapf: disable +DEFAULT_CONF = { + 'template_dirs': os.path.join('..', 'usage_examples'), + 'template_file': 'template.md.jinja' +} +# yapf: enable + +TEMPLATE_CONFIG_FILE = 'config.yml' +DEFAULT_TEMPLATE_CONFIG = { + 'module-file': None, # Required + 'class-name': None, # Required (TODO: Could be made optional if there's only one class definition) + 'exclude-methods': None, + 'include-private-methods': False, + 'output-folder': 'examples' +} + + +class UsageExample(TypedDict, total=False): + """ + Dict representing information from ``..usage-example`` directives + """ + title: str + error: bool + inputfile: str + code: str + description: str + + +class UsageExampleBlock(SphinxDirective): + """ + Directive for usage examples + + These are rendered as admonitions containing the + optional description and title and the content in the + form of a python code-block + """ + + final_argument_whitespace = True + option_spec = { + 'class': directives.class_option, + 'name': directives.unchanged, + 'title': directives.unchanged, + 'description': directives.unchanged, + 'result': directives.unchanged, + 'inputfile': directives.unchanged, + } + has_content = True + + node_class = nodes.admonition + """Subclasses must set this to the appropriate admonition node class.""" + + def run(self): + set_classes(self.options) + self.assert_has_content() + + description = self.options.pop('description', '') + title_text = self.options.pop('title', 'Simple Usage') + for key in ('result', 'inputfile'): + self.options.pop(key, None) + + admonition_node = self.node_class(description, **self.options) + self.add_name(admonition_node) + + textnodes, messages = self.state.inline_text(title_text, self.lineno) + title = nodes.title(title_text, '', *textnodes) + title.source, title.line = (self.state_machine.get_source_and_line(self.lineno)) + admonition_node += title + admonition_node += messages + text_nodes, messages = self.state.inline_text(description.strip(), self.lineno) + line = nodes.line(description, '', *text_nodes) + admonition_node += line + if not 'classes' in self.options: + admonition_node['classes'] += ['admonition-' + nodes.make_id(title_text)] + + classes = ['code', 'python'] + + # set up lexical analyzer + try: + tokens = Lexer('\n'.join(self.content), 'python', self.state.document.settings.syntax_highlight) + except LexerError as error: + raise self.warning(error) + + code_node = nodes.literal_block('\n'.join(self.content), classes=classes) + self.add_name(code_node) + + # analyze content and add nodes for every token + for classes, value in tokens: + if classes: + code_node += nodes.inline(value, value, classes=classes) + else: + # insert as Text to decrease the verbosity of the output + code_node += nodes.Text(value) + + admonition_node += code_node + + return [admonition_node] + + +def generate_usage_example_files(app: Sphinx) -> None: + """ + Generate the markdown files for the specified usage examples + + The process consists of two steps: + 1. The module specified in the individual configurations (``config.yml`` files in the ``template_dirs`` + specified in the ``conf.py`` file) + is parsed to extract the + information specified in ``.. usage-example::`` directives. + 2. The information extracted is used to render the jinja template + in the ``template_dirs`` + """ + logger.info('Generating usage examples ...') + + config = copy.deepcopy(DEFAULT_CONF) + config.update(app.config.usage_examples_conf) + + for template_folder in config['template_dirs']: + template_folder_path, template_conf = _load_template_conf(template_folder, + app.builder.srcdir) #type: ignore[union-attr] + usage_examples = _gather_usage_examples(**template_conf) + _render_templates(template_folder_path, usage_examples, config, template_conf) + + +def _load_template_conf(template_folder: str, srcdir: str) -> tuple[Path, dict[str, Any]]: + """ + Load the configuration for the current template folder + + :param template_folder: Filepath to the folder containing the template + :param srcdir: directory containing the conf.py + """ + template_folder_path = Path(srcdir) / template_folder + + config = copy.deepcopy(DEFAULT_TEMPLATE_CONFIG) + with open(template_folder_path / TEMPLATE_CONFIG_FILE, encoding='utf-8') as file: + config.update(yaml.safe_load(file)) + + if config['exclude-methods'] is not None: + config['exclude-methods'] = set(config['exclude-methods']) #type: ignore[assignment,arg-type] + + if config['module-file'] is None: + raise ConfigError(f'The template in {template_folder} does not define a module') + config['module-file'] = template_folder_path / config['module-file'] #type: ignore[assignment,operator] + + if config['class-name'] is None: + raise ConfigError(f'The template in {template_folder_path} does not define a class') + + config = {k.replace('-', '_'): v for k, v in config.items()} + return template_folder_path, config + + +def _render_templates(template_folder: Path, usage_examples: dict[str, list[UsageExample]], config: dict[str, Any], + template_config: dict[str, Any]) -> None: + """ + Render all usage-example with the specified jinja template + + :param template_folder: Filepath to the folder containing the template + :param usage_examples: Dict of all usage examples + :param config: global configuration of the extension + :param template_config: configuration specific to the current template + """ + template_env = Environment( + loader=FileSystemLoader(template_folder), + keep_trailing_newline=True, + lstrip_blocks=True, + trim_blocks=True, + ) + + for name, examples in usage_examples.items(): + if not examples: + continue + template = template_env.get_template(config['template_file']) + + output_folder = template_folder / template_config['output_folder'] + if not output_folder.exists(): + output_folder.mkdir() + + with open(output_folder / f'{name}.md', 'w', encoding='utf-8') as file: + file.write(template.render(examples=examples, title=f'``{name}``', reference=name)) + logger.info('Rendered Usage example to "%s".', os.fspath(template_folder / f'{name}.md')) + + +def _gather_usage_examples(module_file: Path, + class_name: str, + exclude_methods: set[str] | None = None, + include_private_methods: bool = False, + **kwargs: Any) -> dict[str, list[UsageExample]]: + """ + Gather all usage-example blocks for the specified methods + + :param module_file: Filepath to the module to analyze + :param class_name: Name of the class to analyze + :param exclude_methods: set of str, these methods will not be analyzed + :param include_private_methods: bool, if True methods starting with `_` will be analyzed + + :returns: dict mapping method names to the list of usage examples defined in it's docstring + """ + + class UsageExampleTemplateBlock(CodeBlock): #type: ignore[misc] + """ + Dummy directive which extract the information + of usage-example directives + and collects them in a class variable for later use + """ + + #This directive should contain the template rendering logic + #In conf.py another one will be defined, which creates a admonition + # with code block + option_spec = { + 'title': directives.unchanged, + 'description': directives.unchanged, + 'result': directives.unchanged, + 'inputfile': directives.unchanged, + } + has_content = True + collected_examples: list[UsageExample] = [] + + def run(self): + self.assert_has_content() + + self.options.setdefault('classes', []).append( + 'usage-example') #For checking after the fact if there were usage examples provided + + example = UsageExample({ + 'title': self.options.get('title', 'Simple Usage'), + 'error': self.options.get('result', 'success').lower() == 'error', + 'inputfile': self.options.get('inputfile', 'inp.xml'), + 'code': '\n'.join(self.content) + }) + if 'description' in self.options: + example['description'] = self.options['description'] + self.collected_examples.append(example) + return super().run() + + @classmethod + def reset(cls): + cls.collected_examples = [] + + if exclude_methods is None: + exclude_methods = set() + + with open(module_file, encoding='utf-8') as file: + module = ast.parse(file.read()) + + usage_examples = {} + with patch_directive('usage-example', cast(type[Directive], UsageExampleTemplateBlock)): + + class_definitions = [node for node in module.body if isinstance(node, ast.ClassDef)] + + for class_def in class_definitions: + if class_def.name == class_name: + method_definition = [node for node in class_def.body if isinstance(node, ast.FunctionDef)] + for method in method_definition: + if method.name in exclude_methods: + continue + if method.name.startswith('_') and not include_private_methods: + continue + + docstring = ast.get_docstring(method, clean=True) + short_desc = ast.get_docstring(method, clean=False) + if short_desc is not None: + short_desc = short_desc.split('\n')[0] + + #Ignore unknown directive errors + with redirect_stderr(io.StringIO()): + publish_doctree(docstring) + + method_examples = UsageExampleTemplateBlock.collected_examples + if len(method_examples) == 0: + logger.warning(f'Method {method.name} of class {class_name} has no usage-example') + + for entry in method_examples: + if not 'description' in entry and short_desc: + entry['description'] = short_desc + + usage_examples[method.name] = method_examples + UsageExampleTemplateBlock.reset() + + return usage_examples + + +@contextmanager +def patch_directive(name: str, directive: type[Directive]) -> Generator[None, None, None]: + """ + Temporarily replace the directive of the given name + with a different one + """ + #pylint: disable=protected-access + + before = directives._directives.get(name, None) + try: + register_directive(name, directive) + yield + finally: + directives._directives.pop(name) + if before is not None: + directives._directives[name] = before diff --git a/masci_tools/util/xml/xml_setters_basic.py b/masci_tools/util/xml/xml_setters_basic.py index 33c9660eb..17159e80b 100644 --- a/masci_tools/util/xml/xml_setters_basic.py +++ b/masci_tools/util/xml/xml_setters_basic.py @@ -29,8 +29,7 @@ def xml_replace_tag(xmltree: XMLLike, xpath: XPathLike, element: str | etree._Element, occurrences: int | Iterable[int] | None = None) -> XMLLike: - """ - Replace XML tags by a given tag on the given XML tree + """Replace XML tags under the XPath by a given tag :param xmltree: an xmltree that represents inp.xml :param xpath: a path to the tag to be replaced @@ -79,8 +78,7 @@ def xml_delete_att(xmltree: XMLLike, xpath: XPathLike, name: str, occurrences: int | Iterable[int] | None = None) -> XMLLike: - """ - Deletes an attribute in the XML tree + """Deletes an attribute on the tags of the given XPath in the XML tree :param xmltree: an xmltree that represents inp.xml :param xpath: a path to the attribute to be deleted @@ -116,8 +114,7 @@ def xml_delete_att(xmltree: XMLLike, def xml_delete_tag(xmltree: XMLLike, xpath: XPathLike, occurrences: int | Iterable[int] | None = None) -> XMLLike: - """ - Deletes a tag in the XML tree. + """Deletes a tag under the XPath in the XML tree. :param xmltree: an xmltree that represents inp.xml :param xpath: a path to the tag to be deleted @@ -198,7 +195,7 @@ def xml_create_tag(xmltree: XMLLike, occurrences: int | Iterable[int] | None = None, correct_order: bool = True, several: bool = True) -> XMLLike: - """ + """Creates a tag under the given XPath This method evaluates an xpath expression and creates a tag in a xmltree under the returned nodes. If there are no nodes under the specified xpath an error is raised. @@ -329,8 +326,8 @@ def xml_set_attrib_value_no_create(xmltree: XMLLike, name: str, value: Any, occurrences: int | Iterable[int] | None = None) -> XMLLike: - """ - Sets an attribute in a xmltree to a given value. By default the attribute will be set + """Sets the value of an attribute under the XPath + By default the attribute will be set on all nodes returned for the specified xpath. :param xmltree: an xmltree that represents inp.xml @@ -382,8 +379,7 @@ def xml_set_text_no_create(xmltree: XMLLike, xpath: XPathLike, text: Any, occurrences: int | Iterable[int] | None = None) -> XMLLike: - """ - Sets the text of a tag in a xmltree to a given value. + """Sets the text of a tag under the XPath By default the text will be set on all nodes returned for the specified xpath. :param xmltree: an xmltree that represents inp.xml diff --git a/masci_tools/util/xml/xml_setters_names.py b/masci_tools/util/xml/xml_setters_names.py index 6a628f1ed..49b7a1438 100644 --- a/masci_tools/util/xml/xml_setters_names.py +++ b/masci_tools/util/xml/xml_setters_names.py @@ -40,7 +40,7 @@ def create_tag(xmltree: XMLLike, create_parents: bool = False, occurrences: int | Iterable[int] | None = None, **kwargs: Any) -> XMLLike: - """ + """Create a tag with a given name in the XML tree This method creates a tag with a uniquely identified xpath under the nodes of its parent. If there are no nodes evaluated the subtags can be created with `create_parents=True` @@ -61,6 +61,53 @@ def create_tag(xmltree: XMLLike, :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.create_tag('mtnocoparams') + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.create_tag('lo') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.create_tag('lo', contains='species') + + .. usage-example:: + :title: Nested creation + :description: With ``create_parents=True`` evtl. missing parent tags are also created + + fm.create_tag('diagElements', + create_parents=True, + contains='species', + not_contains='torque') + + .. usage-example:: + :title: Creation of pre-created XML element + :description: Passing a XML element instead of a name will insert this element at the correct position + + fm.create_tag(etree.Element('lo', n='5', l='0', type='SCLO'), + contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.create_tag(etree.Element('lo', n='5', l='0', type='SCLO'), + contains='species', + filters={ + 'species': { + 'name': {'contains': 'Fe'} + } + }) + :returns: xmltree with created tags """ from masci_tools.util.xml.xml_setters_xpaths import xml_create_tag_schema_dict @@ -100,8 +147,7 @@ def delete_tag(xmltree: XMLLike, filters: FilterType | None = None, occurrences: int | Iterable[int] | None = None, **kwargs: Any) -> XMLLike: - """ - This method deletes a tag with a uniquely identified xpath. + """Delete a tag with a given name from the XML tree :param xmltree: an xmltree that represents inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input @@ -116,6 +162,37 @@ def delete_tag(xmltree: XMLLike, :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.delete_tag('expertModes') + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.delete_tag('lo') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.delete_tag('lo', contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.delete_tag('lo', + contains='species', + filters={ + 'species': { + 'name': {'contains': 'Fe'} + } + }) + :returns: xmltree with deleted tags """ from masci_tools.util.xml.xml_setters_basic import xml_delete_tag @@ -136,8 +213,7 @@ def delete_att(xmltree: XMLLike, filters: FilterType | None = None, occurrences: int | Iterable[int] | None = None, **kwargs: Any) -> XMLLike: - """ - This method deletes a attribute with a uniquely identified xpath. + """Delete an attribute with a given name from the XML tree :param xmltree: an xmltree that represents inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input @@ -155,6 +231,38 @@ def delete_att(xmltree: XMLLike, :param exclude: list of str, here specific types of attributes can be excluded valid values are: settable, settable_contains, other + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.delete_att('mixparam') + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.delete_att('alpha') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.delete_att('alpha', contains='nocoParams', not_contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.delete_att('alpha', + contains='nocoParams', + not_contains='species', + filters={ + 'atomGroup': { + 'species': {'not-contains': 'Fe'} + } + }) + :returns: xmltree with deleted attributes """ from masci_tools.util.xml.xml_setters_basic import xml_delete_att @@ -177,8 +285,7 @@ def replace_tag(xmltree: XMLLike, filters: FilterType | None = None, occurrences: int | Iterable[int] | None = None, **kwargs: Any) -> XMLLike: - """ - This method deletes a tag with a uniquely identified xpath. + """Replace a tag with a given name with a different XML element :param xmltree: an xmltree that represents inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input @@ -194,6 +301,37 @@ def replace_tag(xmltree: XMLLike, :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.replace_tag('expertmodes', etree.Element('greensFunction')) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.replace_tag('lo', etree.Element('ldaU', l='3')) + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.replace_tag('lo', etree.Element('ldaU', l='3'), contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.replace_tag('lo', + etree.Element('ldaU', l='3'), + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + :returns: xmltree with replaced tags """ from masci_tools.util.xml.xml_setters_basic import xml_replace_tag @@ -216,10 +354,7 @@ def add_number_to_attrib(xmltree: XMLLike, mode: Literal['abs', 'absolute', 'rel', 'relative'] = 'absolute', occurrences: int | Iterable[int] | None = None, **kwargs: Any) -> XMLLike: - """ - Adds a given number to the attribute value in a xmltree specified by the name of the attribute - and optional further specification - If there are no nodes under the specified xpath an error is raised + """Add/Multiply values for multiple occurrences in the XML tree :param xmltree: an xmltree that represents inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input @@ -240,6 +375,37 @@ def add_number_to_attrib(xmltree: XMLLike, :param exclude: list of str, here specific types of attributes can be excluded valid values are: settable, settable_contains, other + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.add_number_to_attrib('itmax', 10) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.add_number_to_attrib('radius', 1.05, mode='rel') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.add_number_to_attrib('radius', 1.05, mode='rel', contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.add_number_to_attrib('spinUp', + -1, + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + :returns: xmltree with shifted attribute """ from masci_tools.util.xml.xml_setters_xpaths import xml_add_number_to_attrib @@ -267,10 +433,7 @@ def add_number_to_first_attrib(xmltree: XMLLike, filters: FilterType | None = None, mode: Literal['abs', 'absolute', 'rel', 'relative'] = 'absolute', **kwargs: Any) -> XMLLike: - """ - Adds a given number to the first occurrence of an attribute value in a xmltree specified by the name of the attribute - and optional further specification - If there are no nodes under the specified xpath an error is raised + """Add/Multiply value for the first occurrences of an attribute in the XML tree :param xmltree: an xmltree that represents inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input @@ -290,6 +453,37 @@ def add_number_to_first_attrib(xmltree: XMLLike, :param exclude: list of str, here specific types of attributes can be excluded valid values are: settable, settable_contains, other + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.add_number_to_first_attrib('itmax', 10) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.add_number_to_first_attrib('radius', 1.05, mode='rel') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.add_number_to_first_attrib('radius', 1.05, mode='rel', contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.add_number_to_first_attrib('spinUp', + -1, + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + :returns: xmltree with shifted attribute """ return add_number_to_attrib(xmltree, @@ -312,9 +506,8 @@ def set_attrib_value(xmltree: XMLLike, occurrences: int | Iterable[int] | None = None, create: bool = False, **kwargs: Any) -> XMLLike: - """ - Sets an attribute in a xmltree to a given value, specified by its name and further - specifications. + """Set the value of an attribute possibly occurring multiple times + If there are no nodes under the specified xpath a tag can be created with `create=True`. The attribute values are converted automatically according to the types of the attribute with :py:func:`~masci_tools.util.xml.converters.convert_to_xml()` if they @@ -337,6 +530,37 @@ def set_attrib_value(xmltree: XMLLike, :param exclude: list of str, here specific types of attributes can be excluded valid values are: settable, settable_contains, other + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_attrib_value('itmax', 180) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_attrib_value('radius', 1.05) + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.set_attrib_value('radius', 1.05, contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.set_attrib_value('spinUp', + 1, + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + :returns: xmltree with set attribute """ from masci_tools.util.xml.xml_setters_xpaths import xml_set_attrib_value @@ -369,9 +593,8 @@ def set_first_attrib_value(xmltree: XMLLike, filters: FilterType | None = None, create: bool = False, **kwargs: Any) -> XMLLike: - """ - Sets the first occurrence of an attribute in a xmltree to a given value, specified by its name and further - specifications. + """Set the value of the first occurrence of an attribute + If there are no nodes under the specified xpath a tag can be created with `create=True`. The attribute values are converted automatically according to the types of the attribute with :py:func:`~masci_tools.util.xml.converters.convert_to_xml()` if they @@ -393,6 +616,37 @@ def set_first_attrib_value(xmltree: XMLLike, :param exclude: list of str, here specific types of attributes can be excluded valid values are: settable, settable_contains, other + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_first_attrib_value('itmax', 180) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_first_attrib_value('radius', 1.05) + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.set_first_attrib_value('radius', 1.05, contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.set_first_attrib_value('spinUp', + 1, + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + :returns: xmltree with set attribute """ return set_attrib_value(xmltree, @@ -415,9 +669,9 @@ def set_text(xmltree: XMLLike, occurrences: int | Iterable[int] | None = None, create: bool = False, **kwargs: Any) -> XMLLike: - """ - Sets the text on tags in a xmltree to a given value, specified by the name of the tag and - further specifications. By default the text will be set on all nodes returned for the specified xpath. + """Set the value of text of tags in the XML tree possibly occurring mutliple times + + By default the text will be set on all nodes returned for the specified xpath. If there are no nodes under the specified xpath a tag can be created with `create=True`. The text values are converted automatically according to the types with :py:func:`~masci_tools.util.xml.converters.convert_to_xml()` if they @@ -437,6 +691,44 @@ def set_text(xmltree: XMLLike, :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_text('kpoint', [[1,2,3], [4,5,6]]) + + .. usage-example:: + :title: Setting one value + :description: If only one value for the text is given all elements are set ot the same value + in this case only one list of three coordinates is set + + fm.set_text('kpoint', [4,4,4]) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_text('q', '1 1 1') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.set_text('q', '1 1 1', contains='spinSpiral') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.set_text('valenceConfig', + '(1s1/2)', + contains='species', + filters={ + 'species': { + './lo/@n': {'>': 4} + }}) + :returns: xmltree with set text """ from masci_tools.util.xml.xml_setters_xpaths import xml_set_text @@ -455,9 +747,9 @@ def set_first_text(xmltree: XMLLike, filters: FilterType | None = None, create: bool = False, **kwargs: Any) -> XMLLike: - """ - Sets the text the first occurrence of a tag in a xmltree to a given value, specified by the name of the tag and - further specifications. By default the text will be set on all nodes returned for the specified xpath. + """Set the value of text of the first occurrence of a tag in the XML tree + + By default the text will be set on all nodes returned for the specified xpath. If there are no nodes under the specified xpath a tag can be created with `create=True`. The text values are converted automatically according to the types with :py:func:`~masci_tools.util.xml.converters.convert_to_xml()` if they @@ -476,6 +768,37 @@ def set_first_text(xmltree: XMLLike, :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_first_text('kpoint', [1,2,3]) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_first_text('valenceConfig', '(1s1/2)') + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.set_first_text('valenceConfig', '(1s1/2)', contains='species') + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.set_first_text('valenceConfig', + '(1s1/2)', + contains='species', + filters={ + 'species': { + './lo/@n': {'>': 4} + }}) + :returns: xmltree with set text """ return set_text(xmltree, @@ -497,8 +820,9 @@ def set_simple_tag(xmltree: XMLLike, filters: FilterType | None = None, create_parents: bool = False, **kwargs: Any) -> XMLLike: - """ - Sets one or multiple `simple` tag(s) in an xmltree. A simple tag can only hold attributes and has no + """Sets one or multiple ``simple`` tag(s) (no subtags/text) in an xmltree. + + A simple tag can only hold attributes and has no subtags. The tag is specified by its name and further specification If the tag can occur multiple times all existing tags are DELETED and new ones are written. If the tag only occurs once it will automatically be created if its missing. @@ -518,6 +842,47 @@ def set_simple_tag(xmltree: XMLLike, :param contains: str, this string has to be in the final path :param not_contains: str, this string has to NOT be in the final path + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_simple_tag('soc', {'theta': 0.1, 'phi': 0.2, 'l_soc': True}) + + .. usage-example:: + :title: Tag selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_simple_tag('lo', [{'l': 0, 'n': 6, 'type': 'SCLO'}, + {'l': 1, 'n': 6, 'type': 'SCLO'}]) + + .. usage-example:: + :title: Tag selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + + fm.set_simple_tag('lo', [{'l': 0, 'n': 6, 'type': 'SCLO'}, + {'l': 1, 'n': 6, 'type': 'SCLO'}], + contains='species') + + .. usage-example:: + :title: Nested creation + :description: With `create_parents=True` if the parents of the tag are missing they are + created + + fm.set_simple_tag('realAxis', {'ne': 1300, 'ellow': -1.0, 'elup': 1.0}, create_parents=True) + + .. usage-example:: + :title: Added filters + :description: The filters argument allows to be more specific + + fm.set_simple_tag('lo', [{'l': 0, 'n': 6, 'type': 'SCLO'}, + {'l': 1, 'n': 6, 'type': 'SCLO'}], + contains='species', + filters={ + 'species': { + 'atomicNumber': {'>': 30} + }}) + :returns: xmltree with set simple tags """ from masci_tools.util.xml.xml_setters_xpaths import xml_set_simple_tag @@ -549,7 +914,8 @@ def set_complex_tag(xmltree: XMLLike, filters: FilterType | None = None, create: bool = False, **kwargs: Any) -> XMLLike: - """ + """Setattributes, text and subtags of a given tag in the XML tree + Function to correctly set tags/attributes for a given tag. Goes through the attributedict and decides based on the schema_dict, how the corresponding key has to be handled. @@ -592,7 +958,8 @@ def set_species_label(xmltree: XMLLike, atom_label: str, changes: dict[str, Any], create: bool = False) -> XMLLike: - """ + """Set the attributes of a species, given by an atom label occurring in it's atom type + This method calls :func:`~masci_tools.util.xml.xml_setters_names.set_species()` method for a certain atom species that corresponds to an atom with a given label @@ -603,6 +970,31 @@ def set_species_label(xmltree: XMLLike, :param create: bool, if species does not exist create it and all subtags? :returns: xml etree of the new inp.xml + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_species_label('222', {'mtSphere' : {'radius' : 2.7}}) + + .. usage-example:: + :title: Adding child elements + :description: The `changes` dictionary is not limited to setting attributes + Child elements can also be created. Notice here that if multiple + elements of a given name are allowed all previously existing elements + are deleted and replaced with the ones specified in `changes` + + fm.set_species_label('222', {'ldaU' : {'l' : 3, 'U': 4.0, 'J': 0.5, 'l_amf': True}, + 'lo': [{'l': 0, 'n': 6, 'type': 'SCLO'}, + {'l': 1, 'n': 6, 'type': 'SCLO'}]}) + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` as the first argument applies the changes + to all species + + fm.set_species_label('all', {'mtSphere' : {'radius' : 2.7}}) + """ from masci_tools.util.schema_dict_util import evaluate_attribute @@ -633,8 +1025,7 @@ def set_species(xmltree: XMLLike, changes: dict[str, Any], filters: FilterType | None = None, create: bool = False) -> XMLLike: - """ - Method to set parameters of a species tag of the fleur inp.xml file. + """Set the attributes of a species, specified by name position, etc. :param xmltree: xml etree of the inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input @@ -652,18 +1043,57 @@ def set_species(xmltree: XMLLike, **changes** is a python dictionary containing dictionaries that specify attributes to be set inside the certain specie. For example, if one wants to set a MT radius it - can be done via:: + can be done via + + .. usage-example:: + + fm.set_species('Fe-1', {'mtSphere' : {'radius' : 2.7}}) + + .. usage-example:: + :title: Adding child elements + :description: The `changes` dictionary is not limited to setting attributes + Child elements can also be created. Notice here that if multiple + elements of a given name are allowed all previously existing elements + are deleted and replaced with the ones specified in `changes` + + fm.set_species('Fe-1', {'ldaU' : {'l' : 3, 'U': 4.0, 'J': 0.5, 'l_amf': True}, + 'lo': [{'l': 0, 'n': 6, 'type': 'SCLO'}, + {'l': 1, 'n': 6, 'type': 'SCLO'}]}) + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` as the first argument applies the changes + to all species + + fm.set_species('all', {'mtSphere' : {'radius' : 2.7}}) - changes = {'mtSphere' : {'radius' : 2.2}} + .. usage-example:: + :title: Modifying a subset of species + :description: Providing `'all-'` as the first argument applies the changes + to all species which contain the search string in it's name - Another example:: + fm.set_species('all-Pt', {'mtSphere' : {'radius' : 2.7}, + 'lo': {'l': 0, 'n': 6, 'type': 'SCLO'},}) - 'changes': {'special': {'socscale': 0.0}} + Example species. All these attributes tags can be modified. + Additional keys can for example be found in the FLEUR documentation flapw.de + + .. code-block:: xml + + + + + + [Ne] + (3s1/2) (3p1/2) (3p3/2) (4s1/2) (3d3/2) (3d5/2) + + + + + + + - that switches SOC terms on a sertain specie. ``mtSphere``, ``atomicCutoffs``, - ``energyParameters``, ``lo``, ``electronConfig``, ``nocoParams``, ``ldaU`` and - ``special`` keys are supported. To find possible - keys of the inner dictionary please refer to the FLEUR documentation flapw.de """ from masci_tools.util.xml.xml_setters_xpaths import xml_set_complex_tag @@ -686,8 +1116,7 @@ def clone_species(xmltree: XMLLike, species_name: str, new_name: str, changes: dict[str, Any] | None = None) -> XMLLike: - """ - Method to create a new species from an existing one with evtl. modifications + """Clone (and possibly change) a species in the XML tree For reference of the changes dictionary look at :py:func:`set_species()` @@ -699,6 +1128,20 @@ def clone_species(xmltree: XMLLike, :param changes: a optional python dict specifying what you want to change. :returns xmltree: xml etree of the new inp.xml + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.clone_species('Fe-1', 'Fe-clone-1') + + .. usage-example:: + :title: Modifying the cloned species + :description: The `changes` dictionary is passed on to ``set_species`` + to modify the cloned species + + fm.clone_species('Fe-1', 'Fe-clone-1', {'mtSphere' : {'radius' : 2.7}}) + """ from masci_tools.util.schema_dict_util import evaluate_attribute from masci_tools.util.xml.common_functions import eval_xpath_one @@ -736,7 +1179,7 @@ def shift_value_species_label(xmltree: XMLLike, number_to_add: Any, mode: Literal['abs', 'absolute', 'rel', 'relative'] = 'absolute', **kwargs: Any) -> XMLLike: - """ + """Add/Multiply numeric attributes in a or multiple atom species Shifts the value of an attribute on a species by label if atom_label contains 'all' then applies to all species @@ -754,6 +1197,29 @@ def shift_value_species_label(xmltree: XMLLike, :param not_contains: str, this string has to NOT be in the final path :returns: xml etree of the new inp.xml + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.shift_value_species_label('222', 'radius', 1) + + .. usage-example:: + :title: Relative shift + :description: Passing ``mode="rel"`` or ``mode="relative"`` will multiply + the initial attribute value with the provided number instead + of adding them + + fm.shift_value_species_label('222', 'radius', 1.2, mode="relative") + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` as the first argument applies the changes + to all atomgroups + + fm.shift_value_species_label('all', 'lmax', 2) + + """ from masci_tools.util.schema_dict_util import evaluate_attribute from masci_tools.util.xml.xml_setters_xpaths import xml_add_number_to_first_attrib @@ -793,8 +1259,9 @@ def shift_value_species_label(xmltree: XMLLike, def set_atomgroup_label(xmltree: XMLLike, schema_dict: fleur_schema.SchemaDict, atom_label: str, changes: dict[str, Any]) -> XMLLike: - """ - This method calls :func:`~masci_tools.util.xml.xml_setters_names.set_atomgroup()` + """Set the attributes of a atom group, given by an atom label occuring in it's definition + + This method calls :py:func:`~masci_tools.util.xml.xml_setters_names.set_atomgroup()` method for a certain atom species that corresponds to an atom with a given label. :param xmltree: xml etree of the inp.xml @@ -810,6 +1277,20 @@ def set_atomgroup_label(xmltree: XMLLike, schema_dict: fleur_schema.SchemaDict, 'changes': {'nocoParams': {'beta': val}} + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_atomgroup_label('222', {'nocoParams': {'alpha': 1.57}}) + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` as the first argument applies the changes + to all species + + fm.set_atomgroup_label('all', {'nocoParams': {'alpha': 1.57}}) + """ from masci_tools.util.schema_dict_util import evaluate_attribute if atom_label == 'all': @@ -839,8 +1320,7 @@ def set_atomgroup(xmltree: XMLLike, position: int | Literal['all'] | None = None, species: str | None = None, filters: FilterType | None = None) -> XMLLike: - """ - Method to set parameters of an atom group of the fleur inp.xml file. + """Set the attributes of a atom group, specified by species name, position, etc. :param xmltree: xml etree of the inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input @@ -858,6 +1338,26 @@ def set_atomgroup(xmltree: XMLLike, 'changes': {'nocoParams': {'beta': val}} + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_atomgroup({'nocoParams': {'alpha': 1.57}}, species='Fe-1') + + .. usage-example:: + :title: Specifying the number of the atromgroup + :description: Providing the ``position`` argument will modify the n-th + atomgroup in the ``inp.xml`` file. (Indexing starts at 1) + + fm.set_atomgroup({'nocoParams': {'alpha': 1.57}}, position=1) + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` to either ``position`` or ``species``` + will modify all atomgroups + + fm.set_atomgroup({'nocoParams': {'alpha': 1.57}}, species='all') + """ from masci_tools.util.xml.xml_setters_xpaths import xml_set_complex_tag @@ -889,9 +1389,7 @@ def switch_species_label(xmltree: XMLLike, new_species_name: str, clone: bool = False, changes: dict[str, Any] | None = None) -> XMLLike: - """ - Method to switch the species of an atom group of the fleur inp.xml file based on a label - of a contained atom + """Change the species of an atom group, identified by the lable of an atom in it's group :param xmltree: xml etree of the inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input @@ -926,8 +1424,7 @@ def switch_species(xmltree: XMLLike, filters: FilterType | None = None, clone: bool = False, changes: dict[str, Any] | None = None) -> XMLLike: - """ - Method to switch the species of an atom group of the fleur inp.xml file. + """Change the species of an atom group, identified by species name, position, etc. :param xmltree: xml etree of the inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input @@ -941,6 +1438,37 @@ def switch_species(xmltree: XMLLike, See :py:class:`~masci_tools.util.xml.xpathbuilder.XPathBuilder` for details :returns: xml etree of the new inp.xml + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.switch_species('Pt-1', species='Fe-1') + + + .. usage-example:: + :title: Specifying the number of the atromgroup + :description: Providing the ``position`` argument will modify the n-th + atomgroup in the ``inp.xml`` file. (Indexing starts at 1) + + fm.switch_species('Pt-1', position=1) + + .. usage-example:: + :title: Create a clone of the old species + :description: Specifying a non-existent new species name and ``clone=True`` + will create a clone of the old species with that name. Additionally + the ``changes`` argument can then be used to apply changes to the new + clone + + fm.switch_species('Fe-clone-2', species='Fe-1', clone=True, changes={'mtSphere' : {'radius' : 2.7}}) + + .. usage-example:: + :title: Modifying all species + :description: Providing `'all'` to either ``position`` or ``species``` + will modify the species of all atomgroups + + fm.switch_species('Fe-1', species='all') + """ from masci_tools.util.schema_dict_util import evaluate_attribute from masci_tools.util.xml.xml_setters_xpaths import xml_set_attrib_value @@ -989,8 +1517,7 @@ def shift_value(xmltree: XMLLike, changes: dict[str, Any], mode: Literal['abs', 'absolute', 'rel', 'relative'] = 'absolute', path_spec: dict[str, Any] | None = None) -> XMLLike: - """ - Shifts numerical values of attributes directly in the inp.xml file. + """Add/Multiply values of attributes occurring once in the XML tree The first occurrence of the attribute is shifted @@ -1007,6 +1534,21 @@ def shift_value(xmltree: XMLLike, An example of changes:: changes = {'itmax' : 1, 'dVac': -0.123} + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.shift_value({'itmax' : 5, 'mindistance': 1}) + + .. usage-example:: + :title: Relative shift + :description: Passing ``mode="rel"`` or ``mode="relative"`` will multiply + the initial attribute values with the provided number instead + of adding them + + fm.shift_value({'itmax' : 2, 'mindistance': 3}, mode="relative") + """ from masci_tools.util.case_insensitive_dict import CaseInsensitiveDict @@ -1028,8 +1570,7 @@ def set_inpchanges(xmltree: XMLLike, schema_dict: fleur_schema.SchemaDict, changes: dict[str, Any], path_spec: dict[str, Any] | None = None) -> XMLLike: - """ - This method sets all the attribute and texts provided in the change_dict. + """Set the value of attributes/text occurring once in the XML tree The first occurrence of the attribute/tag is set @@ -1048,6 +1589,28 @@ def set_inpchanges(xmltree: XMLLike, } :returns: an xmltree of the inp.xml file with changes. + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_inpchanges({'itmax' : 1, 'kmax': 4.3}) + + .. usage-example:: + :title: Attribute selection not unique + :result: Error + :description: If no or multiple locations could be possible an error is raised + + fm.set_inpchanges({'itmax' : 1, 'theta': 1.57}) + + .. usage-example:: + :title: Attribute selection + :description: Selection can be done by adding conditions on what the XPath should(n't) contain + in the ``path_spec`` argument + + fm.set_inpchanges({'itmax' : 1, 'theta': 1.57}, + path_spec={'theta': {'contains': 'soc'}}) + """ from masci_tools.util.xml.xml_setters_xpaths import xml_set_first_attrib_value, xml_set_first_text from masci_tools.util.xml.common_functions import split_off_attrib @@ -1091,16 +1654,6 @@ def set_xcfunctional(xmltree: XMLLike, """ Set the Exchange Correlation potential tag - Setting a inbuilt XC functional - .. code-block:: python - - set_xcfunctional(xmltree, schema_dict, 'vwn') - - Setting a LibXC XC functional - .. code-block:: python - - set_xcfunctional(xmltree, schema_dict, {'exchange': 'lda_x', 'correlation':"lda_c_xalpha"}, libxc=True) - :param xmltree: XML tree that represents inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input :param xc_functional: str or dict. If str it is the name of a inbuilt XC functional. If it is a dict it @@ -1110,6 +1663,20 @@ def set_xcfunctional(xmltree: XMLLike, :param libxc: bool if True the functional is a LibXC functional :returns: an xmltree with modified xcFunctional tag + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_xcfunctional('pbe') + + + .. usage-example:: + :title: Setting a LibXC functional + + fm.set_xcfunctional({'exchange': 'lda_x', 'correlation':"lda_c_xalpha"}, libxc=True) + + """ if not libxc and isinstance(xc_functional, dict): @@ -1143,9 +1710,10 @@ def set_kpointlist(xmltree: XMLLike, switch: bool = False, overwrite: bool = False, additional_attributes: dict[str, Any] | None = None) -> XMLLike: - """ - Explicitly create a kPointList from the given kpoints and weights. This routine will add the - specified kPointList with the given name. + """Create a k-point list with the given points and weights + + .. note:: + If no name is given, a name of the form ``default-`` is generated .. warning:: For input versions Max4 and older **all** keyword arguments are not valid (`name`, `kpoint_type`, @@ -1163,6 +1731,22 @@ def set_kpointlist(xmltree: XMLLike, :param overwrite: bool, if True and a kPointlist with the given name already exists it will be overwritten :returns: an xmltree of the inp.xml file with changes. + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_kpointlist([[0,0,0],[0.5,0.5,0.5]], + [1,1], switch=True) + + .. usage-example:: + :title: MaX 4 compatibility + :description: For input files before the MaX 5 release the previous kpoint list will always + be overwritten and the ``switch`` argument has no effect + :inputfile: inp_max4.xml + + fm.set_kpointlist([[0,0,0],[0.5,0.5,0.5]], + [1,1]) """ from masci_tools.util.xml.builder import FleurElementMaker from masci_tools.util.schema_dict_util import evaluate_attribute @@ -1271,8 +1855,7 @@ def set_kpointlist_max4(xmltree: XMLLike, @schema_dict_version_dispatch(output_schema=False) def switch_kpointset(xmltree: XMLLike, schema_dict: fleur_schema.SchemaDict, list_name: str) -> XMLLike: - """ - Switch the used k-point set + """Switch the used k-point set .. warning:: This method is only supported for input versions after the Max5 release @@ -1296,8 +1879,7 @@ def switch_kpointset(xmltree: XMLLike, schema_dict: fleur_schema.SchemaDict, lis @switch_kpointset.register(max_version='0.31') def switch_kpointset_max4(xmltree: XMLLike, schema_dict: fleur_schema.SchemaDict, list_name: str) -> XMLLike: - """ - Sets a k-point mesh directly into inp.xml specific for inputs of version Max4 + """Switch the used k-point set .. warning:: This method is only supported for input versions after the Max5 release @@ -1314,8 +1896,7 @@ def switch_kpointset_max4(xmltree: XMLLike, schema_dict: fleur_schema.SchemaDict @schema_dict_version_dispatch(output_schema=False) def set_nkpts(xmltree: XMLLike, schema_dict: fleur_schema.SchemaDict, count: int, gamma: bool = False) -> XMLLike: - """ - Sets a k-point mesh directly into inp.xml + """Sets a k-point mesh directly specific for inputs of version Max4 .. warning:: This method is only supported for input versions before the Max5 release @@ -1327,6 +1908,21 @@ def set_nkpts(xmltree: XMLLike, schema_dict: fleur_schema.SchemaDict, count: int in the k-point mesh :returns: an xmltree of the inp.xml file with changes. + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + :inputfile: inp_max4.xml + + fm.set_nkpts(200) + + + .. usage-example:: + :title: Include Gamma point + :inputfile: inp_max4.xml + + fm.set_nkpts(200, gamma=True) + """ raise NotImplementedError(f"'set_npkts' is not implemented for inputs of version '{schema_dict['inp_version']}'") @@ -1334,8 +1930,7 @@ def set_nkpts(xmltree: XMLLike, schema_dict: fleur_schema.SchemaDict, count: int @set_nkpts.register(max_version='0.31') def set_nkpts_max4(xmltree: XMLLike, schema_dict: fleur_schema.SchemaDict, count: int, gamma: bool = False) -> XMLLike: - """ - Sets a k-point mesh directly into inp.xml specific for inputs of version Max4 + """Sets a k-point mesh directly specific for inputs of version Max4 :param xmltree: xml tree that represents inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input @@ -1368,8 +1963,7 @@ def set_kpath(xmltree: XMLLike, kpath: dict[str, Iterable[float]], count: int, gamma: bool = False) -> XMLLike: - """ - Sets a k-path directly into inp.xml as a alternative kpoint set with purpose 'bands' + """Sets a k-path directly as an alternative kpoint set with purpose 'bands' .. warning:: This method is only supported for input versions before the Max5 release @@ -1382,6 +1976,14 @@ def set_kpath(xmltree: XMLLike, in the k-point mesh :returns: an xmltree of the inp.xml file with changes. + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + :inputfile: inp_max4.xml + + fm.set_kpath({'Point': [0, 0, 0], 'Point2': [1, 1, 1]}, 200) + """ raise NotImplementedError( @@ -1395,8 +1997,7 @@ def set_kpath_max4(xmltree: XMLLike, kpath: dict[str, Iterable[float]], count: int, gamma: bool = False) -> XMLLike: - """ - Sets a k-path directly into inp.xml as a alternative kpoint set with purpose 'bands' + """Sets a k-path directly as an alternative kpoint set with purpose 'bands' :param xmltree: xml tree that represents inp.xml :param schema_dict: InputSchemaDict containing all information about the structure of the input @@ -1458,6 +2059,25 @@ def set_kpointpath(xmltree: XMLLike, :param special_points: dict mapping names to coordinates for special points to use :returns: xmltree with a created kpoint path + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_kpointpath(nkpts=50, switch=True) + + .. usage-example:: + :title: Specifying custom points + :description: By default the path is chosen by ASE. By providing path + this can be modified + + fm.set_kpointpath(path='CA', + nkpts=25, + special_points={ + 'C': [0, 0, 0], + 'A': [0, 0, 0.5] + }) + """ from masci_tools.util.xml.xml_getters import get_cell from ase.dft.kpoints import bandpath @@ -1518,6 +2138,13 @@ def set_kpointmesh(xmltree: XMLLike, :param map_to_first_bz: bool if True the kpoints are mapped into the [0,1] interval :returns: xmltree with a created kpoint path + + Usage Examples (fm refers to an instance of :py:class:`~masci_tools.io.fleurxmlmodifier.FleurXMLModifier`) + + .. usage-example:: + + fm.set_kpointmesh([4, 4, 1], switch=True) + """ from masci_tools.util.xml.xml_getters import get_symmetry_information, get_cell from spglib import get_stabilized_reciprocal_mesh diff --git a/pyproject.toml b/pyproject.toml index d4d2c2796..e7d40b7d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,8 @@ docs = [ #1.19.3 is broken 'sphinx-autodoc-typehints!=1.19.3', 'myst-nb', - 'sphinx-copybutton' + 'sphinx-copybutton', + 'sphinx-design' ] testing = [ 'pytest~=6.0', diff --git a/utils/write_fleurxmlmodifier_docstrings.py b/utils/write_fleurxmlmodifier_docstrings.py index 24982a876..dd9b4bf62 100644 --- a/utils/write_fleurxmlmodifier_docstrings.py +++ b/utils/write_fleurxmlmodifier_docstrings.py @@ -26,21 +26,21 @@ def get_method_docstring(name, docstring, module): if all(x not in line for x in (':param xmltree:', ':param schema_dict:', ':param nmmplines:', ':returns'))] additional_lines = [ - f'Appends a :py:func:`~masci_tools.util.xml.{module}.{name}()` to', + 'This registration method does not modify the file immediately ' + f'but only appends a :py:func:`~masci_tools.util.xml.{module}.{name}()` to', 'the list of tasks that will be done on the xmltree.', '' ] - if lines[0]: - lines.insert(0, '') if lines[-1]: lines.append('') - for line in reversed(additional_lines): - lines.insert(1, line) + for line in additional_lines: + lines.append(line) while all(not line.strip() for line in lines[-2:]): lines.pop() lines = [2 * INDENT * ' ' + line if line.strip() else line.lstrip() for line in lines] lines[-1] = 2 * INDENT * ' ' + lines[0] = lines[0].lstrip() #Two levels of indentation have to be added since the docstrings go into methods return '\n'.join(lines)