Skip to content

Commit

Permalink
Implement HSD-wrappers to manipulate nested content
Browse files Browse the repository at this point in the history
  • Loading branch information
aradi committed May 29, 2023
1 parent 0d97116 commit a65c13a
Show file tree
Hide file tree
Showing 9 changed files with 815 additions and 22 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
*~
.idea
.env
.vscode
*.pyc
dist
build
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
# a list of builtin themes.
#
# html_theme = 'alabaster'
html_theme = 'sphinx_rtd_theme'
html_theme = 'sphinx_book_theme'

# 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,
Expand Down
81 changes: 81 additions & 0 deletions docs/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ or into the user space issueing ::
Quick tutorial
==============

The basics
----------

A typical, self-explaining input written in HSD looks like ::

driver {
Expand Down Expand Up @@ -117,3 +120,81 @@ Python ::
and then stored again in HSD format ::

hsd.dump(hsdinput, "test2.hsd")


Accesing nested data structures via wrappers
--------------------------------------------

The hsd module contains lightweight wrappers (``HsdDict``, ``HsdList`` and
``HsdValue``), which offer convenient access to entries in nested data
structures. With the help of these wrappers, nested nodes and values can be
directly accessed using paths. When accessing nested content via wrappers, the
resulting objects will be wrappers themself, wrapping the appropriate parts of
the data structure (and inheriting certain properties of the original wrapper).

For example, reading and wrapping the example above::

import hsd
hsdinp = hsd.wrap(hsd.load("test.hsd"))

creates an ``HsdDict`` wrapper instance (``hsdinp``), which can be used to query
encapsulated information in the structure::

# Reading out the value directly (100)
maxsteps = hsdinp["driver", "conjugate_gradients", "max_steps"].value

# Storing wrapper (HsdValue) instance and reading out value and the attribute
temp = hsdinp["hamiltonian / dftb / filling / fermi / temperature"]
temp_value = temp.value
temp_unit = temp.attrib

# Getting a default value, if a given path does not exists:
pot = hsdinp.get_item("hamiltonian / dftb / bias", default=hsd.HsdValue(100, attrib="V"))

# Setting a value for given path by creating missing parents
hsdinp.set_item("analysis / calculate_forces", True, parents=True)

As demonstrated above, paths can be specified as tuples or as slash (``/``) joined strings.

The wrappers also support case-insensitive access. Let's have a look at a
mixed-case example file ``test2.hsd``::

Driver {
ConjugateGradients {
MovedAtoms = 1 2 "7:19"
MaxSteps = 100
}

We now make copy of the data structure before wrapping it, and make sure that
all keys are converted to lower case, but the original names are saved as
HSD-attributes::

hsdinp = hsd.copy(hsd.load("test2.hsd"), lower_names=True, save_names=True)

This way, paths passed to the Hsd-wrapper are treated in a case-insensitive
way::

maxsteps = hsdinp["driver", "CONJUGATEGRADIENTS", "MAXSTEPS"].value

When adding new items, the access is and remains case in-sensitive, but the
actual form of the name of the new node will be saved. The code snippet::

hsdinp["driver", "conjugategradients", "MaxForce"] = hsd.HsdValue(1e-4, attrib="au")
maxforceval = hsdinp["driver", "conjugategradients", "maxforce"]
print(f"{maxforceval.value} {maxforceval.attrib}")
print(hsd.dump_string(hsdinp.value, use_hsd_attribs=True))

will result in ::

0.0001 au
Driver {
ConjugateGradients {
MovedAtoms = 1 2 "7:19"
MaxSteps = 100
MaxForce [au] = 0.0001
}
}

where the case-convention for ``MaxForce`` is identical to the one used when the
item was created.
6 changes: 3 additions & 3 deletions src/hsd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
"""
Toolbox for reading, writing and manipulating HSD-data.
"""
from hsd.common import HSD_ATTRIB_LINE, HSD_ATTRIB_EQUAL, HSD_ATTRIB_SUFFIX,\
HSD_ATTRIB_NAME, HsdError
from hsd.dict import HsdDictBuilder, HsdDictWalker
from hsd.common import HSD_ATTRIB_LINE, HSD_ATTRIB_EQUAL, HSD_ATTRIB_NAME, HsdError
from hsd.dict import ATTRIB_KEY_SUFFIX, HSD_ATTRIB_KEY_SUFFIX, HsdDictBuilder, HsdDictWalker
from hsd.eventhandler import HsdEventHandler, HsdEventPrinter
from hsd.formatter import HsdFormatter
from hsd.io import load, load_string, dump, dump_string
from hsd.parser import HsdParser
from hsd.wrappers import HsdDict, HsdList, HsdValue, copy, wrap

__version__ = '0.1'
6 changes: 0 additions & 6 deletions src/hsd/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ def unquote(txt):
# Name for default attribute (when attribute name is not specified)
DEFAULT_ATTRIBUTE = "unit"

# Suffix to mark attribute
ATTRIB_SUFFIX = ".attrib"

# Suffix to mark hsd processing attributes
HSD_ATTRIB_SUFFIX = ".hsdattrib"

# HSD attribute containing the original tag name
HSD_ATTRIB_NAME = "name"

Expand Down
29 changes: 18 additions & 11 deletions src/hsd/dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@
"""
import re
from typing import List, Tuple, Union
from hsd.common import HSD_ATTRIB_NAME, np, ATTRIB_SUFFIX, HSD_ATTRIB_SUFFIX, HsdError,\
QUOTING_CHARS, SPECIAL_CHARS
from hsd.common import HSD_ATTRIB_NAME, np, HsdError, QUOTING_CHARS, SPECIAL_CHARS
from hsd.eventhandler import HsdEventHandler, HsdEventPrinter


# Dictionary key suffix to mark attribute
ATTRIB_KEY_SUFFIX = ".attrib"

# Dictionary keysuffix to mark hsd processing attributes
HSD_ATTRIB_KEY_SUFFIX = ".hsdattrib"


_ItemType = Union[float, complex, int, bool, str]

_DataType = Union[_ItemType, List[_ItemType]]
Expand Down Expand Up @@ -130,26 +137,26 @@ def close_tag(self, tagname):
parentblock[key] = [{None: prevcont}, self._curblock]

if attrib and prevcont is None:
parentblock[key + ATTRIB_SUFFIX] = attrib
parentblock[key + ATTRIB_KEY_SUFFIX] = attrib
elif prevcont is not None:
prevattrib = parentblock.get(key + ATTRIB_SUFFIX)
prevattrib = parentblock.get(key + ATTRIB_KEY_SUFFIX)
if isinstance(prevattrib, list):
prevattrib.append(attrib)
else:
parentblock[key + ATTRIB_SUFFIX] = [prevattrib, attrib]
parentblock[key + ATTRIB_KEY_SUFFIX] = [prevattrib, attrib]

if self._include_hsd_attribs:
if self._lower_tag_names:
hsdattrib = {} if hsdattrib is None else hsdattrib
hsdattrib[HSD_ATTRIB_NAME] = tagname
if prevcont is None:
parentblock[key + HSD_ATTRIB_SUFFIX] = hsdattrib
parentblock[key + HSD_ATTRIB_KEY_SUFFIX] = hsdattrib
else:
prevhsdattrib = parentblock.get(key + HSD_ATTRIB_SUFFIX)
prevhsdattrib = parentblock.get(key + HSD_ATTRIB_KEY_SUFFIX)
if isinstance(prevhsdattrib, list):
prevhsdattrib.append(hsdattrib)
else:
parentblock[key + HSD_ATTRIB_SUFFIX] = [prevhsdattrib, hsdattrib]
parentblock[key + HSD_ATTRIB_KEY_SUFFIX] = [prevhsdattrib, hsdattrib]
self._curblock = parentblock
self._data = None

Expand Down Expand Up @@ -219,11 +226,11 @@ def walk(self, dictobj):

for key, value in dictobj.items():

if key.endswith(ATTRIB_SUFFIX) or key.endswith(HSD_ATTRIB_SUFFIX):
if key.endswith(ATTRIB_KEY_SUFFIX) or key.endswith(HSD_ATTRIB_KEY_SUFFIX):
continue

hsdattrib = dictobj.get(key + HSD_ATTRIB_SUFFIX)
attrib = dictobj.get(key + ATTRIB_SUFFIX)
hsdattrib = dictobj.get(key + HSD_ATTRIB_KEY_SUFFIX)
attrib = dictobj.get(key + ATTRIB_KEY_SUFFIX)

if isinstance(value, dict):

Expand Down
3 changes: 2 additions & 1 deletion src/hsd/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""
Provides functionality to dump Python structures to HSD
"""
from collections.abc import Mapping
import io
from typing import Union, TextIO
from hsd.dict import HsdDictWalker, HsdDictBuilder
Expand Down Expand Up @@ -155,7 +156,7 @@ def dump(data: dict, hsdfile: Union[TextIO, str],
See :func:`hsd.load_string` for an example.
"""
if not isinstance(data, dict):
if not isinstance(data, Mapping):
msg = "Invalid object type"
raise TypeError(msg)
if isinstance(hsdfile, str):
Expand Down
Loading

0 comments on commit a65c13a

Please sign in to comment.