Understanding and Using Kvantify Qrunch’s Fluent Builder Pattern
Overview
When you use Kvantify Qrunch, your ultimate goal is simple: you want to define a molecular system, build a quantum-chemical problem, select a calculator, and obtain results — such as the total energy.
In practice, this involves four main building blocks:
Molecular structure — Defines the atoms, coordinates, charge, and spin.
Problem builder — Defines what kind of problem to solve (e.g., ground state, embedding, excited state).
Problem — Combines the molecular structure with the problem builder’s configuration.
Calculator — Specifies how the problem should be solved (e.g., CI, VQE, FAST-VQE, BEAST-VQE).
The typical workflow in Qrunch therefore looks like this:
molecular_structure → problem_builder → problem → calculator → result
In Kvantify Qrunch, many of the most important objects are very complex and require multiple decisions. In the case of a calculator, we may ask:
What type of calculator do you want? A Variational quantum eigensolver (VQE) or a conventional configuration interaction (CI).
If we choose VQE, then which algorithm do you want to use? an adaptive VQE, like FAST-VQE or BEAST-VQE, or a basic parameter optimization.
If we choose adaptive, then do we want to include both single and double excitations?
The fluent builder pattern is how Kvantify Qrunch guides you through those decisions, one step at a time, in a way that is:
Safe — you can only call valid next steps.
Discoverable — your IDE will show you the next available methods.
Flexible — you can configure optional settings without breaking the flow.
You’ll encounter this pattern in:
problem_builder_creator()— builds quantum-chemical problem builders.calculator_creator()— builds calculators such as CI or FAST-VQE.estimator_creator()— builds estimators for expectation values.sampler_creator()— builds samplers for quantum measurement.
and many others.
Setting Up Your IDE
To get the maximum benefit from the fluent builder pattern, we strongly recommend using an Integrated Development Environment (IDE) with a powerful static type checker. The discoverability of the builder pattern comes alive when your IDE can provide real-time auto-completion and type hints, guiding you to the next valid methods in the chain.
This functionality relies on a static type checker that understands Python’s typing system. Kvantify Qrunch is fully type-annotated and internally tested with pyright to ensure a smooth experience. Here’s how to set up some of the most popular IDEs:
You need to have a Python virtual environment with qrunch installed. All IDEs at some point needs to be pointed to the correct Python interpreter inside that virtual environment; which is typically found in a .venv folder inside your project, or if you use conda, the path can be found by running conda env list.
VS Code, combined with the Pylance extension, provides an excellent experience out of the box. Pylance is the default language server for the official Python extension from Microsoft.
To ensure the best type-checking, we recommend setting Pylance’s type checking mode to strict:
First, ensure VS Code is using the virtual environment where qrunch is installed. Press
Ctrl+Shift+P, search for “Python: Select Interpreter”, and choose the correct environment. VS Code typically auto-detects virtual environments in your project folder (e.g., .venv).Open your settings file (settings.json) by pressing
Ctrl+Shift+Pand searching for “Preferences: Open User Settings (JSON)”.Add the following line to your settings:
"python.analysis.typeCheckingMode": "strict"
This will enable Pylance to catch type errors and provide the most accurate auto-completion for the builder pattern.
PyCharm and IntelliJ IDEA (with the Python plugin) have a powerful, built-in static analysis engine.
To get type hints, you must configure the project to use the Python interpreter from the virtual environment where qrunch is installed.
Go to File > Settings (on Windows/Linux) or PyCharm > Preferences (on macOS).
Navigate to Project: <your-project-name> > Python Interpreter.
Click the gear icon, select Add…, and point the IDE to the python executable inside your virtual environment.
Once the interpreter is configured, the IDE will automatically analyze the code and provide type hints and auto-completion.
Modern versions of Spyder use the Language Server Protocol (LSP) for code analysis. To get the best results, you should ensure LSP is enabled and configured to use a capable type checker like pyright.
Go to Tools > Preferences > Python Interpreter and select “Use the following Python interpreter”. Point it to the python executable inside the virtual environment where qrunch is installed.
Install the necessary packages in your Python environment:
pip install python-lsp-server[pyright]
In Spyder, go to Tools > Preferences > Completion and Linting > LSP.
Ensure that “Enable LSP” is checked. Spyder should automatically detect and use python-lsp-server with pyright.
For Neovim users, the setup involves using nvim-lspconfig to connect to a language server like pyright. The server needs to know which Python environment to use.
The easiest way is to launch Neovim from a terminal where your virtual environment is already activated. pyright will then pick up the correct interpreter automatically.
Install pyright (e.g., via npm or pip):
# Using npm (recommended) npm install -g pyright # Or using pip pip install pyright
Assuming you use a plugin manager like lazy.nvim, ensure nvim-lspconfig is installed.
Add the following Lua code to your Neovim configuration to set up pyright:
-- In your lsp.lua or init.lua local lspconfig = require('lspconfig') -- Setup for pyright lspconfig.pyright.setup{}With this configuration, Neovim will provide full type-aware diagnostics as you use the Qrunch builder patterns.
To also enable auto-completion, consider installing a completion plugin like nvim-cmp and configuring it to work with LSP.
The Pattern in Four Verbs
Narrow — Pick a broad category, then narrow down to the specific type of object you want.
Configure — Add settings or modifications with
.with_…methods.Choose — Enter a sub-choice with
.choose_…, then pick exactly one terminal option.Add - Add modifiers with
.add_...methods, then pick exactly one terminal option.
Finally, you call .create() to produce the final object.
How This Looks in Kvantify Qrunch
Here’s the conceptual flow for calculator_creator():
calculator_creator() # Start
└── .vqe() # Narrow: pick Variational quantum eigensolver (VQE)
└── .iterative() # Narrow: pick an adaptive algorithm
├── .with_... # Configure: add optional settings
├── .choose_... # Choose: start sub-choice → must end in a terminal pick
├── .add... # Add: start sub-choice → must end in a terminal pick
└── .create() # Create the final object
We look into each of these concepts in separate sections below
Narrow: Narrow Down the Object Type
To explain the Narrow in more detail we can examine the problem_builder_creator:
import qrunch as qc
problem_builder = (
qc.problem_builder_creator() # Stage 1: general problem builder creator
.ground_state() # Stage 2: Narrow to a ground state problem builder creator
.standard() # Stage 3: Narrow to a full ground state problem builder creator
.create() # Stage 4: finalizes and return a full ground state problem builder
)
An alternative pathway could be
import qrunch as qc
problem_builder = (
qc.problem_builder_creator() # Stage 1: general problem builder creator
.ground_state() # Stage 2: Narrow to a ground state problem builder creator
.projective_embedding() # Stage 3: Narrow to a Wavefunction-in-DFT projection based embeddding ground state problem builder creator
.create() # Stage 4: finalizes and return a projective embedding ground state problem builder
)
Each of these narrowing methods changes the type of the object. This is the step builder part of the pattern — you can’t skip straight to .standard() without first picking .ground_state().
Configure: Using .with_… Methods
To explain the Configure in more detail we can examine the problem_builder_creator:
All .with_… methods modify the configuration of the builder and return the same builder so you can keep chaining.
import qrunch as qc
problem_builder = (
qc.problem_builder_creator() # Stage 1: general problem builder creator
.ground_state() # Stage 2: Narrow to a ground state problem builder creator
.projective_embedding() # Stage 3: Narrow to a Wavefunction-in-DFT projection based embeddding ground state problem builder creator
.with_cube_generator(cube_generator) # Configure: set a cube generator on the projective embedding ground state problem builder
.create() # Stage 4: finalizes and return a projective embedding ground state problem builder
)
For another example, we can examine the estimator_creator:
import qrunch as qc
estimator = (
qc.estimator_creator() # Stage 1: general estimator creator
.excitation_gate() # Stage 2: Narrow to an excitation gate estimator creator
.with_parallel_setting("parallel") # Configure: set parallel execution on the excitation gate estimator
.with_spin_particle_conservation() # Configure: specify fermionic encoding in the excitation gate estimator
.with_seed(seed=42) # Configure: set random seed on the excitation gate estimator
.create() # Finalize and create the excitation gate estimator with the specified configurations
)
Each .with_… method adjusts the estimator’s configuration without changing its type. So you can keep calling more .with_… methods if you want to.
Choose: Using .choose_… Methods
A .choose_… method starts a sub-builder that requires you to pick exactly one terminal option. Once you make the pick, you are returned to the parent builder.
Example: selecting the two-electron repulsion integral strategy:
import qrunch as qc
problem_builder = (
qc.problem_builder_creator() # Stage 1: general problem builder creator
.ground_state() # Stage 2: Narrow to a ground state problem builder creator
.standard() # Stage 3: Narrow to a standard ground state problem builder creator
.choose_repulsion_integral_builder() # Start sub-choice: choose repulsion integral strategy
.resolution_of_the_identity() # Perform the sub-choice selection - Here we pick resolution of the identity (RI)
.create() # Stage 4: finalizes and return a full ground state problem builder, that uses RI integrals.
)
What happens here: - .choose_repulsion_integral_builder() starts a builder within a builder. - Calling .resolution_of_the_identity() sets the builder’s integral strategy and returns you to the original builder.
Another example is
import qrunch as qc
hf_options = qc.HartreeFockCalculatorOptions(verbose=10)
problem_builder = (
qc.problem_builder_creator() # Stage 1: general problem builder creator
.ground_state() # Stage 2: Narrow to a ground state problem builder creator
.projective_embedding() # Stage 3: Narrow to a Wavefunction-in-DFT projection based embeddding ground state problem builder creator
.choose_full_system_solver() # Start sub-choice: choose full system solver
.hartree_fock(options=hf_options) # Perform the sub-choice selection - Here we pick Hartree-Fock (HF) with desired options
.choose_embedded_orbital_calculator() # Start sub-choice: choose embedded orbital calculator
.moller_plesset_2() # Perform the sub-choice selection - Here we pick Moller-Plesset perturbation theory to second order (MP2)
.choose_localizer() # Start sub-choice: choose orbital localizer
.pipek_mezey() # Perform the sub-choice selection - Here we pick Pipek-Mezey localization
.choose_orbital_assigner() # Start sub-choice: choose orbital assigner
.total_weight(assignment_tolerance=0.2) # Perform the sub-choice selection - Here we pick total weight orbital assignment
.choose_projector_builder() # Start sub-choice: choose projector builder
.manby(level_shift_parameter=0.5) # Perform the sub-choice selection - Here we pick Manby projector builder
.create() # Stage 4: finalizes and return a full ground state problem builder, that uses RI integrals.
)
Add: Using .add_… Methods
A .add_… method starts a sub-builder that requires you to pick exactly one terminal option. Once you make the pick, you are returned to the parent builder.
Example: adding an active space modifier:
import qrunch as qc
problem_builder = (
qc.problem_builder_creator() # Stage 1: general problem builder creator
.ground_state() # Stage 2: Narrow to a ground state problem builder creator
.standard() # Stage 3: Narrow to a standard ground state problem builder creator
.add_problem_modifier() # Start sub-choice: add an active space modifier
.active_space(..) # Perform the sub-choice selection - Here we add an active space modifier.
.create() # Stage 4: finalizes and return a full ground state problem builder, that uses RI integrals.
)
What happens here: - .add_problem_modifier() starts a builder within a builder. - Calling .active_space() adds the builder’s active space and returns you to the original builder.
See Define an Active Space (Complete Active Space) for more details on the active space selection.
The difference between .choose_… and .add_… is that you can call .add_… multiple times to add multiple modifiers.
Note
That the order matters. When you call .add_… multiple times, they are applied in the order you added them. In the example below, the active space modifier is applied first, followed by the to_dense_integrals modifier.
Thus you could do something like this:
import qrunch as qc
problem_builder = (
qc.problem_builder_creator() # Stage 1: general problem builder creator
.ground_state() # Stage 2: Narrow to a ground state problem builder creator
.standard() # Stage 3: Narrow to a standard ground state problem builder creator
.choose_repulsion_integral_builder() # Start sub-choice: choose repulsion integral strategy
.resolution_of_the_identity() # Perform the sub-choice selection - Here we pick resolution of the identity (RI)
.add_problem_modifier() # Start sub-choice
.active_space(..) # Perform the sub-choice selection - Here we add an active space modifier.
.add_problem_modifier() # Start sub-choice
.to_dense_integrals(..) # Perform the sub-choice selection - Here we add to_dense_integrals modifer.
.create() # Stage 4: finalizes and return a full ground state problem builder, that uses RI integrals.
)
See Choose Electron-Repulsion Integral Builder for more details on integral strategies.
Note
That the .choose_… can also be called multiple times but each time you call it you are replacing the previous choice. So it is the final choice that matters.