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:

  1. Molecular structure — Defines the atoms, coordinates, charge, and spin.

  2. Problem builder — Defines what kind of problem to solve (e.g., ground state, embedding, excited state).

  3. Problem — Combines the molecular structure with the problem builder’s configuration.

  4. 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.

Creating a calculator using the builder pattern

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.

The Pattern in Four Verbs

  1. Narrow — Pick a broad category, then narrow down to the specific type of object you want.

  2. Configure — Add settings or modifications with .with_… methods.

  3. Choose — Enter a sub-choice with .choose_…, then pick exactly one terminal option.

  4. 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:

from qrunch.chemistry import problem_builder_creator

problem_builder = (
    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

from qrunch.chemistry import problem_builder_creator

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:

from qrunch.chemistry import problem_builder_creator

problem_builder = (
    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:

from qrunch.chemistry import problem_builder_creator

problem_builder = (
    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.

See Also