User Guide
This guide covers the main features of HSD-Fortran with practical examples.
Basic Usage
Loading HSD Files
The most common operation is loading an HSD file into a tree structure:
use hsd
implicit none
type(hsd_table) :: root
type(hsd_error_t), allocatable :: error
! Load from file
call hsd_load("config.hsd", root, error)
if (allocated(error)) then
call error%print() ! Print formatted error message
stop 1
end if
You can also parse HSD from a string:
character(len=*), parameter :: hsd_content = &
'Driver { MaxSteps = 100 }'
call hsd_load_string(hsd_content, root, error)
Accessing Values
Use hsd_get to retrieve values from the tree using path notation:
integer :: max_steps, stat
real(dp) :: temperature
logical :: scc_enabled
character(len=:), allocatable :: method
! Access nested values with "/" separator
call hsd_get(root, "Driver/MaxSteps", max_steps, stat)
call hsd_get(root, "Hamiltonian/DFTB/Temperature", temperature, stat)
call hsd_get(root, "Hamiltonian/DFTB/SCC", scc_enabled, stat)
call hsd_get(root, "Hamiltonian/Method", method, stat)
! Check status
if (stat /= HSD_STAT_OK) then
print *, "Value not found or type error"
end if
Default Values
Use hsd_get_or when you want a default value if the key is missing:
integer :: timeout, seed
real(dp) :: tolerance
! Returns default if path not found
call hsd_get_or(root, "Driver/Timeout", timeout, default=3600, stat=stat)
call hsd_get_or(root, "Options/RandomSeed", seed, default=12345, stat=stat)
call hsd_get_or(root, "Tolerance", tolerance, default=1.0e-6_dp, stat=stat)
! stat will be HSD_STAT_NOT_FOUND if default was used
if (stat == HSD_STAT_NOT_FOUND) then
print *, "Using default value"
end if
Working with Arrays
HSD supports arrays with space or comma-separated values:
# In HSD file
Values = 1 2 3 4 5
Coords = 1.0, 2.5, 3.0
Elements = C H O N
Reading arrays in Fortran:
integer, allocatable :: int_arr(:)
real(dp), allocatable :: real_arr(:)
character(len=:), allocatable :: str_arr(:)
call hsd_get(root, "Values", int_arr, stat)
call hsd_get(root, "Coords", real_arr, stat)
call hsd_get(root, "Elements", str_arr, stat)
Working with Matrices
Multi-line data in HSD represents matrices:
KPoints {
4 0 0
0 4 0
0 0 4
}
Use hsd_get_matrix to read 2D arrays:
real(dp), allocatable :: kpoints(:,:)
integer :: stat
call hsd_get_matrix(root, "KPoints", kpoints, stat)
! kpoints is now a 3x3 array
Type Introspection
Query the structure before accessing values:
! Check if a path exists
if (hsd_has_child(root, "Hamiltonian/DFTB")) then
print *, "DFTB block exists"
end if
! Check node type
if (hsd_is_table(root, "Hamiltonian")) then
print *, "Hamiltonian is a table (has children)"
end if
if (hsd_is_value(root, "Driver/MaxSteps")) then
print *, "MaxSteps is a leaf value"
end if
! Check if value is an array
if (hsd_is_array(root, "Values")) then
print *, "Values contains an array"
end if
! Count children
print *, "Number of children:", hsd_child_count(root, "Hamiltonian")
! Get all child keys
character(len=:), allocatable :: keys(:)
call hsd_get_keys(root, "Hamiltonian", keys)
Working with Attributes (Units)
HSD supports attributes, commonly used for physical units:
Temperature [Kelvin] = 300.0
MaxForce [eV/Angstrom] = 0.001
Access attributes in Fortran:
character(len=:), allocatable :: unit
real(dp) :: temperature
! Get the attribute (unit)
call hsd_get_attrib(root, "Temperature", unit, stat)
if (stat == HSD_STAT_OK) then
print *, "Unit:", unit ! "Kelvin"
end if
! Check if attribute exists
if (hsd_has_attrib(root, "Temperature")) then
print *, "Temperature has a unit specified"
end if
! Get value with unit conversion (custom helper)
call hsd_get_with_unit(root, "Temperature", temperature, "Kelvin", stat)
Modifying Trees
Use hsd_set to modify values:
! Set scalar values
call hsd_set(root, "Driver/MaxSteps", 200)
call hsd_set(root, "Hamiltonian/DFTB/SCC", .true.)
call hsd_set(root, "Name", "my_calculation")
! Set arrays
integer :: values(5) = [1, 2, 3, 4, 5]
call hsd_set(root, "NewValues", values)
Remove children:
call hsd_remove_child(root, "OldSection")
Saving HSD Files
Write the tree back to a file:
! Write to file
call hsd_dump(root, "output.hsd")
! Or get as string
character(len=:), allocatable :: output
call hsd_dump_to_string(root, output)
print *, output
Tree Operations
Cloning Trees
Create an independent copy of a tree:
type(hsd_table) :: copy
call hsd_clone(root, copy)
! Modify copy without affecting root
Merging Trees
Combine two trees (source overwrites target for conflicts):
type(hsd_table) :: defaults, user_config, merged
call hsd_load("defaults.hsd", defaults, error)
call hsd_load("user.hsd", user_config, error)
call hsd_clone(defaults, merged)
call hsd_merge(merged, user_config) ! User settings override defaults
Schema Validation
Define and validate input schemas:
use hsd
implicit none
type(hsd_schema_t) :: schema
type(hsd_error_t), allocatable :: error
! Initialize schema
call schema_init(schema)
! Add required fields
call schema_add_field(schema, "Driver/MaxSteps", &
field_type=FIELD_TYPE_INTEGER, required=FIELD_REQUIRED)
call schema_add_field(schema, "Hamiltonian/DFTB/SCC", &
field_type=FIELD_TYPE_LOGICAL, required=FIELD_REQUIRED)
! Add optional field with allowed values
call schema_add_field_enum(schema, "Driver/Method", &
allowed_values=["ConjugateGradient", "SteepestDescent", "FIRE"], &
required=FIELD_OPTIONAL)
! Validate
call schema_validate(schema, root, error)
if (allocated(error)) then
print *, "Validation failed:", error%message
end if
! Strict validation (fails on unknown fields)
call schema_validate_strict(schema, root, error)
! Cleanup
call schema_destroy(schema)
Validation Helpers
Additional validation utilities:
integer :: max_steps
real(dp) :: tolerance
character(len=:), allocatable :: method
! Require a value (error if missing)
call hsd_require(root, "Driver/MaxSteps", max_steps, error)
if (allocated(error)) stop 1
! Validate numeric range
call hsd_validate_range(max_steps, min_val=1, max_val=10000, error)
! Validate against allowed values
call hsd_validate_one_of(method, ["option1", "option2", "option3"], error)
Visitor Pattern
Traverse the entire tree with custom logic:
type, extends(hsd_visitor_t) :: tree_printer
contains
procedure :: visit_table => print_table
procedure :: visit_value => print_value
end type
type(tree_printer) :: printer
call hsd_accept(root, printer)
contains
subroutine print_table(self, table, path, depth, stat)
class(tree_printer), intent(inout) :: self
type(hsd_table), intent(in) :: table
character(len=*), intent(in) :: path
integer, intent(in) :: depth
integer, intent(out), optional :: stat
print *, repeat(" ", depth), "Table: ", trim(path)
if (present(stat)) stat = 0
end subroutine
subroutine print_value(self, value, path, depth, stat)
class(tree_printer), intent(inout) :: self
type(hsd_value), intent(in) :: value
character(len=*), intent(in) :: path
integer, intent(in) :: depth
integer, intent(out), optional :: stat
print *, repeat(" ", depth), "Value: ", trim(path), " = ", value%get_string()
if (present(stat)) stat = 0
end subroutine
Best Practices
Always check for errors after parsing and validation
Use schemas for complex input validation
Clone trees before modifying if you need the original
Use path notation for cleaner code instead of manual tree traversal
Validate ranges for numeric inputs
Provide defaults for optional configuration values
Example: Complete Configuration Parser
subroutine load_config(filename, config, error)
character(len=*), intent(in) :: filename
type(my_config), intent(out) :: config
type(hsd_error_t), allocatable, intent(out) :: error
type(hsd_table) :: root
type(hsd_schema_t) :: schema
integer :: stat
! Load file
call hsd_load(filename, root, error)
if (allocated(error)) return
! Setup schema
call schema_init(schema)
call schema_add_field(schema, "MaxIterations", FIELD_TYPE_INTEGER, FIELD_REQUIRED)
call schema_add_field(schema, "Tolerance", FIELD_TYPE_REAL, FIELD_OPTIONAL)
call schema_add_field(schema, "Method", FIELD_TYPE_STRING, FIELD_OPTIONAL)
! Validate
call schema_validate(schema, root, error)
call schema_destroy(schema)
if (allocated(error)) return
! Extract values
call hsd_get(root, "MaxIterations", config%max_iter, stat)
call hsd_get_or(root, "Tolerance", config%tolerance, default=1.0e-6_dp, stat=stat)
call hsd_get_or(root, "Method", config%method, default="default", stat=stat)
! Validate ranges
call hsd_validate_range(config%max_iter, 1, 100000, error)
end subroutine