Getting Started
This guide will walk you through installing ProPauli and running your first simulation.
Installation
To use ProPauli, you will need a C++23 compatible compiler (Clang is preferred) and CMake 3.14 or later.
You can build the library from source with the following commands:
git clone https://github.com/zefresk/ProPauli.git
cd ProPauli
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release
cmake --build build --config Release
This will build the ProPauli library, which you can then link against in your own projects.
Quickstart: Your First Simulation
Let’s simulate a simple 8-qubit quantum circuit. This circuit first applies a Hadamard gate to every qubit to create a superposition, then entangles them with a chain of CNOT gates. Finally, we will calculate the expectation value of the \(Z^{\otimes 8}\) observable.
// 1. Initialize an 8-qubit circuit
Circuit qc{ 8 };
// 2. Build the quantum circuit
// Apply a Hadamard gate to every qubit
for (unsigned i = 0; i < 8; ++i) {
qc.add_operation("H", i);
}
// Create a chain of CNOT gates
for (unsigned i = 0; i < 7; ++i) {
qc.add_operation("CX", i, i + 1);
}
// 3. Define the observable to measure
// We'll measure the observable ZZZZZZZZ
std::string z_obs_str(8, 'Z');
Observable obs{ z_obs_str };
// 4. Run the simulation
auto final_obs = qc.run(obs);
// 5. Get the expectation value
auto exp_val = final_obs.expectation_value();
std::cout << "Expectation value of " << z_obs_str << ": " << exp_val << std::endl;
This example demonstrates the complete workflow: initializing a Circuit, adding operations, defining an Observable, running the simulation, and retrieving the final expectation value.
Examples from the README
Here are the examples from the README, providing a quick overview of the library’s main features.
Basic Circuit
A simple 2-qubit circuit calculating the expectation value of the “ZZ” observable.
Circuit qc{ 2 };
qc.add_operation("H", 0);
qc.add_operation("Rz", 0, 1.57f);
qc.add_operation("CX", 0u, 1u);
auto result = qc.run(Observable{ "ZZ" });
std::cout << "Expectation value: " << result.expectation_value() << std::endl;
Large Circuit with Truncation
This demonstrates a 64-qubit circuit that uses truncators to manage the complexity of the simulation. Terms with very small coefficients or a high number of non-identity Pauli operators (high Pauli weight) are removed.
Circuit qc{ 64,
combine_truncators(CoefficientTruncator<>{ 0.001f }, // remove terms with coefficient below 0.001
WeightTruncator<>{ 6 } // remove terms with pauli weight > 6
) };
// Apply a layer of Hadamard gates
for (unsigned i = 0; i < 64; ++i)
qc.add_operation("H", i);
// Entangling layer
for (unsigned i = 0; i < 63; ++i) {
qc.add_operation("CX", i, i + 1);
}
auto result = qc.run(Observable{ std::string(64, 'Z') });
std::cout << "Expectation value: " << result.expectation_value() << std::endl;
Custom Truncator
Users can define their own truncation logic. The easiest way is by providing a lambda to the PredicateTruncator.
// A custom truncator that removes Pauli terms with a specific weight
auto predicate = [](const auto& pt) { return pt.pauli_weight() == 2; };
Circuit qc{ 4, std::make_shared<PredicateTruncator<decltype(predicate)>>(predicate) };
qc.add_operation("H", 0);
qc.add_operation("H", 1);
qc.add_operation("Rz", 0, 1.57f);
qc.add_operation("CX", 0u, 1u);
auto result = qc.run(Observable{ "XXXX" });
std::cout << "Expectation value: " << result.expectation_value() << std::endl;
Noise Model
This example shows how to define a simple noise model where 1% amplitude damping noise is applied after every CNOT gate.
NoiseModel<coeff_t> nm;
nm.add_amplitude_damping_on_gate(QGate::Cx, 0.01);
Circuit qc{ 4, std::make_shared<NeverTruncator<>>(), nm };
qc.add_operation("H", 0);
qc.add_operation("Rz", 0, 1.57f);
qc.add_operation("CX", 0, 1);
qc.add_operation("CX", 2, 3);
auto result = qc.run(Observable{ "ZZZZ" });
std::cout << "Expectation value: " << result.expectation_value() << std::endl;