Settings
Every algorithm in QDK/Chemistry is configured through a Settings object—a type-safe key-value store for customizing algorithm behavior.
This unified configuration system simplifies saving, loading, and sharing settings across workflows.
Discovering available settings
When working with an algorithm, it is often necessary to determine which settings are available and their current values. QDK/Chemistry provides several methods for discovering this information:
- Documentation
Each algorithm’s documentation page includes an “Available settings” section that lists all supported parameters, their types, default values, and descriptions. For examples, see ScfSolver or MultiConfigurationCalculator.
- Runtime inspection
Settings can be inspected programmatically using methods such as
keys(),items(), orprint_settings(). This approach is particularly useful when exploring unfamiliar implementations or verifying configurations.
The following example demonstrates how to discover available algorithms and inspect their settings:
#include <qdk/chemistry/algorithms/scf.hpp>
// List available implementations for a specific algorithm type
auto scf_names = qdk::chemistry::algorithms::ScfSolverFactory::available();
for (const auto& name : scf_names) {
std::cout << "SCF solver: " << name << std::endl;
}
// Create an instance and inspect its settings
auto scf = qdk::chemistry::algorithms::ScfSolverFactory::create();
for (const auto& key : scf->settings().keys()) {
std::cout << " " << key << ": " << scf->settings().get_as_string(key)
<< std::endl;
}
from qdk_chemistry.algorithms import available, create, inspect_settings, print_settings
# List all algorithm types and their implementations
for algo_type, implementations in available().items():
print(f"{algo_type}: {implementations}")
# Display a formatted settings table for a specific implementation
print_settings("scf_solver", "qdk")
# Inspect settings programmatically
for name, type_name, default, description, limits in inspect_settings(
"scf_solver", "qdk"
):
print(f"{name} ({type_name}): {default}")
# Create an instance and iterate over its settings
scf = create("scf_solver")
for key, value in scf.settings().items():
print(f" {key}: {value}")
Working with settings
All algorithm classes expose their configuration through the settings() method, which returns a reference to the algorithm’s internal Settings object.
This object supports both read and write operations.
Settings are validated at execution time, allowing modifications at any point before calling run().
Important
Settings are locked after execution.
When run() is invoked on an algorithm, its settings are automatically locked to ensure reproducibility.
Any subsequent attempt to modify the settings raises a SettingsAreLocked exception.
To run the same algorithm with different parameters, create a new algorithm instance.
scf = create("scf_solver")
scf.settings().set("method", "hf")
energy, wfn = scf.run(structure, charge=0, spin_multiplicity=1, basis_or_guess="sto-3g") # type: ignore[name-defined] # noqa: F821
# Settings are now locked - this raises SettingsAreLocked:
# scf.settings().set("method", "b3lyp")
# Create a new instance for different settings
scf2 = create("scf_solver")
scf2.settings().set("method", "b3lyp")
energy2, wfn2 = scf2.run(
structure, charge=0, spin_multiplicity=1, basis_or_guess="cc-pvdz"
) # type: ignore[name-defined] # noqa: F821
Accessing and modifying settings
// Create an algorithm
auto scf_solver = algorithms::ScfSolverFactory::create();
// Get the settings object for that algorithm
auto& settings = scf_solver->settings();
// Get a parameter
auto max_iter = settings.get<int64_t>("max_iterations");
std::cout << "Max iterations: " << max_iter << std::endl;
// Get the settings object
auto& settings = scf_solver->settings();
// Set an integer value
settings.set("max_iterations", 100);
// Set a string value
settings.set("basis_set", "def2-tzvp");
// Set a numeric value
settings.set("convergence_threshold", 1.0e-8);
from qdk_chemistry.algorithms import create # noqa: E402
from qdk_chemistry.data import Settings # noqa: E402
# Create an algorithm
scf_solver = create("scf_solver")
# Get the settings object for that algorithm
settings = scf_solver.settings()
# Get a parameter
max_iter = settings.get("max_iterations")
print(f"Max iterations: {max_iter}")
# Get the settings object
settings = scf_solver.settings()
# Set an integer value
settings.set("max_iterations", 100)
# Set a string value
settings.set("method", "B3LYP")
# Set a numeric value
settings.set("convergence_threshold", 1.0e-8)
Passing settings at creation time (Python only)
The Python registry’s create() function accepts keyword arguments that are automatically applied to the algorithm’s settings.
This provides a convenient shorthand for configuring algorithms in a single line:
from qdk_chemistry.algorithms import create # noqa E402
# Pass settings directly to create() as keyword arguments
scf_solver = create(
"scf_solver",
max_iterations=100,
convergence_threshold=1.0e-8,
)
# This is equivalent to:
# scf_solver = create("scf_solver")
# scf_solver.settings().set("max_iterations", 100)
# scf_solver.settings().set("basis_set", "def2-tzvp")
# scf_solver.settings().set("convergence_threshold", 1.0e-8)
This is equivalent to creating the algorithm and then calling settings().set() for each parameter, but is more concise for common use cases.
The C++ API does not provide this shorthand; settings must be configured explicitly after algorithm creation.
Checking and retrieving values
The Settings class provides methods for safely checking and retrieving values.
Use has() to verify existence, get() for direct access (raises an exception if the key is missing), or get_or_default() to specify a fallback value.
// Check if a setting exists
if (settings.has("basis_set")) {
std::cout << "Basis set is configured: "
<< settings.get<std::string>("basis_set") << std::endl;
}
// Get with default fallback
auto custom_param = settings.get_or_default<int>("my_custom_param", 42);
std::cout << "Custom parameter (with default): " << custom_param << std::endl;
// Get all setting keys
auto keys = settings.keys();
// Get the number of settings
size_t count = settings.size();
// Check if settings are empty
bool is_empty = settings.empty();
// Validate that required settings exist
settings.validate_required({"basis_set", "convergence_threshold"});
// Get a setting as a string representation
std::string value_str = settings.get_as_string("convergence_threshold");
// Update an existing setting (throws if key doesn't exist)
settings.update("convergence_threshold", 1.0e-9);
// Get the type name of a setting
std::string type_name = settings.get_type_name("convergence_threshold");
# Check if a setting exists
if settings.has("method"):
# Use the setting
print(f"Method is selected: {settings.get('method')}")
# Get with default fallback
custom_param = settings.get_or_default("my_custom_param", 42)
print(f"Custom parameter (with default): {custom_param}")
# Get all setting keys
keys = settings.keys()
# Get the number of settings
count = settings.size()
# Check if settings are empty
is_empty = settings.empty()
# Validate that required settings exist
settings.validate_required(["convergence_threshold"])
# Get a setting as a string representation
value_str = settings.get_as_string("convergence_threshold")
# Update an existing setting (throws if key doesn't exist)
settings.update("convergence_threshold", 1.0e-9)
# Get the type name of a setting
type_name = settings.get_type_name("convergence_threshold")
Serialization
Settings can be serialized to JSON (human-readable) or HDF5 (efficient, type-preserving) formats to support reproducibility and configuration sharing. For additional information on data persistence, see Serialization.
// Save settings to JSON file
settings.to_json_file("configuration.settings.json");
// Load settings from JSON file
auto settings_from_json_file =
data::Settings::from_json_file("configuration.settings.json");
// Generic file I/O with specified format
settings.to_file("configuration.settings.json", "json");
auto settings_from_file =
data::Settings::from_file("configuration.settings.json", "json");
// Convert to JSON object
auto json_data = settings.to_json();
// Load from JSON object
auto settings_from_json = data::Settings::from_json(json_data);
import os # noqa E402
import tempfile # noqa E402
tmpdir = tempfile.mkdtemp()
os.chdir(tmpdir)
# Save settings to JSON file
settings.to_json_file("configuration.settings.json")
# Load settings from JSON file
settings_from_json_file = Settings.from_json_file("configuration.settings.json")
# Generic file I/O with specified format
settings.to_file("configuration.settings.json", "json")
settings_from_file = Settings.from_file("configuration.settings.json", "json")
# Convert to JSON string
json_data = settings.to_json()
# Load from JSON string
settings_from_json = Settings.from_json(json_data)
Example formats
{
"basis_set": "sto-3g",
"convergence_threshold": 1e-08,
"max_iterations": 200,
"method": "hf"
}
/settings
├── basis_set # String
├── convergence_threshold # Double
├── max_iterations # Integer
└── method # String
Extending settings
Algorithm developers can create specialized settings classes by extending Settings.
Default values are established during construction using the set_default method, ensuring that configurations are discoverable and well-documented.
This pattern integrates with the Factory Pattern and plugin system.
class MySettings : public data::Settings {
public:
MySettings() {
// Set default values during construction
set_default("max_iterations", 100);
set_default("convergence_threshold", 1e-6);
set_default("method", std::string("default"));
}
};
class MySettings(Settings):
def __init__(self):
super().__init__()
# Set default values during construction
self._set_default("max_iterations", "int", 100)
self._set_default("convergence_threshold", "double", 1e-6)
self._set_default("method", "string", "default")
Supported types
The settings system uses a variant-based type system that provides flexibility while maintaining type safety. The following types are supported:
C++ type |
Python type |
Description |
|---|---|---|
|
|
Boolean flags |
|
|
64-bit signed integers |
|
|
Double-precision floating-point |
|
|
Text values |
|
|
Integer arrays |
|
|
Floating-point arrays |
|
|
String arrays |
C++ implementation
The C++ layer uses a std::variant-based storage that provides compile-time type checking through templates.
Python implementation
The Python bindings automatically convert between native Python types and the underlying C++ types.
When setting values, Python objects are validated against the expected type and converted appropriately—for example, Python float values become C++ double, and Python list objects become std::vector containers.
When retrieving values, the reverse conversion occurs transparently.
The get_expected_python_type() method can be used to query the expected Python type for any setting:
expected_type = settings.get_expected_python_type("convergence_threshold")
print(expected_type) # "float"
expected_type = settings.get_expected_python_type("method")
print(expected_type) # "str"
This is particularly useful when building dynamic configuration interfaces or validating user input before applying it to settings.
Note
Integer type handling.
All integer types are stored internally as int64_t (signed 64-bit).
In C++, integer types such as int, long, size_t, and unsigned are automatically converted to int64_t when setting values.
Retrieval supports conversion back to other integer types via the templated get<T>() method, with range checking to prevent overflow.
In Python, native int values are converted to int64_t automatically, and boolean values are explicitly rejected to prevent accidental type confusion (since bool is a subclass of int in Python).
Constraints
Settings can define constraints that specify valid ranges or allowed values.
Constraints are established when algorithm developers define settings using set_default(), and they serve two purposes: documentation and validation guidance.
Constraint types
Bound constraints — Define minimum and maximum values for numeric settings (e.g.,
max_iterationsmust be between 1 and 1000).List constraints — Define an explicit set of allowed values for string or integer settings (e.g.,
methodmust be one of["hf", "dft"]).
Inspecting constraints
Constraints can be queried using has_limits() and get_limits():
if (settings.has_limits("max_iterations")) {
auto limits = settings.get_limits("max_iterations");
// limits is a Constraint variant (BoundConstraint or ListConstraint)
}
if settings.has_limits("max_iterations"):
limits = settings.get_limits("max_iterations")
# Returns (min, max) tuple for bounds, or list for allowed values
print(f"Allowed range: {limits}") # e.g., (1, 1000)
if settings.has_limits("method"):
allowed = settings.get_limits("method")
print(f"Allowed values: {allowed}") # e.g., ['hf', 'dft']
The print_settings() and inspect_settings() functions include constraint information in their output, making it easy to understand the valid configuration options for any algorithm.
Error handling
The Settings class raises descriptive exceptions to facilitate early detection of configuration issues:
SettingNotFound: Raised when the requested key does not exist.SettingTypeMismatch: Raised when the key exists but the requested type does not match the stored type.SettingsAreLocked: Raised when attempting to modify settings afterrun()has been called.
// Error handling example
try {
auto value = settings.get<double>("non_existent_setting");
} catch (const data::SettingNotFound& e) {
std::cerr << e.what()
<< std::endl; // "Setting not found: non_existent_setting"
// Use a fallback and continue execution
auto value = settings.get_or_default<double>("non_existent_setting", 0.0);
}
import qdk_chemistry # noqa E402
# Error handling example
try:
value = settings.get("non_existent_setting")
except qdk_chemistry.data.SettingNotFound as e:
print(e) # "Setting not found: non_existent_setting"
# Use a fallback and continue execution
value = settings.get_or_default("non_existent_setting", 0.0)
See also
Factory Pattern — Algorithm creation and customization
Serialization — Data persistence in QDK/Chemistry
Plugin system — Extending QDK/Chemistry with custom implementations