Adding New JSON Output Fields
This guide explains how to add new fields to the JSON output in metalquicha.
Overview
JSON output is centralized in a single location. All calculation workflows populate
a json_output_data_t container, and the driver calls write_json_output() once
at the end to write everything to disk.
Key files:
src/core/mqc_json_output_types.f90- Container type definitionsrc/io/mqc_json_writer.f90- JSON writing logic
Step-by-Step Guide
1. Add the Field to the Container Type
Edit src/core/mqc_json_output_types.f90 and add your new field to the
json_output_data_t type:
type :: json_output_data_t
! ... existing fields ...
! Your new field
real(dp), allocatable :: mulliken_charges(:)
logical :: has_mulliken_charges = .false.
! ... rest of type ...
end type
Conventions:
Use
has_<field>boolean flags for optional dataInitialize booleans to
.false.in the type definitionUse allocatable arrays for variable-size data
2. Update the Cleanup Procedures
In the same file, update json_output_data_destroy and json_output_data_reset:
subroutine json_output_data_destroy(self)
class(json_output_data_t), intent(inout) :: self
! ... existing deallocations ...
if (allocated(self%mulliken_charges)) deallocate(self%mulliken_charges)
end subroutine
subroutine json_output_data_reset(self)
class(json_output_data_t), intent(inout) :: self
call self%destroy()
! ... existing resets ...
self%has_mulliken_charges = .false.
end subroutine
3. Populate the Field in Your Workflow
In the workflow module that computes your data (e.g., mqc_unfragmented_workflow.f90),
populate the field when json_data is present:
if (present(json_data)) then
! Populate your new field
allocate(json_data%mulliken_charges(n_atoms))
json_data%mulliken_charges = calculated_charges
json_data%has_mulliken_charges = .true.
end if
4. Add JSON Writing Logic
Edit src/io/mqc_json_writer.f90 and add the JSON output in the appropriate
write_*_impl subroutine:
subroutine write_unfragmented_json_impl(data)
! ... existing code ...
! Add your field to the JSON
if (data%has_mulliken_charges) then
call json%add(main_obj, 'mulliken_charges', data%mulliken_charges)
end if
! ... rest of subroutine ...
end subroutine
Which subroutine to modify:
write_unfragmented_json_impl- For unfragmented calculationswrite_mbe_breakdown_json_impl- For MBE fragmented calculationswrite_gmbe_pie_json_impl- For GMBE calculationswrite_vibrational_json_impl- For vibrational/thermochemistry data
If your field applies to multiple calculation types, add it to each relevant subroutine.
5. Build and Test
cmake --build build -j
./build/mqc your_input.mqc
cat output_your_input.json
Example: Adding Orbital Energies
Here’s a complete example of adding HOMO/LUMO energies:
1. In mqc_json_output_types.f90:
type :: json_output_data_t
! ... existing fields ...
real(dp) :: homo_energy = 0.0_dp
real(dp) :: lumo_energy = 0.0_dp
logical :: has_orbital_energies = .false.
end type
2. In your workflow (e.g., mqc_unfragmented_workflow.f90):
if (present(json_data)) then
json_data%homo_energy = result%homo
json_data%lumo_energy = result%lumo
json_data%has_orbital_energies = .true.
end if
3. In mqc_json_writer.f90:
if (data%has_orbital_energies) then
call json%create_object(orbitals_obj, 'orbital_energies')
call json%add(main_obj, orbitals_obj)
call json%add(orbitals_obj, 'homo_hartree', data%homo_energy)
call json%add(orbitals_obj, 'lumo_hartree', data%lumo_energy)
call json%add(orbitals_obj, 'gap_hartree', data%lumo_energy - data%homo_energy)
end if
Architecture Notes
The JSON output flow:
run_calculation()
|
+-- workflow populates json_data fields
|
+-- write_json_output(json_data) <-- SINGLE write point
|
+-- dispatches based on output_mode
|
+-- calls appropriate write_*_impl()
Output modes (set automatically by workflows):
OUTPUT_MODE_UNFRAGMENTED- Single molecule calculationOUTPUT_MODE_MBE- Many-Body ExpansionOUTPUT_MODE_GMBE_PIE- Generalized MBE with PIE
The output_mode determines which write_*_impl function is called, but you
can add shared fields (like orbital energies) to multiple implementations if needed.