libPSML API

The API for libPSML follows very closely the structure of the schema.

Exported types

The library exports the opaque types:

  • ps_t: Main handle for all the libPSML routines. It contains data structures resulting from parsing

  • ps_annotation_t: A handle to deal with the content of <annotation> elements in the PSML file. More information on annotations can be found here.

  • ps_radfunc_t: A handle for the data structures implementing the functionality of radial functions, in particular their evaluation.

More information about the associated implementations can be found in the developer notes.

In addition, the library exports the integer parameter ps_real_kind that represents the kind of the real numbers accepted and returned by the library.

Parsing

  • The routine psml_reader (filename, ps, debug) parses the PSML file filename and populates the data structures in the handle ps. An optional debug argument determines whether the library issues debugging messages while parsing.

  • ps_destroy (ps) is a low-level routine provided for completeness in cases where a pristine ps is needed for further use.

Library identification

  • The function ps_GetLibPSMLVersion returns the version as an integer (for example: 1106) instead of the typical dot form (1.1.6).

Data accessors

The API follows closely the element structure of the PSML format. Each section in the high-level document structure of the schema is mapped to a group of routines in the API. Within each, there are routines to query any internal structure (attributes, existence, number, or selection of child elements) and routines to obtain specific data items (attributes, content of child elements).

Root attributes and namespace

The namespace and the attributes of the root element

default namespace = "http://esl.cecam.org/PSML/ns/1.1"

Root.Attributes =  attribute energy_unit { "hartree" }
                 , attribute length_unit { "bohr" }
                 , attribute uuid { xsd:NMTOKEN }
                 , attribute version { xsd:decimal }

are read by the routine ps_RootAttributes_Get (ps,uuid,version,namespace). As in all the routines that follow, the handle ps is mandatory. All other arguments are optional, with intent(out), and of type character(len=*). The argument version returns the PSML version of the file being processed. A given version of the library is able to process files with lower version numbers, up to a limit.

Provenance data

Provenance =  element provenance {
                attribute record-number { xsd:positiveInteger }?
              , attribute creator { xsd:string }
              , attribute date { xsd:string }

              , Annotation?
          , InputFile*    # zero or more input files
              }

InputFile =  element input-file {
               attribute name { xsd:NMTOKEN }, # No spaces or commas allowed
               text
             }

As there can be several <provenance> elements, the API provides a function to enquire about their number (depth of provenance information), and a routine to get the information from a given level:

The integer argument level selects the provenance depth level (1 is the deepest, or older, so to get the latest record the routine should be called with level=depth as returned from the previous routine). All other arguments are optional with intent(out). creator and date are strings. Here and in what follows, annotation arguments are of the opaque type ps_annotation_t. If there is no annotation, an empty structure is returned.

Pseudo-atom specification attributes and annotation

PseudoAtomSpec.Attributes = 
       attribute atomic-label { xsd:NMTOKEN },
       attribute atomic-number { xsd:double },
       attribute z-pseudo { xsd:double },
       attribute core-corrections { "yes" | "no" },
       attribute relativity { "no" | "scalar" | "dirac" },
       attribute spin-dft { "yes" | "no" }?,
       attribute flavor { xsd:string }?
  • ps_PseudoAtomSpec_Get (ps, atomic_symbol, atomic_label, atomic_number, z_pseudo, pseudo_flavor, relativity, spin_dft, core_corrections, annotation)

The arguments spin_dft and core_corrections are boolean, and the routine returns an empty string in flavor if the attribute is not present (recall that flavor is a cascading attribute that can be set at multiple levels).

Valence configuration

ValenceConfiguration =  element valence-configuration {
                          attribute total-valence-charge { xsd:double },
                      Annotation?,
                          ValenceShell+
                        }

ValenceShell =   Shell

Shell =  element shell {
           attribute_l,
           attribute_n,
           attribute occupation { xsd:double },
           attribute occupation-up { xsd:double }?,
           attribute occupation-down { xsd:double }?
         }

attribute_l = attribute l { "s" | "p" | "d" | "f" | "g" }
attribute_n = attribute n { "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" }

This routine returns (as always, in optional arguments), the values of the top-level attributes, any annotation, and the number of Shell elements, which serves as upper limit for the index i in the following routine, which extracts shell information:

The n and l quantum number arguments are integers (despite the use of spectroscopic symbols for the angular momentum in the format), and the occupations real.

Exchange and correlation

ExchangeCorrelation =  element exchange-correlation {
                         Annotation?
                         , element libxc-info {
                              attribute number-of-functionals { xsd:positiveInteger },
                              LibxcFunctional+
                           }
                        }

LibxcFunctional =   element functional {
                      attribute id { xsd:positiveInteger },
                      attribute name { xsd:string },
                      attribute weight { xsd:double }?,

                      # allow canonical names and libxc-style symbols

                      attribute type { "exchange" | "correlation" | "exchange-correlation" |
                                   "XC_EXCHANGE" | "XC_CORRELATION" |
                              "XC_EXCHANGE_CORRELATION" }?
                    }

The routines follow the same structure as those in the previous section.

Valence and Core Charges

ValenceCharge =  element valence-charge {
                   attribute total-charge { xsd:double },
                   attribute is-unscreening-charge { "yes" | "no" }?,
                   attribute rescaled-to-z-pseudo { "yes" | "no" }?,
                   Annotation?,
                   Radfunc
                 }  

# =========
CoreCharge =  element pseudocore-charge {
                attribute matching-radius { xsd:double },
                attribute number-of-continuous-derivatives { xsd:nonNegativeInteger },
                Annotation?,
                Radfunc
              }

These are radial functions with some metadata in the form of attributes, an optional annotation, and a Radfunc child. The accessors have the extra optional argument func that returns a handle to a ps_radfunc_t object, which can later be used to get extra information.

  • ps_ValenceCharge_Get (ps,total_charge, is_unscreening_charge, rescaled_to_z_pseudo, annotation,func)

The routine returns an emtpy string in is_unscreening_charge and rescaled_to_z_pseudo if the attributes are not present in the PSML file.

rc corresponds to the matching radius and nderivs to the continuity information. Negative values are returned if the corresponding attributes are not present in the file.

The func object can be used to evaluate the radial functions at a particular point r:

but the API offers some convenience functions

Local Potential and Local Charge Density

LocalPotential =  element local-potential {
                    attribute type { xsd:string },
                    Annotation?,
                    Grid?,
                    Radfunc,
            LocalCharge?  # Optional local-charge element
                  }

LocalCharge =  element local-charge {
             Radfunc
           }

In this version of the API, the optional <local-charge> element is not given a first-class status. To evaluate it (if the boolean argument has_local_charge is .true.), the func_local_charge argument has to be used in the ps_GetValue routine above. The local potential can be evaluated via the func object or with the convenience function

Semilocal potentials

SemiLocalPotentials =  element semilocal-potentials {
                         attribute_set,
                         attribute flavor  { xsd:string }?,
                         Annotation?,
             Grid?,
                         Potential+
                        }

Potential =   element slps {
                attribute flavor { xsd:string }?,
                attribute_l,
                attribute_j ?,
                attribute_n,
                attribute rc { xsd:double },
                attribute eref { xsd:double }?,
                Radfunc
              }   

As explained here there can be several <semilocal-potentials> elements corresponding to different sets. Internally, the data is built up in linked lists during the parsing stage and later all the data for the <slps> child elements are re-arranged into flat tables, which can be queried like a simple database. The table indexes for the potentials with specific quantum numbers, or set membership, can be obtained with the routine

  • ps_SemilocalPotentials_Filter (ps,indexes_in,l,j,n,set,indexes,number)

    • indexes_in: (Optional, in) Initial set of indexes on which to perform the filtering operation. If not present, the full table is used.
    • l,j,n,set: (Optional, in) Values for filtering criteria.
    • indexes: (Optional, out) Set of indexes which satisfy the criteria.
    • number: (Optional, out) Number of items which satisfy the criteria.

The set argument has to be given using special integer symbols exported by the API, as explained here.

The appropriate indexes can then be fed into the following routines to get specific information:

All arguments except ps and i are optional. The value returned in set is an integer which can be converted to a mnemonic string through the str_of_set convenience function. The annotation returned corresponds to the optional <annotation> element of the parent block of the <slps> element.

The routine returns a very large positive value in eref if the corresponding attribute is not present in the file.

Nonlocal Projectors

NonLocalProjectors =  element nonlocal-projectors {
                        attribute_set,
                        Annotation?,
                        Grid?,
                    Projector+
                      }

Projector =   element proj {
                attribute ekb { xsd:double },
                attribute eref { xsd:double }?,
                attribute_l,
                attribute_j ?,
                attribute seq { xsd:positiveInteger },
                attribute type { xsd:string },
                Radfunc
              }+

The ideas are exactly the same as for the semilocal potentials. The relevant routines are:

The routine returns a very large positive value in eref if the corresponding attribute is not present in the file.

Pseudo Wavefunctions

PseudoWaveFunctions =  element pseudo-wave-functions {
                         attribute_set,
                         Annotation?,
                         Grid?,
                         PseudoWf+
                       }

PseudoWf =  element pswf {
              attribute_l,
              attribute_j ?,
              attribute_n,
              attribute energy_level { xsd:double} ?,
              Radfunc
            }

Again, the same strategy:

The routine returns a very large positive value in energy_level if the corresponding attribute is not present in the file.

Radial function and grid information

Radfunc =  element radfunc {
             Grid?,              # Optional grid element
             element data {      
               list { xsd:double+ }       # One or more floating point numbers
             }
           }

Grid =  element grid {
          attribute npts { xsd:positiveInteger },
          Annotation?,
          element grid-data {
                 list { xsd:double+ }   # One or more floating point numbers
          }
        }

In keeping with the PSML philosophy of being grid-agnostic, the basic API does not provide any direct means of accessing the data used in the tabulation of the radial functions. The values of the functions at a particular point r can be generally obtained through the ps_XXXX_Value interfaces, or through the ps_GetValue interface using func objects of type ps_radfunc_t.

It is nevertheless possible to get annotation data for the grid of a particular radial function, or for the top-level grid, through the function

If a radial function handle func is given, the annotation for that radial function's grid is returned. Otherwise, the return value is the annotation for the top-level grid.

The evaluation engine

In the current version of the library the evaluation of tabulated functions is performed by default with polynomial interpolation, using a slightly modified version of an algorithm borrowed (with permission) from the oncvpsp program by D.R. Hamann. By default seventh-order interpolation, as in oncvpsp, is used. If the library is compiled with the appropriate pre-processor symbols, the interpolator and/or its order can be chosen at runtime, but we note that this should be considered a debugging feature. Reproducibility of results would be hampered if client codes change the interpolation parameters at will. Generator codes should instead strive to produce data tabulations that will guarantee a given level of precision when interpolated with the default scheme, using appropriate output grids on which to sample their internal data sets. For example, our own work on enabling PSML output in oncvpsp (see below) includes diagnostic tools to check the interpolation accuracy.

Most codes use internally a non-uniform grid (e.g. logarithmic). We have found that a good choice of output grid is a subset of the producer's working grid points that leaves out most of the very close points near the origin but maintains the rest. This can be achieved by imposing a minimum inter-point separation $\delta$. This parameter $\delta$ can be smaller than the typical linear-grid step used currently by most codes, and still lead to smaller grids (in terms of number of points) that preserve the accuracy of the output.

High-order interpolation can lead to ringing effects (oscillations of the interpolating polynomial between points), notably near edge regions when the shape of the function changes abruptly. This is the case, for example, if the function drops to zero within the interpolation range as a result of cutting off a tail. The actual interpolated values will typically be very small, but might cause undesirable effects in the client code. To avoid this problem, the libPSML evaluator works internally with an effective end-of-range that is determined by analyzing the data values after parsing.

If needed for debugging purposes, the evaluator engine can be configured by the routine:

All arguments are optional, and apply globally to the operation of the library. The custom_interpolator argument is not allowed if the underlying Fortran compiler does not support procedure pointers. quality_level (an integer) is by default and will typically be the interpolation order, but its meaning can change with the interpolator in use. The evaluator uses an effective range by default, as discussed above, but this feature can be turned off by setting use_effective_range to .false.. The debug argument will turn on any extra printing configured in the evaluator. By default, no extra printing is produced.

Finally, in case it is necessary to look at the raw tabular data for debugging purposes, the library also provides a low-level routine:

If a radial function handle func is given, the grid points and the actual tabulated data are returned in rg and data, which must be passed as allocatable arrays.

Predicate routines for contents

The following convenience functions return a logical value. For semi-local potentials, non-local projectors, and pseudo-wave-functions, the test is done on the union of all possible sets.

Editing of ps structures

The PSML library has currently limited support for editing the content of ps_t objects from user programs. For example, such an editing might be done by a KB-projector generator to insert a new provenance record (and KB and local-potential data) in the ps object, prior to dumping to a new PSML file.

Annotations can be created using routines exported by the PSML API (see below)

Dump of ps structures

The contents of a (possibly edited) ps_t object can be dumped to a PSML file using the routine

Here fname is the output file name, and indent is a logical variable that determines whether automatic indenting of elements is turned on (by default it is not).

Annotation API

To support the annotation functionality, the library contains a module implementing a basic instance of an association list (a data structure holding key-value pairs), and exports the ps_annotation_t type, the empty annotation object EMPTY_ANNOTATION and the following routines:

(The names of these routines are aliases of the originals in the assoc_list module.)

  • reset_annotation (annotation)

Cleans the contents of the ps_annotation_t object annotation so that it can be reused.

  • insert_annotation_pair (annotation,key,value,stat)

Inserts the key, value pair of string variables in the ps_annotation_t object annotation. Internally, annotation can grow as much as needed.

  • function nitems_annotation (annotation) result(nitems)

Returns the number of key-value pairs in the annotation object

  • get_annotation_value (annotation,key,value,stat)
  • get_annotation_value (annotation,i,value,stat)

This routine has two interfaces. The first gets the value associated to the key, and the second gets the value associated to the i'th entry in the annotation object.

  • get_annotation_key (annotation,i,key,stat)

Gets the key of the i'th entry in the annotation object.

Together with the second form of get_annotation_value, this routine can be used to scan the complete annotation object. The first form of get_annotation_value is appropriate if the key(s) are known.

In all the above routines a non-zero stat signals an error condition.