├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── DevGuide.md ├── LICENSE ├── README.md ├── docs ├── LogosQ.png └── api.md ├── examples ├── basic_gates.rs ├── grover_algorithm.rs ├── quantum_fourier_transform.rs ├── quantum_teleportation.rs ├── state_vis.rs └── visualization.rs ├── scripts ├── publish.sh ├── start.sh └── test.sh ├── src ├── algorithms │ ├── mod.rs │ ├── qft.rs │ ├── rqc.rs │ └── xyz_heisenberg.rs ├── circuits │ ├── mod.rs │ ├── single_qubit.rs │ ├── three_qubits.rs │ ├── two_qubits.rs │ └── utils.rs ├── gates │ └── mod.rs ├── lib.rs ├── noise │ └── mod.rs ├── prelude.rs ├── states │ └── mod.rs ├── utils │ └── mod.rs └── vis │ ├── circuit.rs │ ├── gate.rs │ ├── mod.rs │ └── state.rs └── tests ├── algorithms ├── test_rqc.rs └── test_xyz_heisenberg.rs ├── test_circuits.rs ├── test_circuits_cnot.rs ├── test_circuits_swap.rs ├── test_circuits_toffoli.rs ├── test_gates.rs ├── test_normalize.rs ├── test_parallel.rs ├── test_qft.rs └── test_states.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | # Install system dependencies for fontconfig 20 | - name: Install dependencies 21 | run: | 22 | sudo apt-get update 23 | sudo apt-get install -y libfontconfig1-dev pkg-config 24 | 25 | - name: Build 26 | run: cargo build --verbose 27 | 28 | - name: Run tests 29 | run: cargo test --verbose 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.svg 4 | *.txt 5 | documentation 6 | benchmarks -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "logosq" 3 | version = "0.1.10" 4 | edition = "2021" 5 | authors = ["Shiwen An "] 6 | license = "MIT" 7 | keywords = ["QuantumComputing", "QuantumInformation", "VQC", "QML"] 8 | readme = "README.md" 9 | homepage = "https://logosqbook.vercel.app" 10 | description = "This project is a quantum computing library developed in Rust, inspired by existing libraries such as QPP in C++, Yao.jl in Julia, and Pennylane in Python. " 11 | 12 | [dependencies] 13 | ndarray = { version = "0.15", features = [] } 14 | rand = "0.8" 15 | thiserror = "1.0" 16 | num-complex = "0.4" 17 | approx = "0.5.1" 18 | plotters = "0.3.7" 19 | rustfft = "6.4.1" # or latest compatible version 20 | # Make rayon optional so it can be used in feature flags 21 | rayon = { version = "1.8.0", optional = true } 22 | 23 | [lib] 24 | crate-type = ["cdylib", "rlib"] 25 | 26 | [profile.dev] 27 | opt-level = 1 28 | 29 | [profile.release] 30 | opt-level = 3 31 | 32 | [features] 33 | default = ["parallel"] 34 | # Enable rayon dependency and ndarray's rayon integration 35 | parallel = ["dep:rayon", "ndarray/rayon"] 36 | -------------------------------------------------------------------------------- /DevGuide.md: -------------------------------------------------------------------------------- 1 | Collecting workspace information# LogosQ Development Guide 2 | 3 | This guide is designed to help developers contribute to LogosQ, a quantum computing library written in Rust. Whether you're experienced with quantum computing or just getting started, your contributions are welcome! 4 | 5 | ## Project Overview 6 | 7 | [LogosQ](https://github.com/zazabap/logosq.git) is a comprehensive quantum computing library developed in Rust, inspired by established libraries like [QPP](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0208073) (C++), [Yao.jl](https://yaoquantum.org/) (Julia), and [Pennylane](https://pennylane.ai/) (Python). 8 | 9 | The name λόγος (lógos) comes from Greek meaning "logic." 10 | 11 | ## Development Roadmap 12 | #### Completed 13 | - ✅ Core data structures for quantum gates, states, and circuits 14 | - ✅ Implementation of basic quantum gates and their operations 15 | - ✅ Quantum state representation and manipulation 16 | - ✅ Circuit model for combining gates and executing on states 17 | - ✅ Circuit visualization tools (text and SVG) 18 | - ✅ State visualization tools 19 | 20 | #### In Progress 21 | - 🚧 Intermediate Representation (IR) for circuit optimization 22 | - 🚧 More quantum algorithms implementations 23 | - Quantum Fourier Transform 24 | - Variational Quantum Circuit 25 | - Quantum Gradient Descent 26 | - 🚧 Comprehensive testing suite 27 | - 🚧 Page for documentation with example 28 | - 🚧 Noise models and error simulation 29 | 30 | #### Planned 31 | - 📅 Hardware backend interfaces 32 | - 📅 Docker setup for the one-click run test 33 | - 📅 Advanced optimization techniques 34 | - 📅 Quantum machine learning modules 35 | - 📅 Distributed simulation capabilities 36 | - 📅 Integration with other quantum frameworks 37 | 38 | ## Getting Started 39 | 40 | ### Prerequisites 41 | - [Rust](https://rustup.rs/) (latest stable version) 42 | - [Git](https://git-scm.com/) 43 | - For visualization features: `libfontconfig1-dev` and `pkg-config` (on Debian/Ubuntu) 44 | 45 | ### Setting Up Development Environment 46 | 47 | 1. Clone the repository: 48 | ```bash 49 | git clone https://github.com/zazabap/logosq.git 50 | cd logosq 51 | ``` 52 | 53 | 2. Install dependencies (for Ubuntu/Debian): 54 | ```bash 55 | sudo apt-get update 56 | sudo apt-get install -y libfontconfig1-dev pkg-config 57 | ``` 58 | 59 | 3. Build the project: 60 | ```bash 61 | cargo build 62 | ``` 63 | 64 | 4. Run the tests: 65 | ```bash 66 | cargo test 67 | ``` 68 | 69 | ## Project Structure 70 | 71 | - lib.rs: Main library entry point and public API 72 | - `src/gates/`: Quantum gates and their operations 73 | - `src/circuits/`: Quantum circuit definitions and manipulation 74 | - `src/states/`: Quantum state representation and operations 75 | - `src/algorithms/`: Quantum algorithm implementations 76 | - `src/noise/`: Simulation of noise in quantum systems 77 | - `src/utils/`: Utility functions and types 78 | - `src/vis/`: Visualization tools for circuits, gates, and states 79 | - `examples/`: Example code demonstrating library usage 80 | - `tests/`: Integration tests 81 | - `benches/`: Performance benchmarks 82 | - `documentation/`: Project documentation (Next.js) 83 | 84 | ## How to Contribute 85 | 86 | ### Areas Needing Help 87 | 88 | 1. **Algorithm Implementation**: Help implement quantum algorithms in the `src/algorithms/` module 89 | 2. **Testing**: Expand test coverage across all modules 90 | 3. **Documentation**: Improve code documentation and examples 91 | 4. **Visualization**: Enhance visualization capabilities for complex quantum states and circuits 92 | 5. **Performance Optimization**: Help make simulations faster and more efficient 93 | 94 | ### Contribution Workflow 95 | 96 | 1. **Find an issue**: Look for issues labeled "good first issue" or "help wanted" in the GitHub repository 97 | 2. **Fork the repository**: Create your own fork of the project 98 | 3. **Create a branch**: Make a new branch for your feature or bugfix 99 | 4. **Make changes**: Implement your feature or fix the bug 100 | 5. **Write tests**: Add tests for your new functionality 101 | 6. **Update documentation**: Ensure your code is well-documented 102 | 7. **Submit a PR**: Create a pull request with a clear description of your changes 103 | 104 | ### Coding Standards 105 | 106 | - Follow Rust's official [style guidelines](https://doc.rust-lang.org/1.0.0/style/README.html) 107 | - Use meaningful variable and function names 108 | - Write clear documentation comments (use for doc comments) 109 | - Include unit tests for new functionality 110 | - Format your code with `rustfmt` 111 | - Check your code with `clippy` 112 | 113 | ## Community 114 | 115 | - **GitHub Issues**: For bug reports and feature requests 116 | - **Pull Requests**: For submitting contributions 117 | - **Discussions**: For broader conversations about the project's direction 118 | 119 | ## Learning Resources 120 | 121 | If you're new to quantum computing, these resources might help: 122 | - [Waterloo's Quantum Information Processing course](https://www.math.uwaterloo.ca/~wcleung/intro-qinfo.html) 123 | - Nielsen and Chuang's "Quantum Computation and Quantum Information" 124 | - John Preskill's lecture notes 125 | 126 | ## License 127 | 128 | This project is licensed under the MIT License. See the LICENSE file for details. 129 | 130 | --- 131 | 132 | We appreciate your interest in contributing to LogosQ! Your contributions will help build a robust, efficient, and accessible quantum computing library for the Rust ecosystem. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 zazabap 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 | LogosQ Logo 6 | LogosQ: Quantum Computing Library in Rust 7 |

8 |
9 | 10 | ## Overview 11 | This project is a quantum computing library developed in Rust, inspired by existing libraries such as QPP in C++, Yao.jl in Julia, and Pennylane in Python. The library aims to provide a comprehensive set of tools for quantum computing, including quantum gates, circuits, states, algorithms, and noise simulation. 12 | 13 | λόγος (lógos) comes from Greek means the logic. 14 | 15 | ## Installation 16 | To build and run the project, ensure you have Rust installed on your machine. You can install Rust using [rustup](https://rustup.rs/). 17 | 18 | 19 | Add LogosQ to your project by adding the following to your `Cargo.toml`: 20 | 21 | ```toml 22 | [dependencies] 23 | logosq = "0.1.10" 24 | ``` 25 | or clone the repository and navigate to the project directory: 26 | 27 | ```bash 28 | git clone https://github.com/zazabap/logosq.git 29 | cd logosq 30 | cargo build 31 | ``` 32 | 33 | ## Usage 34 | To use the library, you can include it in your Rust project by adding it as a dependency in your `Cargo.toml` file. Here’s an example of how to create and manipulate basic quantum gates 35 | 36 | The `examples` directory contains several example files demonstrating how to use the library. 37 | 38 | ## Contributing 39 | Contributions are welcome! Please open an issue or submit a pull request for any enhancements or bug fixes. For further details, please check the DevGuide.md 40 | 41 | ## License 42 | This project is licensed under the MIT License. See the LICENSE file for more details. 43 | 44 | ## FAQ 45 | 46 | 1. Some bugs might appear when using the crate, one of the error related with missing package in libfontconfig1-dev, in such case, running on debian/linux (tested on ubuntu) could fix the build error. 47 | ``` 48 | sudo apt-get update 49 | sudo apt-get install -y libfontconfig1-dev pkg-config 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/LogosQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazabap/LogosQ/bc4ca10795ac8adac35b9f37782b2904621f73bc/docs/LogosQ.png -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # API Documentation for logosq Quantum Computing Library 2 | 3 | ## Overview 4 | 5 | The logosq library provides a comprehensive set of tools for quantum computing, including the ability to define and manipulate quantum gates, circuits, and states, as well as implement various quantum algorithms. This document outlines the public API of the library, detailing the available types, functions, and usage examples. 6 | 7 | ## Modules 8 | 9 | ### Gates 10 | 11 | The `gates` module provides functionality for creating and manipulating quantum gates. 12 | 13 | #### Types 14 | 15 | - **Gate**: Represents a quantum gate. 16 | 17 | #### Functions 18 | 19 | - `fn pauli_x() -> Gate`: Returns the Pauli-X gate. 20 | - `fn hadamard() -> Gate`: Returns the Hadamard gate. 21 | - `fn cnot(control: usize, target: usize) -> Gate`: Returns a CNOT gate with specified control and target qubits. 22 | 23 | ### Circuits 24 | 25 | The `circuits` module allows users to define quantum circuits. 26 | 27 | #### Types 28 | 29 | - **Circuit**: Represents a sequence of quantum gates. 30 | 31 | #### Functions 32 | 33 | - `fn new() -> Circuit`: Creates a new empty circuit. 34 | - `fn add_gate(&mut self, gate: Gate)`: Adds a gate to the circuit. 35 | - `fn measure(&self, qubit: usize) -> MeasurementResult`: Measures the specified qubit. 36 | 37 | ### States 38 | 39 | The `states` module manages quantum states. 40 | 41 | #### Types 42 | 43 | - **State**: Represents a quantum state vector. 44 | 45 | #### Functions 46 | 47 | - `fn initialize(size: usize) -> State`: Initializes a quantum state of the given size. 48 | 49 | ### Algorithms 50 | 51 | The `algorithms` module implements various quantum algorithms. 52 | 53 | #### Functions 54 | 55 | - `fn grover_search(target: &State) -> Circuit`: Implements Grover's search algorithm. 56 | - `fn quantum_teleportation(state: &State) -> Circuit`: Implements the quantum teleportation protocol. 57 | 58 | ### Noise 59 | 60 | The `noise` module simulates noise in quantum systems. 61 | 62 | #### Functions 63 | 64 | - `fn depolarizing_noise(state: &State, probability: f64) -> State`: Applies depolarizing noise to the state. 65 | - `fn amplitude_damping(state: &State) -> State`: Applies amplitude damping noise to the state. 66 | 67 | ### Utils 68 | 69 | The `utils` module contains utility functions and types. 70 | 71 | #### Functions 72 | 73 | - `fn complex_add(a: Complex, b: Complex) -> Complex`: Adds two complex numbers. 74 | - `fn normalize(state: &mut State)`: Normalizes the quantum state vector. 75 | 76 | ## Usage Example 77 | 78 | check the example folder in the library. 79 | 80 | ## Conclusion 81 | 82 | This API documentation provides a high-level overview of the logosq library's capabilities. For more detailed information, please refer to the individual module documentation and usage examples provided in the `examples` directory. -------------------------------------------------------------------------------- /examples/basic_gates.rs: -------------------------------------------------------------------------------- 1 | // This file demonstrates the usage of the quantum computing library to create and manipulate basic quantum gates. 2 | 3 | use logosq::circuits::Circuit; 4 | use logosq::gates::{cnot_gate, h_gate, x_gate, Gate}; 5 | use logosq::states::State; 6 | 7 | fn main() { 8 | // Create single-qubit gates 9 | println!("Creating basic quantum gates:"); 10 | let x = x_gate(); 11 | let h = h_gate(); 12 | println!("Created basic gates: X, H, Z"); 13 | 14 | // Demonstrate gates on quantum states 15 | println!("\nDemonstrating gate operations:"); 16 | 17 | // X gate flips |0⟩ to |1⟩ 18 | let mut state = State::zero_state(1); 19 | println!("Initial state: |0⟩"); 20 | println!("Probability of |0⟩: {:.4}", state.probability(0)); 21 | println!("Probability of |1⟩: {:.4}", state.probability(1)); 22 | 23 | x.apply(&mut state); 24 | println!("\nAfter X gate:"); 25 | println!("Probability of |0⟩: {:.4}", state.probability(0)); 26 | println!("Probability of |1⟩: {:.4}", state.probability(1)); 27 | 28 | // Hadamard creates superposition 29 | let mut state = State::zero_state(1); 30 | h.apply(&mut state); 31 | println!("\nAfter H gate on |0⟩:"); 32 | println!("Probability of |0⟩: {:.4}", state.probability(0)); 33 | println!("Probability of |1⟩: {:.4}", state.probability(1)); 34 | 35 | // Building a Bell state circuit 36 | println!("\nCreating a Bell state:"); 37 | let mut circuit = Circuit::new(2); 38 | 39 | // Add Hadamard to first qubit 40 | circuit.add_single_qubit_gate(h.matrix.clone(), 0, "H"); 41 | 42 | // Add CNOT between qubits 0 and 1 43 | let cnot = cnot_gate(); 44 | circuit.add_matrix_gate(cnot.matrix.clone(), vec![0, 1], "CNOT"); 45 | 46 | // Execute circuit on |00⟩ state 47 | let mut state = State::zero_state(2); 48 | circuit.execute(&mut state); 49 | 50 | // Bell state should have equal probabilities for |00⟩ and |11⟩ 51 | println!("Bell state probabilities:"); 52 | println!("Probability of |00⟩: {:.4}", state.probability(0)); 53 | println!("Probability of |01⟩: {:.4}", state.probability(1)); 54 | println!("Probability of |10⟩: {:.4}", state.probability(2)); 55 | println!("Probability of |11⟩: {:.4}", state.probability(3)); 56 | 57 | // Demonstrate measurement 58 | println!("\nMeasuring Bell state 100 times:"); 59 | let mut zeros_zeros = 0; 60 | let mut ones_ones = 0; 61 | 62 | for _ in 0..100 { 63 | let test_state = state.clone(); 64 | let result = test_state.measure(); 65 | if result == 0 { 66 | zeros_zeros += 1; 67 | } else if result == 3 { 68 | ones_ones += 1; 69 | } 70 | } 71 | 72 | println!("Measured |00⟩: {} times", zeros_zeros); 73 | println!("Measured |11⟩: {} times", ones_ones); 74 | } 75 | -------------------------------------------------------------------------------- /examples/grover_algorithm.rs: -------------------------------------------------------------------------------- 1 | use logosq::prelude::*; 2 | use logosq::vis::circuit_text; 3 | use logosq::vis::save_circuit_svg; 4 | 5 | fn main() { 6 | // Define the number of qubits and create a circuit 7 | let num_qubits = 3; 8 | let target_state = 0b011; // Example target state |011> 9 | 10 | let mut circuit = Circuit::new(num_qubits).with_name("Grover's Algorithm"); 11 | 12 | // Step 1: Initialize with Hadamard gates 13 | for i in 0..num_qubits { 14 | circuit.h(i); 15 | } 16 | 17 | // Calculate optimal number of iterations 18 | let iterations = 19 | ((std::f64::consts::PI / 4.0) * (1 << num_qubits as u32) as f64).sqrt() as usize; 20 | println!("Running {} iterations", iterations); 21 | 22 | // Step 2: Apply Grover iterations 23 | for _ in 0..iterations { 24 | // Oracle - marks the target state by flipping its sign 25 | apply_oracle(&mut circuit, target_state, num_qubits); 26 | 27 | // Diffusion operator - amplifies the amplitude of the marked state 28 | apply_diffusion(&mut circuit, num_qubits); 29 | } 30 | 31 | // Step 3: Measure the qubits 32 | let results = circuit.execute_and_measure(); 33 | 34 | // Display results 35 | println!("Measurement results: {:?}", results); 36 | println!("Target state: {}", target_state); 37 | 38 | // Visualize the circuit (if the visualization feature is enabled) 39 | println!("\nCircuit diagram:"); 40 | println!("{}", circuit_text(&circuit)); 41 | 42 | // Optionally save the circuit diagram 43 | save_circuit_svg(&circuit, "grover_algorithm.svg").unwrap_or_else(|e| { 44 | println!("Failed to save circuit diagram: {}", e); 45 | }); 46 | } 47 | 48 | // Function to apply the oracle that marks the target state 49 | fn apply_oracle(circuit: &mut Circuit, target_state: usize, num_qubits: usize) { 50 | // Apply X gates to qubits where target_state bit is 0 51 | for i in 0..num_qubits { 52 | if (target_state >> i) & 1 == 0 { 53 | circuit.x(i); 54 | } 55 | } 56 | 57 | // Apply multi-controlled Z gate 58 | // For a 3-qubit system, we can use Toffoli (CCZ) plus some H gates 59 | if num_qubits == 3 { 60 | circuit.h(2); 61 | circuit.add_operation(toffoli_gate(), vec![0, 1, 2], "CCZ"); 62 | circuit.h(2); 63 | } else { 64 | // For larger systems, we would need a more general approach 65 | // This is a simplified implementation 66 | let control_qubits = (0..num_qubits - 1).collect::>(); 67 | let target = num_qubits - 1; 68 | 69 | circuit.h(target); 70 | // Multi-controlled NOT gate 71 | apply_multi_controlled_not(circuit, &control_qubits, target); 72 | circuit.h(target); 73 | } 74 | 75 | // Undo X gates 76 | for i in 0..num_qubits { 77 | if (target_state >> i) & 1 == 0 { 78 | circuit.x(i); 79 | } 80 | } 81 | } 82 | 83 | // Function to apply the diffusion operator 84 | fn apply_diffusion(circuit: &mut Circuit, num_qubits: usize) { 85 | // Apply Hadamard gates to all qubits 86 | for i in 0..num_qubits { 87 | circuit.h(i); 88 | } 89 | 90 | // Apply X gates to all qubits 91 | for i in 0..num_qubits { 92 | circuit.x(i); 93 | } 94 | 95 | // Apply multi-controlled Z gate (inversion about the mean) 96 | if num_qubits == 3 { 97 | circuit.h(2); 98 | circuit.add_operation(toffoli_gate(), vec![0, 1, 2], "CCZ"); 99 | circuit.h(2); 100 | } else { 101 | // For larger systems, use a generalized approach 102 | let control_qubits = (0..num_qubits - 1).collect::>(); 103 | let target = num_qubits - 1; 104 | 105 | circuit.h(target); 106 | apply_multi_controlled_not(circuit, &control_qubits, target); 107 | circuit.h(target); 108 | } 109 | 110 | // Undo X gates 111 | for i in 0..num_qubits { 112 | circuit.x(i); 113 | } 114 | 115 | // Undo Hadamard gates 116 | for i in 0..num_qubits { 117 | circuit.h(i); 118 | } 119 | } 120 | 121 | // Helper function to apply a multi-controlled NOT gate 122 | fn apply_multi_controlled_not(circuit: &mut Circuit, controls: &[usize], target: usize) { 123 | // This is a simplified implementation for demo purposes 124 | // In a real quantum computer, this would require decomposition into elementary gates 125 | 126 | // For 2 control qubits, we can use the built-in Toffoli gate 127 | if controls.len() == 2 { 128 | circuit.add_operation( 129 | toffoli_gate(), 130 | vec![controls[0], controls[1], target], 131 | "Toffoli", 132 | ); 133 | } else { 134 | // For more control qubits, this would require a more complex implementation 135 | // using ancilla qubits and gate decomposition 136 | println!( 137 | "Warning: Multi-controlled NOT with {} control qubits is being simulated directly", 138 | controls.len() 139 | ); 140 | 141 | // This is a placeholder for a more complex implementation 142 | let mut qubits = controls.to_vec(); 143 | qubits.push(target); 144 | // circuit.add_operation( 145 | // // In a real implementation, this would be a properly constructed matrix 146 | // // for a multi-controlled NOT operation 147 | // MatrixGate::identity(qubits.len()), 148 | // qubits, 149 | // "Multi-controlled NOT" 150 | // ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /examples/quantum_fourier_transform.rs: -------------------------------------------------------------------------------- 1 | use logosq::algorithms::qft; 2 | use logosq::circuits::Circuit; 3 | use logosq::vis::circuit_text; 4 | use logosq::vis::save_circuit_svg; 5 | use logosq::vis::Visualizable; 6 | use logosq::State; 7 | 8 | fn main() { 9 | let num_qubits = 5; // Example with 5 qubits 10 | let results = quantum_fourier_transform_example(num_qubits); 11 | println!("Measurement results after QFT: {:?}", results); 12 | } 13 | // how to combine algorithms 14 | pub fn quantum_fourier_transform_example(num_qubits: usize) -> Vec { 15 | let mut state = State::zero_state(num_qubits); 16 | // Create a circuit that demonstrates QFT 17 | let mut circuit = Circuit::new(num_qubits); 18 | circuit.x(0); 19 | circuit.execute(&mut state); 20 | 21 | // Apply QFT 22 | qft::apply(&mut state); 23 | print!("State after QFT: {}\n", state.visualize()); 24 | qft::apply_inverse(&mut state); 25 | print!("State after inverse QFT: {}\n", state.visualize()); 26 | circuit.execute_and_measure() 27 | } 28 | 29 | pub fn qft_c2() { 30 | let num_qubits = 3; // Example with 3 qubits 31 | let circuit = Circuit::new(num_qubits); 32 | // circuit.compose(&qft::create_circuit(num_qubits)); 33 | 34 | print!("\nCircuit diagram:\n"); 35 | print!("{}", circuit_text(&circuit)); 36 | save_circuit_svg(&circuit, "quantum_fourier_transform.svg").unwrap_or_else(|e| { 37 | println!("Failed to save circuit diagram: {}", e); 38 | }); 39 | 40 | // Measure all qubits 41 | circuit.execute_and_measure(); 42 | } 43 | -------------------------------------------------------------------------------- /examples/quantum_teleportation.rs: -------------------------------------------------------------------------------- 1 | use logosq::prelude::*; 2 | use logosq::vis::{circuit_text, save_circuit_svg}; 3 | use std::f64::consts::PI; 4 | 5 | fn main() { 6 | println!("Quantum Teleportation Protocol"); 7 | println!("------------------------------"); 8 | 9 | // Create a circuit with 3 qubits 10 | let mut circuit = Circuit::new(3).with_name("Quantum Teleportation"); 11 | 12 | // Step 1: Create a state to teleport 13 | // |ψ⟩ = cos(π/8)|0⟩ + sin(π/8)|1⟩ 14 | circuit.ry(0, PI / 4.0); 15 | println!("Preparing state to teleport: |ψ⟩ = cos(π/8)|0⟩ + sin(π/8)|1⟩"); 16 | 17 | // Step 2: Create an entangled pair (Bell state) between qubits 1 and 2 18 | circuit.h(1); 19 | circuit.cnot(1, 2); 20 | println!("Created entangled Bell state between Alice and Bob"); 21 | 22 | // Step 3: Alice performs the teleportation protocol 23 | circuit.cnot(0, 1); 24 | circuit.h(0); 25 | println!("Alice performs entanglement operation"); 26 | 27 | // Create initial state - properly initialized zero state 28 | let initial_state = State::zero_state(3); 29 | 30 | // Execute the circuit up to this point 31 | let mut teleport_state_state = initial_state.clone(); 32 | circuit.execute(&mut teleport_state_state); 33 | let teleport_state = teleport_state_state; 34 | 35 | // Simulate measurements on qubits 0 and 1 36 | // We'll manually calculate the probabilities from the state vector 37 | let m0 = manual_measure_qubit(&teleport_state, 0); 38 | let m1 = manual_measure_qubit(&teleport_state, 1); 39 | 40 | println!("Alice's measurements (simulated): m0={}, m1={}", m0, m1); 41 | 42 | // Create a new circuit that applies conditional operations based on measurements 43 | let mut bob_circuit = Circuit::new(3); 44 | 45 | // Apply the same initial operations to get to the same state 46 | bob_circuit.ry(0, PI / 4.0); 47 | bob_circuit.h(1); 48 | bob_circuit.cnot(1, 2); 49 | bob_circuit.cnot(0, 1); 50 | bob_circuit.h(0); 51 | 52 | // Apply Bob's corrections based on Alice's measurement results 53 | if m1 == 1 { 54 | bob_circuit.x(2); 55 | println!("Bob applies X gate based on m1"); 56 | } 57 | 58 | if m0 == 1 { 59 | bob_circuit.z(2); 60 | println!("Bob applies Z gate based on m0"); 61 | } 62 | 63 | // Execute Bob's full circuit 64 | let mut bob_state = initial_state.clone(); 65 | bob_circuit.execute(&mut bob_state); 66 | let final_state = bob_state; 67 | 68 | // Calculate the probability of measuring |1⟩ on Bob's qubit 69 | let prob_one = calculate_prob_one(&final_state, 2); 70 | println!("Probability of Bob measuring |1⟩: {:.4}", prob_one); 71 | 72 | // Visualize the circuit 73 | println!("\nCircuit diagram:"); 74 | println!("{}", circuit_text(&bob_circuit)); 75 | 76 | // Save the circuit diagram 77 | save_circuit_svg(&bob_circuit, "quantum_teleportation.svg").unwrap_or_else(|e| { 78 | println!("Failed to save circuit diagram: {}", e); 79 | }); 80 | 81 | println!("\nAnalysis:"); 82 | println!("The initial state |ψ⟩ has been teleported from Alice to Bob."); 83 | println!("Bob's qubit should now be in the state |ψ⟩ = cos(π/8)|0⟩ + sin(π/8)|1⟩"); 84 | println!( 85 | "Theoretical probability of measuring |1⟩: {:.4}", 86 | (PI / 8.0).sin().powi(2) 87 | ); 88 | } 89 | 90 | // Helper function to manually calculate measurement probability and result 91 | fn manual_measure_qubit(state: &State, qubit_idx: usize) -> usize { 92 | let mut prob_one = 0.0; 93 | 94 | // Calculate probability by summing up amplitudes where the qubit is 1 95 | for i in 0..state.vector.len() { 96 | if (i & (1 << qubit_idx)) != 0 { 97 | prob_one += state.vector[i].norm_sqr(); 98 | } 99 | } 100 | 101 | // Deterministic result based on probability (for reproducibility) 102 | if prob_one > 0.5 { 103 | 1 104 | } else { 105 | 0 106 | } 107 | } 108 | 109 | // Helper function to calculate probability of measuring |1⟩ 110 | fn calculate_prob_one(state: &State, qubit_idx: usize) -> f64 { 111 | let mut prob_one = 0.0; 112 | 113 | for i in 0..state.vector.len() { 114 | if (i & (1 << qubit_idx)) != 0 { 115 | prob_one += state.vector[i].norm_sqr(); 116 | } 117 | } 118 | 119 | prob_one 120 | } 121 | -------------------------------------------------------------------------------- /examples/state_vis.rs: -------------------------------------------------------------------------------- 1 | use logosq::states::State; 2 | use logosq::vis::Visualizable; 3 | use num_complex::Complex64; 4 | use std::env; 5 | use std::f64::consts::{FRAC_1_SQRT_2, PI}; 6 | 7 | fn main() -> std::io::Result<()> { 8 | println!("LogosQ Quantum State Visualization Examples\n"); 9 | 10 | // Parse command line arguments 11 | let args: Vec = env::args().collect(); 12 | let show_all = args.len() <= 1 || args.contains(&String::from("all")); 13 | let show_basic = show_all || args.contains(&String::from("basic")); 14 | let show_superposition = show_all || args.contains(&String::from("super")); 15 | let show_phase = show_all || args.contains(&String::from("phase")); 16 | let show_arbitrary = show_all || args.contains(&String::from("arb")); 17 | let show_evolution = show_all || args.contains(&String::from("evolution")); 18 | 19 | if args.len() > 1 20 | && !show_basic 21 | && !show_superposition 22 | && !show_phase 23 | && !show_arbitrary 24 | && !show_evolution 25 | { 26 | println!("Usage: cargo run --example state_vis [all|basic|super|phase|arb|evolution]"); 27 | println!(" all: Show all visualizations (default)"); 28 | println!(" basic: Show basic states (|0⟩, |1⟩)"); 29 | println!(" super: Show superposition states (|+⟩, |-⟩)"); 30 | println!(" phase: Show phase states (|i⟩, |-i⟩)"); 31 | println!(" arb: Show arbitrary states"); 32 | println!(" evolution: Show state evolution under gates"); 33 | return Ok(()); 34 | } 35 | 36 | println!("Note: This example visualizes single-qubit states using the 3D Bloch sphere.\n"); 37 | 38 | // Example 1: Single qubit basis states 39 | if show_basic { 40 | println!("=== Example 1: Single Qubit Basis States ==="); 41 | 42 | println!("\n|0⟩ state (North Pole of Bloch Sphere):"); 43 | let zero_state = State::zero_state(1); 44 | println!("{}", zero_state.visualize()); 45 | zero_state.save_visualization("zero_state.svg")?; 46 | println!("Saved visualization to zero_state.svg"); 47 | 48 | println!("\n|1⟩ state (South Pole of Bloch Sphere):"); 49 | let one_state = State::one_state(1); 50 | println!("{}", one_state.visualize()); 51 | one_state.save_visualization("one_state.svg")?; 52 | println!("Saved visualization to one_state.svg"); 53 | } 54 | 55 | // Example 2: Single qubit superposition states 56 | if show_superposition { 57 | println!("\n=== Example 2: Single Qubit Superposition States ==="); 58 | 59 | println!("\n|+⟩ state (Positive X-axis on Bloch Sphere):"); 60 | let plus_state = State::plus_state(1); 61 | println!("{}", plus_state.visualize()); 62 | plus_state.save_visualization("plus_state.svg")?; 63 | println!("Saved visualization to plus_state.svg"); 64 | 65 | println!("\n|-⟩ state (Negative X-axis on Bloch Sphere):"); 66 | let mut minus_state = State::zero_state(1); 67 | minus_state.vector[0] = Complex64::new(FRAC_1_SQRT_2, 0.0); 68 | minus_state.vector[1] = Complex64::new(-FRAC_1_SQRT_2, 0.0); 69 | println!("{}", minus_state.visualize()); 70 | minus_state.save_visualization("minus_state.svg")?; 71 | println!("Saved visualization to minus_state.svg"); 72 | } 73 | 74 | // Example 3: Phase states 75 | if show_phase { 76 | println!("\n=== Example 3: Phase States on Bloch Sphere ==="); 77 | 78 | println!("\n|i⟩ state (Positive Y-axis on Bloch Sphere):"); 79 | let mut i_state = State::zero_state(1); 80 | i_state.vector[0] = Complex64::new(FRAC_1_SQRT_2, 0.0); 81 | i_state.vector[1] = Complex64::new(0.0, FRAC_1_SQRT_2); 82 | println!("{}", i_state.visualize()); 83 | i_state.save_visualization("i_state.svg")?; 84 | println!("Saved visualization to i_state.svg"); 85 | 86 | println!("\n|-i⟩ state (Negative Y-axis on Bloch Sphere):"); 87 | let mut neg_i_state = State::zero_state(1); 88 | neg_i_state.vector[0] = Complex64::new(FRAC_1_SQRT_2, 0.0); 89 | neg_i_state.vector[1] = Complex64::new(0.0, -FRAC_1_SQRT_2); 90 | println!("{}", neg_i_state.visualize()); 91 | neg_i_state.save_visualization("neg_i_state.svg")?; 92 | println!("Saved visualization to neg_i_state.svg"); 93 | } 94 | 95 | // Example 4: Arbitrary single-qubit states 96 | if show_arbitrary { 97 | println!("\n=== Example 4: Arbitrary Single-Qubit States ==="); 98 | 99 | // Create an arbitrary state 100 | println!("\nArbitrary state 1:"); 101 | let mut arbitrary_state1 = State::zero_state(1); 102 | let theta = PI / 4.0; // 45 degrees from Z-axis 103 | let phi = PI / 3.0; // 60 degrees azimuthal angle 104 | 105 | arbitrary_state1.vector[0] = Complex64::new(theta.cos(), 0.0); 106 | arbitrary_state1.vector[1] = 107 | Complex64::new(theta.sin() * phi.cos(), theta.sin() * phi.sin()); 108 | arbitrary_state1.normalize(); 109 | println!("{}", arbitrary_state1.visualize()); 110 | arbitrary_state1.save_visualization("arbitrary_state1.svg")?; 111 | println!("Saved visualization to arbitrary_state1.svg"); 112 | 113 | // Create another arbitrary state 114 | println!("\nArbitrary state 2:"); 115 | let mut arbitrary_state2 = State::zero_state(1); 116 | let theta = 2.0 * PI / 3.0; // 120 degrees from Z-axis 117 | let phi = 3.0 * PI / 4.0; // 135 degrees azimuthal angle 118 | 119 | arbitrary_state2.vector[0] = Complex64::new(theta.cos(), 0.0); 120 | arbitrary_state2.vector[1] = 121 | Complex64::new(theta.sin() * phi.cos(), theta.sin() * phi.sin()); 122 | arbitrary_state2.normalize(); 123 | println!("{}", arbitrary_state2.visualize()); 124 | arbitrary_state2.save_visualization("arbitrary_state2.svg")?; 125 | println!("Saved visualization to arbitrary_state2.svg"); 126 | } 127 | 128 | // Display information about interactive viewing 129 | println!("\n=== Interactive Viewing ==="); 130 | println!("You can open any of the generated SVG files in your browser or SVG viewer."); 131 | println!("Alternatively, uncomment the view() method in the code to display directly."); 132 | 133 | // Note about multi-qubit states 134 | println!("\n=== Note on Multi-Qubit States ==="); 135 | println!("The current visualization implementation only supports single-qubit states."); 136 | println!("Multi-qubit states can be viewed in text format but cannot be visualized on the Bloch sphere."); 137 | 138 | println!("\nAll examples completed successfully!"); 139 | Ok(()) 140 | } 141 | -------------------------------------------------------------------------------- /examples/visualization.rs: -------------------------------------------------------------------------------- 1 | use logosq::circuits::Circuit; 2 | use logosq::gates::{self}; 3 | use logosq::vis::{circuit_text, save_circuit_svg}; 4 | // Use the trait methods 5 | use logosq::vis::Visualizable; 6 | 7 | fn main() -> std::io::Result<()> { 8 | // Example 1: Bell State Circuit 9 | println!("=== Example 1: Bell State Circuit ==="); 10 | let mut bell_circuit = Circuit::new(2).with_name("Bell State"); 11 | 12 | // Using the single_qubit::h_gate() function that returns a MatrixGate 13 | let h = gates::single_qubit::h_gate(); 14 | bell_circuit.add_operation(h, vec![0], "H"); 15 | 16 | // Using the multi_qubit::cnot_gate() function 17 | let cnot = gates::multi_qubit::cnot_gate(); 18 | bell_circuit.add_operation(cnot, vec![0, 1], "CNOT"); 19 | 20 | // Using direct functions 21 | println!("Text visualization:"); 22 | println!("{}", circuit_text(&bell_circuit)); 23 | 24 | // Save SVG to file 25 | save_circuit_svg(&bell_circuit, "bell_circuit.svg")?; 26 | println!("SVG visualization saved to 'bell_circuit.svg'"); 27 | 28 | // Example 2: GHZ State Circuit 29 | println!("\n=== Example 2: GHZ State Circuit ==="); 30 | let mut ghz_circuit = Circuit::new(3).with_name("GHZ State Preparation"); 31 | 32 | // Add H gate to first qubit 33 | ghz_circuit.add_operation(gates::single_qubit::h_gate(), vec![0], "H"); 34 | 35 | // Add CNOT gates to create GHZ state 36 | ghz_circuit.add_operation(gates::multi_qubit::cnot_gate(), vec![0, 1], "CNOT"); 37 | ghz_circuit.add_operation(gates::multi_qubit::cnot_gate(), vec![1, 2], "CNOT"); 38 | 39 | // Using trait methods 40 | println!("Text visualization:"); 41 | println!("{}", ghz_circuit.visualize()); 42 | 43 | // Save visualization 44 | ghz_circuit.save_visualization("ghz_circuit.svg")?; 45 | println!("SVG visualization saved to 'ghz_circuit.svg'"); 46 | 47 | // Example 3: Quantum Fourier Transform (QFT) - 3 qubit 48 | println!("\n=== Example 3: Quantum Fourier Transform (3 qubits) ==="); 49 | let mut qft_circuit = Circuit::new(3).with_name("3-Qubit QFT"); 50 | 51 | // QFT implementation 52 | // Qubit 0 53 | qft_circuit.add_operation(gates::single_qubit::h_gate(), vec![0], "H"); 54 | qft_circuit.add_operation( 55 | gates::single_qubit::rz_gate(std::f64::consts::PI / 2.0), 56 | vec![1, 0], 57 | "Rz(π/2)", 58 | ); 59 | qft_circuit.add_operation( 60 | gates::single_qubit::rz_gate(std::f64::consts::PI / 4.0), 61 | vec![2, 0], 62 | "Rz(π/4)", 63 | ); 64 | 65 | // Qubit 1 66 | qft_circuit.add_operation(gates::single_qubit::h_gate(), vec![1], "H"); 67 | qft_circuit.add_operation( 68 | gates::single_qubit::rz_gate(std::f64::consts::PI / 2.0), 69 | vec![2, 1], 70 | "Rz(π/2)", 71 | ); 72 | 73 | // Qubit 2 74 | qft_circuit.add_operation(gates::single_qubit::h_gate(), vec![2], "H"); 75 | 76 | // Swap qubits 0 and 2 77 | qft_circuit.add_operation(gates::multi_qubit::swap_gate(), vec![0, 2], "SWAP"); 78 | 79 | println!("Text visualization:"); 80 | println!("{}", qft_circuit.visualize()); 81 | qft_circuit.save_visualization("qft_circuit.svg")?; 82 | println!("SVG visualization saved to 'qft_circuit.svg'"); 83 | 84 | // Example 4: Complex circuit with Toffoli 85 | println!("\n=== Example 4: Complex Circuit with Toffoli Gate ==="); 86 | let mut complex_circuit = Circuit::new(4).with_name("Complex Example"); 87 | 88 | // Initial layer of Hadamards 89 | let h_gate = gates::single_qubit::h_gate(); 90 | for i in 0..4 { 91 | complex_circuit.add_operation(h_gate.clone(), vec![i], "H"); 92 | } 93 | 94 | // Add some X gates 95 | let x_gate = gates::single_qubit::x_gate(); 96 | complex_circuit.add_operation(x_gate.clone(), vec![1], "X"); 97 | complex_circuit.add_operation(x_gate, vec![3], "X"); 98 | 99 | // Add CNOT gates 100 | let cnot_gate = gates::multi_qubit::cnot_gate(); 101 | complex_circuit.add_operation(cnot_gate.clone(), vec![0, 1], "CNOT"); 102 | complex_circuit.add_operation(cnot_gate.clone(), vec![2, 3], "CNOT"); 103 | 104 | // Add Toffoli gate 105 | complex_circuit.add_operation(gates::multi_qubit::toffoli_gate(), vec![0, 1, 2], "Toffoli"); 106 | 107 | // More gates 108 | complex_circuit.add_operation(gates::single_qubit::h_gate(), vec![0], "H"); 109 | complex_circuit.add_operation(cnot_gate, vec![3, 0], "CNOT"); 110 | 111 | println!("Text visualization:"); 112 | println!("{}", complex_circuit.visualize()); 113 | complex_circuit.save_visualization("complex_circuit.svg")?; 114 | println!("SVG visualization saved to 'complex_circuit.svg'"); 115 | 116 | // Optional: View the circuits in a browser 117 | println!("\nWould you like to view the circuits in your browser? (Uncomment the lines below)"); 118 | // bell_circuit.view()?; 119 | // ghz_circuit.view()?; 120 | // qft_circuit.view()?; 121 | // complex_circuit.view()?; 122 | 123 | Ok(()) 124 | } 125 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazabap/LogosQ/bc4ca10795ac8adac35b9f37782b2904621f73bc/scripts/publish.sh -------------------------------------------------------------------------------- /scripts/start.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazabap/LogosQ/bc4ca10795ac8adac35b9f37782b2904621f73bc/scripts/start.sh -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # filepath: /home/zazabap/Projects/Qforge/LogosQ/scripts/test.sh 3 | 4 | # Exit on any error 5 | set -e 6 | 7 | echo "=== Starting comprehensive LogosQ testing ===" 8 | 9 | # Step 1: Format code 10 | echo "=== Formatting code ===" 11 | cargo fmt --all -- --check 12 | echo "✓ Code formatting verified" 13 | 14 | # Step 2: Run clippy for linting 15 | echo "=== Running Clippy linter ===" 16 | cargo clippy --all-targets --all-features -- -D warnings 17 | echo "✓ Clippy checks passed" 18 | 19 | # Step 3: Run unit tests 20 | echo "=== Running unit tests ===" 21 | cargo test --all-features 22 | echo "✓ Unit tests passed" 23 | 24 | # Step 4: Run doc tests 25 | echo "=== Running documentation tests ===" 26 | cargo test --doc 27 | echo "✓ Documentation tests passed" 28 | 29 | # Step 5: Run benchmarks (if available) 30 | if [ -d "benches" ] || grep -q "\[\[bench\]\]" Cargo.toml; then 31 | echo "=== Running benchmarks ===" 32 | cargo bench 33 | echo "✓ Benchmarks completed" 34 | fi 35 | 36 | # Step 6: Check for unused dependencies 37 | echo "=== Checking for unused dependencies ===" 38 | cargo +nightly udeps --all-targets 2>/dev/null || echo "! cargo-udeps not available (install with 'cargo install cargo-udeps')" 39 | 40 | # Step 7: Generate and validate documentation 41 | echo "=== Generating documentation ===" 42 | cargo doc --no-deps 43 | echo "✓ Documentation generated successfully" 44 | 45 | # Step 8: Run coverage (if tarpaulin is installed) 46 | echo "=== Running code coverage ===" 47 | if command -v cargo-tarpaulin >/dev/null 2>&1; then 48 | cargo tarpaulin --out Html --output-dir coverage 49 | echo "✓ Coverage report generated in coverage/" 50 | else 51 | echo "! cargo-tarpaulin not installed (install with 'cargo install cargo-tarpaulin')" 52 | fi 53 | 54 | # Step 9: Run examples 55 | echo "=== Testing examples ===" 56 | find examples -name "*.rs" | while read -r example; do 57 | echo "Running example: $example" 58 | cargo run --example $(basename "$example" .rs) 59 | done 60 | echo "✓ All examples successfully executed" 61 | 62 | # Step 10: Build in release mode 63 | echo "=== Building in release mode ===" 64 | cargo build --release 65 | echo "✓ Release build successful" 66 | 67 | echo "=== All tests completed successfully! ===" -------------------------------------------------------------------------------- /src/algorithms/mod.rs: -------------------------------------------------------------------------------- 1 | // This file defines the algorithms module for the quantum computing library. 2 | // It exports functions for implementing various quantum algorithms. 3 | 4 | // Import necessary dependencies 5 | 6 | // Declare and re-export each algorithm module 7 | pub mod qft; 8 | pub mod rqc; 9 | pub mod xyz_heisenberg; 10 | 11 | // Optional: Re-export commonly used functions at the module level 12 | // for easier access (without having to use the submodule path) 13 | pub use qft::{apply as apply_qft, create_circuit as create_qft_circuit}; 14 | pub use rqc::generate as generate_random_circuit; 15 | pub use xyz_heisenberg::simulate as simulate_heisenberg; 16 | 17 | // Example usage functions can stay in mod.rs as they demonstrate 18 | -------------------------------------------------------------------------------- /src/algorithms/qft.rs: -------------------------------------------------------------------------------- 1 | // Quantum Fourier Transform implementation - optimized with FFT 2 | 3 | use crate::circuits::Circuit; 4 | use crate::gates::optimized::ControlledPhaseGate; 5 | use crate::states::State; 6 | use rustfft::FftPlanner; 7 | use std::f64::consts::PI; 8 | 9 | /// Creates a Quantum Fourier Transform circuit for the specified number of qubits. 10 | pub fn create_circuit(num_qubits: usize) -> Circuit { 11 | let mut circuit = Circuit::new(num_qubits).with_name("Quantum Fourier Transform"); 12 | 13 | // Gate-based QFT (kept for debugging / compatibility) 14 | for i in 0..num_qubits { 15 | circuit.h(i); 16 | for j in (i + 1)..num_qubits { 17 | let angle = PI / (1 << (j - i)) as f64; 18 | controlled_phase(&mut circuit, i, j, angle); 19 | } 20 | } 21 | 22 | for i in 0..num_qubits / 2 { 23 | circuit.swap(i, num_qubits - i - 1); 24 | } 25 | 26 | circuit 27 | } 28 | 29 | /// Creates an inverse Quantum Fourier Transform circuit for the specified number of qubits. 30 | pub fn create_inverse_circuit(num_qubits: usize) -> Circuit { 31 | let mut circuit = Circuit::new(num_qubits).with_name("Inverse Quantum Fourier Transform"); 32 | 33 | for i in 0..num_qubits / 2 { 34 | circuit.swap(i, num_qubits - i - 1); 35 | } 36 | 37 | for i in (0..num_qubits).rev() { 38 | for j in (i + 1)..num_qubits { 39 | let angle = -PI / ((1 << (j - i)) as f64); 40 | controlled_phase(&mut circuit, i, j, angle); 41 | } 42 | circuit.h(i); 43 | } 44 | 45 | circuit 46 | } 47 | 48 | /// Applies the QFT to a quantum state (optimized using an FFT on the amplitude vector). 49 | /// This replaces running the gate sequence and runs in O(N log N) time. 50 | pub fn apply(state: &mut State) { 51 | let n = state.num_qubits; 52 | let len = 1usize << n; 53 | 54 | // Ensure state vector has contiguous slice 55 | let buf = state 56 | .vector 57 | .as_slice_mut() 58 | .expect("State vector must be contiguous for FFT optimization"); 59 | 60 | // Use inverse FFT to compute QFT: QFT(|x>)_k = 1/sqrt(N) * sum_x exp(2π i x k / N) * a_x 61 | let mut planner = FftPlanner::::new(); 62 | let fft = planner.plan_fft_inverse(len); 63 | // rustfft uses num_complex::Complex which is compatible with Complex64 64 | fft.process(buf); 65 | 66 | // Normalize by 1/sqrt(N) to match quantum normalization 67 | let scale = 1.0 / (len as f64).sqrt(); 68 | for v in buf.iter_mut() { 69 | *v = *v * scale; 70 | } 71 | } 72 | 73 | /// Applies the inverse QFT to a quantum state (optimized using an FFT). 74 | pub fn apply_inverse(state: &mut State) { 75 | let n = state.num_qubits; 76 | let len = 1usize << n; 77 | 78 | let buf = state 79 | .vector 80 | .as_slice_mut() 81 | .expect("State vector must be contiguous for FFT optimization"); 82 | 83 | // Use forward FFT to compute inverse QFT: QFT^{-1} = 1/sqrt(N) * forward FFT 84 | let mut planner = FftPlanner::::new(); 85 | let fft = planner.plan_fft_forward(len); 86 | fft.process(buf); 87 | 88 | let scale = 1.0 / (len as f64).sqrt(); 89 | for v in buf.iter_mut() { 90 | *v = *v * scale; 91 | } 92 | } 93 | 94 | /// Helper function to apply a controlled phase rotation. 95 | /// Adds an optimized ControlledPhaseGate instead of building a full matrix. 96 | pub fn controlled_phase(circuit: &mut Circuit, control: usize, target: usize, angle: f64) { 97 | let gate = ControlledPhaseGate { 98 | control, 99 | target, 100 | angle, 101 | num_qubits: circuit.num_qubits, 102 | }; 103 | 104 | circuit.add_operation(gate, vec![control, target], &format!("CP({:.4})", angle)); 105 | } 106 | -------------------------------------------------------------------------------- /src/algorithms/rqc.rs: -------------------------------------------------------------------------------- 1 | // Random Quantum Circuits implementation 2 | 3 | use crate::circuits::Circuit; 4 | use crate::states::State; 5 | use rand::distributions::{Distribution, WeightedIndex}; 6 | use rand::SeedableRng; 7 | use rand::{thread_rng, Rng}; 8 | use std::f64::consts::PI; 9 | 10 | /// Random gate types that can be applied 11 | #[derive(Debug, Clone)] 12 | pub enum RandomGateType { 13 | Single, // Single-qubit gates like H, X, Y, Z, S, T 14 | TwoQubit, // Two-qubit gates like CNOT, CZ, SWAP 15 | Rotation, // Rotation gates with random angles 16 | } 17 | 18 | /// Configuration for random circuit generation 19 | pub struct RandomCircuitConfig { 20 | pub num_qubits: usize, 21 | pub depth: usize, 22 | pub gate_types: Vec<(RandomGateType, f64)>, // Gate type and probability 23 | pub seed: Option, // Optional seed for reproducibility 24 | } 25 | 26 | impl Default for RandomCircuitConfig { 27 | fn default() -> Self { 28 | Self { 29 | num_qubits: 3, 30 | depth: 10, 31 | gate_types: vec![ 32 | (RandomGateType::Single, 0.5), 33 | (RandomGateType::TwoQubit, 0.3), 34 | (RandomGateType::Rotation, 0.2), 35 | ], 36 | seed: None, 37 | } 38 | } 39 | } 40 | 41 | /// Generates a random quantum circuit according to the provided configuration 42 | pub fn generate(config: &RandomCircuitConfig) -> Circuit { 43 | let mut circuit = 44 | Circuit::new(config.num_qubits).with_name(&format!("Random Circuit (d={})", config.depth)); 45 | 46 | let mut rng = match config.seed { 47 | Some(seed) => rand::rngs::StdRng::seed_from_u64(seed), 48 | None => rand::rngs::StdRng::from_entropy(), 49 | }; 50 | 51 | for _ in 0..config.depth { 52 | // Select a random gate type based on probabilities 53 | let gate_type = select_gate_type(&config.gate_types, &mut rng); 54 | 55 | match gate_type { 56 | RandomGateType::Single => { 57 | add_random_single_qubit_gate(&mut circuit, config.num_qubits, &mut rng); 58 | } 59 | RandomGateType::TwoQubit => { 60 | if config.num_qubits >= 2 { 61 | add_random_two_qubit_gate(&mut circuit, config.num_qubits, &mut rng); 62 | } else { 63 | // Fall back to single-qubit gate if not enough qubits 64 | add_random_single_qubit_gate(&mut circuit, config.num_qubits, &mut rng); 65 | } 66 | } 67 | RandomGateType::Rotation => { 68 | add_random_rotation_gate(&mut circuit, config.num_qubits, &mut rng); 69 | } 70 | } 71 | } 72 | 73 | circuit 74 | } 75 | 76 | /// Helper functions for gate selection and addition 77 | fn select_gate_type(gate_types: &[(RandomGateType, f64)], rng: &mut R) -> RandomGateType { 78 | // Extract probabilities for weighted selection 79 | let weights: Vec = gate_types.iter().map(|(_, prob)| *prob).collect(); 80 | 81 | // Create a weighted distribution 82 | let dist = match WeightedIndex::new(&weights) { 83 | Ok(dist) => dist, 84 | Err(_) => { 85 | // Fallback to uniform distribution if weights are invalid 86 | return RandomGateType::Single; 87 | } 88 | }; 89 | 90 | // Select an index based on the distribution 91 | let selected_idx = dist.sample(rng); 92 | gate_types[selected_idx].0.clone() 93 | } 94 | 95 | /// Adds a random single-qubit gate to the circuit 96 | fn add_random_single_qubit_gate(circuit: &mut Circuit, num_qubits: usize, rng: &mut R) { 97 | // Select a random qubit 98 | let qubit = rng.gen_range(0..num_qubits); 99 | 100 | // Select a random single-qubit gate 101 | match rng.gen_range(0..6) { 102 | 0 => { 103 | circuit.h(qubit); 104 | } 105 | 1 => { 106 | circuit.x(qubit); 107 | } 108 | 2 => { 109 | circuit.y(qubit); 110 | } 111 | 3 => { 112 | circuit.z(qubit); 113 | } 114 | 4 => { 115 | circuit.s(qubit); 116 | } 117 | 5 => { 118 | circuit.t(qubit); 119 | } 120 | _ => unreachable!(), 121 | } 122 | } 123 | 124 | /// Adds a random two-qubit gate to the circuit 125 | fn add_random_two_qubit_gate(circuit: &mut Circuit, num_qubits: usize, rng: &mut R) { 126 | // Ensure we have at least 2 qubits 127 | if num_qubits < 2 { 128 | return; 129 | } 130 | 131 | // Select two different random qubits 132 | let qubit1 = rng.gen_range(0..num_qubits); 133 | let mut qubit2 = rng.gen_range(0..num_qubits); 134 | 135 | // Make sure qubits are different 136 | while qubit2 == qubit1 { 137 | qubit2 = rng.gen_range(0..num_qubits); 138 | } 139 | 140 | // Select a random two-qubit gate 141 | match rng.gen_range(0..3) { 142 | 0 => { 143 | circuit.cnot(qubit1, qubit2); 144 | } 145 | 1 => { 146 | // Controlled-Z implementation 147 | circuit.h(qubit2); 148 | circuit.cnot(qubit1, qubit2); 149 | circuit.h(qubit2); 150 | } 151 | 2 => { 152 | // SWAP implementation using 3 CNOTs 153 | circuit.cnot(qubit1, qubit2); 154 | circuit.cnot(qubit2, qubit1); 155 | circuit.cnot(qubit1, qubit2); 156 | } 157 | _ => unreachable!(), 158 | } 159 | } 160 | 161 | /// Adds a random rotation gate to the circuit 162 | fn add_random_rotation_gate(circuit: &mut Circuit, num_qubits: usize, rng: &mut R) { 163 | // Select a random qubit 164 | let qubit = rng.gen_range(0..num_qubits); 165 | 166 | // Generate a random angle between 0 and 2π 167 | let angle = rng.gen_range(0.0..2.0 * PI); 168 | 169 | // Select a random rotation axis 170 | match rng.gen_range(0..3) { 171 | 0 => { 172 | circuit.rx(qubit, angle); 173 | } 174 | 1 => { 175 | circuit.ry(qubit, angle); 176 | } 177 | 2 => { 178 | circuit.rz(qubit, angle); 179 | } 180 | _ => unreachable!(), 181 | } 182 | } 183 | 184 | /// Applies a random circuit to a quantum state 185 | pub fn apply(state: &mut State, config: &RandomCircuitConfig) { 186 | let circuit = generate(config); 187 | circuit.execute(state); 188 | } 189 | 190 | /// Generates a random circuit with specified number of qubits and depth 191 | /// using default probability distribution for gate types 192 | pub fn generate_simple(num_qubits: usize, depth: usize, seed: Option) -> Circuit { 193 | let config = RandomCircuitConfig { 194 | num_qubits, 195 | depth, 196 | gate_types: vec![ 197 | (RandomGateType::Single, 0.4), 198 | (RandomGateType::TwoQubit, 0.4), 199 | (RandomGateType::Rotation, 0.2), 200 | ], 201 | seed, 202 | }; 203 | 204 | generate(&config) 205 | } 206 | 207 | /// Creates a circuit that approximates a 2-design 208 | /// (A circuit that approximately achieves Haar randomness) 209 | pub fn generate_2design(num_qubits: usize) -> Circuit { 210 | // For a 2-design, we need O(n²) depth where n is the number of qubits 211 | let depth = 2 * num_qubits * num_qubits; 212 | 213 | // Use a specific distribution optimized for 2-designs 214 | let config = RandomCircuitConfig { 215 | num_qubits, 216 | depth, 217 | gate_types: vec![ 218 | (RandomGateType::Single, 0.3), 219 | (RandomGateType::TwoQubit, 0.5), 220 | (RandomGateType::Rotation, 0.2), 221 | ], 222 | seed: None, 223 | }; 224 | 225 | let circuit = generate(&config).with_name(&format!( 226 | "2-Design Random Circuit (n={}, d={})", 227 | num_qubits, depth 228 | )); 229 | circuit 230 | } 231 | 232 | /// Generates a circuit for quantum supremacy experiments 233 | /// Similar to the Google Sycamore architecture 234 | pub fn generate_supremacy_circuit(num_qubits: usize, depth: usize) -> Circuit { 235 | let mut circuit = 236 | Circuit::new(num_qubits).with_name(&format!("Supremacy Circuit (d={})", depth)); 237 | 238 | let mut rng = thread_rng(); 239 | 240 | // For each layer 241 | for layer in 0..depth { 242 | // Apply single-qubit gates to all qubits 243 | for q in 0..num_qubits { 244 | // Random rotation angles 245 | let theta = rng.gen_range(0.0..2.0 * PI); 246 | let phi = rng.gen_range(0.0..2.0 * PI); 247 | let lambda = rng.gen_range(0.0..2.0 * PI); 248 | 249 | // Apply single-qubit rotations (universal single-qubit gate) 250 | circuit.rz(q, phi); 251 | circuit.ry(q, theta); 252 | circuit.rz(q, lambda); 253 | } 254 | 255 | // Apply two-qubit gates in alternating pattern 256 | // Even layers: connect (0,1), (2,3), ... 257 | // Odd layers: connect (1,2), (3,4), ... 258 | let start_idx = layer % 2; 259 | 260 | for q in (start_idx..num_qubits - 1).step_by(2) { 261 | circuit.cnot(q, q + 1); 262 | } 263 | } 264 | 265 | circuit 266 | } 267 | -------------------------------------------------------------------------------- /src/algorithms/xyz_heisenberg.rs: -------------------------------------------------------------------------------- 1 | // XYZ-Heisenberg Model Dynamics implementation 2 | 3 | use crate::circuits::Circuit; 4 | use crate::states::State; 5 | use std::f64::consts::PI; 6 | 7 | /// Parameters for the XYZ-Heisenberg model 8 | pub struct HeisenbergParameters { 9 | pub jx: f64, // Coupling strength in X direction 10 | pub jy: f64, // Coupling strength in Y direction 11 | pub jz: f64, // Coupling strength in Z direction 12 | pub external_field: f64, // External magnetic field strength 13 | pub time_steps: usize, // Number of time steps for evolution 14 | pub dt: f64, // Time step size 15 | } 16 | 17 | impl Default for HeisenbergParameters { 18 | fn default() -> Self { 19 | Self { 20 | jx: 1.0, 21 | jy: 1.0, 22 | jz: 1.0, 23 | external_field: 0.0, 24 | time_steps: 100, 25 | dt: 0.01, 26 | } 27 | } 28 | } 29 | 30 | /// Creates a circuit that simulates the XYZ-Heisenberg model for the given parameters 31 | pub fn create_circuit(num_qubits: usize, params: &HeisenbergParameters) -> Circuit { 32 | let mut circuit = Circuit::new(num_qubits).with_name("XYZ-Heisenberg Model"); 33 | 34 | // Time evolution using Trotter-Suzuki decomposition 35 | for _ in 0..params.time_steps { 36 | // Add XX interactions 37 | for i in 0..num_qubits - 1 { 38 | add_xx_interaction(&mut circuit, i, i + 1, params.jx * params.dt); 39 | } 40 | 41 | // Add YY interactions 42 | for i in 0..num_qubits - 1 { 43 | add_yy_interaction(&mut circuit, i, i + 1, params.jy * params.dt); 44 | } 45 | 46 | // Add ZZ interactions 47 | for i in 0..num_qubits - 1 { 48 | add_zz_interaction(&mut circuit, i, i + 1, params.jz * params.dt); 49 | } 50 | 51 | // Add external field 52 | if params.external_field != 0.0 { 53 | for i in 0..num_qubits { 54 | circuit.rz(i, params.external_field * params.dt); 55 | } 56 | } 57 | } 58 | 59 | circuit 60 | } 61 | 62 | /// Simulate the XYZ-Heisenberg model dynamics on a given state 63 | pub fn simulate(state: &mut State, params: &HeisenbergParameters) { 64 | let circuit = create_circuit(state.num_qubits, params); 65 | circuit.execute(state); 66 | } 67 | 68 | // Helper functions for adding interactions 69 | fn add_xx_interaction(circuit: &mut Circuit, qubit1: usize, qubit2: usize, strength: f64) { 70 | // Implementation of e^(-i * strength * X⊗X) 71 | circuit.h(qubit1); 72 | circuit.h(qubit2); 73 | add_zz_interaction(circuit, qubit1, qubit2, strength); 74 | circuit.h(qubit1); 75 | circuit.h(qubit2); 76 | } 77 | 78 | fn add_yy_interaction(circuit: &mut Circuit, qubit1: usize, qubit2: usize, strength: f64) { 79 | // Implementation of e^(-i * strength * Y⊗Y) 80 | circuit.rx(qubit1, PI / 2.0); 81 | circuit.rx(qubit2, PI / 2.0); 82 | add_zz_interaction(circuit, qubit1, qubit2, strength); 83 | circuit.rx(qubit1, -PI / 2.0); 84 | circuit.rx(qubit2, -PI / 2.0); 85 | } 86 | 87 | fn add_zz_interaction(circuit: &mut Circuit, qubit1: usize, qubit2: usize, strength: f64) { 88 | // Implementation of e^(-i * strength * Z⊗Z) 89 | circuit.cnot(qubit1, qubit2); 90 | circuit.rz(qubit2, 2.0 * strength); 91 | circuit.cnot(qubit1, qubit2); 92 | } 93 | 94 | /// Calculate energy of the XYZ-Heisenberg model for a given state 95 | pub fn calculate_energy(state: &State, params: &HeisenbergParameters) -> f64 { 96 | let num_qubits = state.num_qubits; 97 | let mut energy = 0.0; 98 | 99 | // Calculate nearest-neighbor interaction energies 100 | for i in 0..num_qubits - 1 { 101 | // Calculate XX interaction energy: Jx * <ψ|X_i X_{i+1}|ψ> 102 | let mut xx_state = state.clone(); 103 | let mut xx_circuit = Circuit::new(num_qubits); 104 | xx_circuit.x(i); 105 | xx_circuit.x(i + 1); 106 | xx_circuit.execute(&mut xx_state); 107 | let xx_energy = params.jx * state.vector.dot(&xx_state.vector).re; 108 | energy += xx_energy; 109 | 110 | // Calculate YY interaction energy: Jy * <ψ|Y_i Y_{i+1}|ψ> 111 | let mut yy_state = state.clone(); 112 | let mut yy_circuit = Circuit::new(num_qubits); 113 | yy_circuit.y(i); 114 | yy_circuit.y(i + 1); 115 | yy_circuit.execute(&mut yy_state); 116 | let yy_energy = params.jy * state.vector.dot(&yy_state.vector).re; 117 | energy += yy_energy; 118 | 119 | // Calculate ZZ interaction energy: Jz * <ψ|Z_i Z_{i+1}|ψ> 120 | let mut zz_state = state.clone(); 121 | let mut zz_circuit = Circuit::new(num_qubits); 122 | zz_circuit.z(i); 123 | zz_circuit.z(i + 1); 124 | zz_circuit.execute(&mut zz_state); 125 | let zz_energy = params.jz * state.vector.dot(&zz_state.vector).re; 126 | energy += zz_energy; 127 | } 128 | 129 | // Calculate external field contribution: h * <ψ|Z_i|ψ> 130 | if params.external_field != 0.0 { 131 | for i in 0..num_qubits { 132 | let mut z_state = state.clone(); 133 | let mut z_circuit = Circuit::new(num_qubits); 134 | z_circuit.z(i); 135 | z_circuit.execute(&mut z_state); 136 | let z_energy = params.external_field * state.vector.dot(&z_state.vector).re; 137 | energy += z_energy; 138 | } 139 | } 140 | 141 | energy 142 | } 143 | 144 | /// Calculate energy using a more efficient approach with expectation values 145 | pub fn calculate_energy_efficient(state: &State, params: &HeisenbergParameters) -> f64 { 146 | let num_qubits = state.num_qubits; 147 | let mut energy = 0.0; 148 | 149 | // Calculate nearest-neighbor interaction energies 150 | for i in 0..num_qubits - 1 { 151 | energy += params.jx * expectation_xx(state, i, i + 1); 152 | energy += params.jy * expectation_yy(state, i, i + 1); 153 | energy += params.jz * expectation_zz(state, i, i + 1); 154 | } 155 | 156 | // Calculate external field contribution 157 | if params.external_field != 0.0 { 158 | for i in 0..num_qubits { 159 | energy += params.external_field * expectation_z(state, i); 160 | } 161 | } 162 | 163 | energy 164 | } 165 | 166 | /// Calculate expectation value of X_i X_j 167 | fn expectation_xx(state: &State, i: usize, j: usize) -> f64 { 168 | let mut state_copy = state.clone(); 169 | 170 | // Create a temporary circuit to apply X gates to specific qubits 171 | let mut circuit = Circuit::new(state.num_qubits); 172 | circuit.x(i); 173 | circuit.x(j); 174 | 175 | // Execute the circuit on our state copy 176 | circuit.execute(&mut state_copy); 177 | 178 | // Calculate inner product 179 | state.vector.dot(&state_copy.vector).re 180 | } 181 | 182 | /// Calculate expectation value of Y_i Y_j 183 | fn expectation_yy(state: &State, i: usize, j: usize) -> f64 { 184 | let mut state_copy = state.clone(); 185 | 186 | // Create a temporary circuit to apply Y gates to specific qubits 187 | let mut circuit = Circuit::new(state.num_qubits); 188 | circuit.y(i); 189 | circuit.y(j); 190 | 191 | // Execute the circuit on our state copy 192 | circuit.execute(&mut state_copy); 193 | 194 | // Calculate inner product 195 | state.vector.dot(&state_copy.vector).re 196 | } 197 | 198 | /// Calculate expectation value of Z_i Z_j 199 | fn expectation_zz(state: &State, i: usize, j: usize) -> f64 { 200 | let mut state_copy = state.clone(); 201 | 202 | // Create a temporary circuit to apply Z gates to specific qubits 203 | let mut circuit = Circuit::new(state.num_qubits); 204 | circuit.z(i); 205 | circuit.z(j); 206 | 207 | // Execute the circuit on our state copy 208 | circuit.execute(&mut state_copy); 209 | 210 | // Calculate inner product 211 | state.vector.dot(&state_copy.vector).re 212 | } 213 | 214 | /// Calculate expectation value of Z_i 215 | fn expectation_z(state: &State, i: usize) -> f64 { 216 | let mut state_copy = state.clone(); 217 | 218 | // Create a temporary circuit to apply Z gate to specific qubit 219 | let mut circuit = Circuit::new(state.num_qubits); 220 | circuit.z(i); 221 | 222 | // Execute the circuit on our state copy 223 | circuit.execute(&mut state_copy); 224 | 225 | // Calculate inner product 226 | state.vector.dot(&state_copy.vector).re 227 | } 228 | -------------------------------------------------------------------------------- /src/circuits/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::gates::{Gate, MatrixGate}; 2 | use crate::noise::NoiseModel; 3 | use crate::states::State; 4 | use ndarray::Array2; 5 | use num_complex::Complex64; 6 | use rayon::prelude::*; 7 | use std::fmt; 8 | use std::rc::Rc; 9 | 10 | mod single_qubit; 11 | mod three_qubits; 12 | mod two_qubits; 13 | mod utils; 14 | 15 | /// A quantum operation with an associated gate and target qubits 16 | pub struct Operation { 17 | pub gate: Rc, 18 | pub qubits: Vec, 19 | pub name: String, 20 | } 21 | 22 | impl fmt::Debug for Operation { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | write!(f, "{}(qubits: {:?})", self.name, self.qubits) 25 | } 26 | } 27 | 28 | /// Represents a quantum circuit with operations and measurement 29 | pub struct Circuit { 30 | pub operations: Vec, 31 | pub num_qubits: usize, 32 | pub name: Option, 33 | pub noise_models: Vec>, 34 | } 35 | 36 | impl Circuit { 37 | /// Creates a new quantum circuit with specified number of qubits 38 | pub fn new(num_qubits: usize) -> Self { 39 | Circuit { 40 | operations: Vec::new(), 41 | num_qubits, 42 | name: None, 43 | noise_models: Vec::new(), 44 | } 45 | } 46 | 47 | /// Sets a name for the circuit 48 | pub fn with_name(mut self, name: &str) -> Self { 49 | self.name = Some(name.to_string()); 50 | self 51 | } 52 | 53 | /// Adds a gate operation to the circuit 54 | pub fn add_operation(&mut self, gate: G, qubits: Vec, name: &str) { 55 | // Validate qubit indices 56 | for &qubit in &qubits { 57 | assert!( 58 | qubit < self.num_qubits, 59 | "Qubit index {} out of range", 60 | qubit 61 | ); 62 | } 63 | 64 | self.operations.push(Operation { 65 | gate: Rc::new(gate), 66 | qubits, 67 | name: name.to_string(), 68 | }); 69 | } 70 | 71 | /// Adds a matrix gate to the circuit 72 | pub fn add_matrix_gate(&mut self, matrix: Array2, qubits: Vec, name: &str) { 73 | let gate = MatrixGate { matrix }; 74 | self.add_operation(gate, qubits, name); 75 | } 76 | 77 | /// Executes the circuit on a given initial state (without noise) 78 | pub fn execute_without_noise<'a>(&self, initial_state: &'a mut State) -> &'a mut State { 79 | assert_eq!( 80 | initial_state.num_qubits, self.num_qubits, 81 | "Initial state must have the same number of qubits as the circuit" 82 | ); 83 | 84 | for operation in &self.operations { 85 | // Apply the gate to the state 86 | operation.gate.apply(initial_state); 87 | } 88 | 89 | initial_state 90 | } 91 | 92 | /// Creates and executes the circuit on a new zero state 93 | pub fn execute_and_measure(&self) -> Vec { 94 | let mut state = State::zero_state(self.num_qubits); 95 | self.execute(&mut state); 96 | 97 | // Make a copy of the state for parallel measurements 98 | let state_copy = state.clone(); 99 | 100 | // Measure each qubit in parallel 101 | (0..self.num_qubits) 102 | .into_par_iter() 103 | .map(|i| { 104 | let mut local_state = state_copy.clone(); 105 | local_state.measure_qubit(i) 106 | }) 107 | .collect() 108 | } 109 | 110 | /// Calculates the expectation value of an observable 111 | pub fn expectation(&self, observable: &dyn Gate) -> f64 { 112 | let mut state = State::zero_state(self.num_qubits); 113 | self.execute(&mut state); 114 | 115 | // Create a copy of the state to apply the observable 116 | let mut obs_state = state.clone(); 117 | observable.apply(&mut obs_state); 118 | 119 | // Calculate expectation value as <ψ|O|ψ> using parallel reduction 120 | (0..state.vector.len()) 121 | .into_par_iter() 122 | .map(|i| state.vector[i].conj() * obs_state.vector[i]) 123 | .reduce(|| Complex64::new(0.0, 0.0), |a, b| a + b) 124 | .re // Return the real part 125 | } 126 | 127 | /// Composes this circuit with another circuit 128 | pub fn compose(&mut self, other: &Circuit) { 129 | assert_eq!( 130 | self.num_qubits, other.num_qubits, 131 | "Cannot compose circuits with different numbers of qubits" 132 | ); 133 | 134 | // Add all operations from the other circuit 135 | for op in &other.operations { 136 | let op_clone = Operation { 137 | gate: op.gate.clone(), 138 | qubits: op.qubits.clone(), 139 | name: op.name.clone(), 140 | }; 141 | self.operations.push(op_clone); 142 | } 143 | } 144 | 145 | /// Creates a reversed version of this circuit 146 | pub fn reversed(&self) -> Self { 147 | let mut reversed = Circuit::new(self.num_qubits); 148 | 149 | // Add operations in reverse order 150 | for op in self.operations.iter().rev() { 151 | reversed.operations.push(Operation { 152 | gate: op.gate.clone(), 153 | qubits: op.qubits.clone(), 154 | name: format!("reversed_{}", op.name), 155 | }); 156 | } 157 | 158 | if let Some(name) = &self.name { 159 | reversed.name = Some(format!("{}_reversed", name)); 160 | } 161 | 162 | reversed 163 | } 164 | 165 | /// Adds a noise model to the circuit 166 | pub fn add_noise(&mut self, noise_model: N) -> &mut Self { 167 | self.noise_models.push(Rc::new(noise_model)); 168 | self 169 | } 170 | 171 | /// Executes the circuit on a given initial state, applying noise if models are present 172 | pub fn execute<'a>(&self, initial_state: &'a mut State) -> &'a mut State { 173 | assert_eq!( 174 | initial_state.num_qubits, self.num_qubits, 175 | "Initial state must have the same number of qubits as the circuit" 176 | ); 177 | 178 | let apply_noise = !self.noise_models.is_empty(); 179 | 180 | for operation in &self.operations { 181 | // Apply the gate to the state 182 | operation.gate.apply(initial_state); 183 | 184 | // Apply noise if enabled 185 | if apply_noise { 186 | for noise_model in &self.noise_models { 187 | noise_model.apply(initial_state); 188 | } 189 | } 190 | } 191 | 192 | initial_state 193 | } 194 | 195 | /// Creates and executes the circuit on a new zero state, with noise 196 | pub fn execute_and_measure_with_noise(&self) -> Vec { 197 | let mut state = State::zero_state(self.num_qubits); 198 | self.execute(&mut state); 199 | 200 | // Make a copy of the state for parallel measurements 201 | let state_copy = state.clone(); 202 | 203 | // Measure each qubit in parallel 204 | (0..self.num_qubits) 205 | .into_par_iter() 206 | .map(|i| { 207 | let mut local_state = state_copy.clone(); 208 | local_state.measure_qubit(i) 209 | }) 210 | .collect() 211 | } 212 | } 213 | 214 | /// Helper function for tensor product of matrices 215 | 216 | impl fmt::Debug for Circuit { 217 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 218 | let circuit_name = self.name.as_deref().unwrap_or("Unnamed Circuit"); 219 | writeln!(f, "Circuit: {} ({} qubits)", circuit_name, self.num_qubits)?; 220 | 221 | for (i, op) in self.operations.iter().enumerate() { 222 | writeln!(f, "{}: {:?}", i, op)?; 223 | } 224 | 225 | Ok(()) 226 | } 227 | } 228 | 229 | // Implementation for the Clone trait for Circuit 230 | impl Clone for Circuit { 231 | fn clone(&self) -> Self { 232 | let mut cloned = Circuit::new(self.num_qubits); 233 | cloned.name = self.name.clone(); 234 | 235 | for op in &self.operations { 236 | cloned.operations.push(Operation { 237 | gate: op.gate.clone(), 238 | qubits: op.qubits.clone(), 239 | name: op.name.clone(), 240 | }); 241 | } 242 | 243 | cloned 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/circuits/single_qubit.rs: -------------------------------------------------------------------------------- 1 | use super::utils::tensor_product; 2 | use super::Circuit; 3 | use crate::gates::{h_gate, rx_gate, ry_gate, rz_gate, s_gate, t_gate, x_gate, y_gate, z_gate}; 4 | use ndarray::Array2; 5 | use num_complex::Complex64; 6 | 7 | impl Circuit { 8 | /// Applies a single-qubit gate to a specific qubit in the circuit. 9 | /// This properly handles the expansion to the full system dimension. 10 | pub fn add_single_qubit_gate( 11 | &mut self, 12 | gate_matrix: Array2, 13 | target_qubit: usize, 14 | name: &str, 15 | ) { 16 | assert_eq!( 17 | gate_matrix.shape(), 18 | &[2, 2], 19 | "Gate must be a 2x2 matrix for single-qubit operation" 20 | ); 21 | assert!( 22 | target_qubit < self.num_qubits, 23 | "Target qubit index out of range" 24 | ); 25 | 26 | // Identity matrix 27 | let identity = Array2::from_shape_vec( 28 | (2, 2), 29 | vec![ 30 | Complex64::new(1.0, 0.0), 31 | Complex64::new(0.0, 0.0), 32 | Complex64::new(0.0, 0.0), 33 | Complex64::new(1.0, 0.0), 34 | ], 35 | ) 36 | .unwrap(); 37 | 38 | // Start with the appropriate matrix based on the target qubit 39 | let mut full_matrix: Array2; 40 | 41 | if target_qubit == 0 { 42 | // For qubit 0, start with the gate matrix 43 | full_matrix = gate_matrix.clone(); 44 | } else { 45 | // For other qubits, start with identity 46 | full_matrix = identity.clone(); 47 | } 48 | 49 | // Tensor product with identity or gate for each remaining qubit 50 | for i in 1..self.num_qubits { 51 | if i == target_qubit { 52 | full_matrix = tensor_product(&full_matrix, &gate_matrix); 53 | } else { 54 | full_matrix = tensor_product(&full_matrix, &identity); 55 | } 56 | } 57 | 58 | // Add the expanded gate to the circuit 59 | self.add_matrix_gate(full_matrix, (0..self.num_qubits).collect(), name); 60 | } 61 | 62 | /// Adds a Pauli-X (NOT) gate to the circuit 63 | pub fn x(&mut self, qubit: usize) -> &mut Self { 64 | let x = x_gate(); 65 | self.add_single_qubit_gate(x.matrix, qubit, "X"); 66 | self 67 | } 68 | 69 | /// Adds a Pauli-Y gate to the circuit 70 | pub fn y(&mut self, qubit: usize) -> &mut Self { 71 | let y = y_gate(); 72 | self.add_single_qubit_gate(y.matrix, qubit, "Y"); 73 | self 74 | } 75 | 76 | /// Adds a Pauli-Z gate to the circuit 77 | pub fn z(&mut self, qubit: usize) -> &mut Self { 78 | let z = z_gate(); 79 | self.add_single_qubit_gate(z.matrix, qubit, "Z"); 80 | self 81 | } 82 | 83 | /// Adds a Hadamard gate to the circuit 84 | pub fn h(&mut self, qubit: usize) -> &mut Self { 85 | let h = h_gate(); 86 | self.add_single_qubit_gate(h.matrix, qubit, "H"); 87 | self 88 | } 89 | 90 | /// Adds an S gate to the circuit 91 | pub fn s(&mut self, qubit: usize) -> &mut Self { 92 | let s = s_gate(); 93 | self.add_single_qubit_gate(s.matrix, qubit, "S"); 94 | self 95 | } 96 | 97 | /// Adds a T gate to the circuit 98 | pub fn t(&mut self, qubit: usize) -> &mut Self { 99 | let t = t_gate(); 100 | self.add_single_qubit_gate(t.matrix, qubit, "T"); 101 | self 102 | } 103 | 104 | /// Adds an Rx gate to the circuit 105 | pub fn rx(&mut self, qubit: usize, theta: f64) -> &mut Self { 106 | let rx = rx_gate(theta); 107 | self.add_single_qubit_gate(rx.matrix, qubit, &format!("RX({})", theta)); 108 | self 109 | } 110 | 111 | /// Adds an Ry gate to the circuit 112 | pub fn ry(&mut self, qubit: usize, theta: f64) -> &mut Self { 113 | let ry = ry_gate(theta); 114 | self.add_single_qubit_gate(ry.matrix, qubit, &format!("RY({})", theta)); 115 | self 116 | } 117 | 118 | /// Adds an Rz gate to the circuit 119 | pub fn rz(&mut self, qubit: usize, theta: f64) -> &mut Self { 120 | let rz = rz_gate(theta); 121 | self.add_single_qubit_gate(rz.matrix, qubit, &format!("RZ({})", theta)); 122 | self 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/circuits/three_qubits.rs: -------------------------------------------------------------------------------- 1 | use super::Circuit; 2 | use crate::gates::optimized::ToffoliGate; 3 | 4 | impl Circuit { 5 | /// Adds a Toffoli gate to the circuit - OPTIMIZED 6 | pub fn toffoli(&mut self, control1: usize, control2: usize, target: usize) -> &mut Self { 7 | assert!( 8 | control1 < self.num_qubits && control2 < self.num_qubits && target < self.num_qubits, 9 | "Qubit indices out of range" 10 | ); 11 | assert!( 12 | control1 != control2 && control1 != target && control2 != target, 13 | "Control and target qubits must be different" 14 | ); 15 | 16 | let gate = ToffoliGate { 17 | control1, 18 | control2, 19 | target, 20 | num_qubits: self.num_qubits, 21 | }; 22 | 23 | self.add_operation(gate, vec![control1, control2, target], "Toffoli"); 24 | self 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/circuits/two_qubits.rs: -------------------------------------------------------------------------------- 1 | use super::Circuit; 2 | use crate::gates::optimized::{CNOTGate, CZGate, SWAPGate}; 3 | use ndarray::Array2; 4 | use num_complex::Complex64; 5 | 6 | // Import rayon conditionally 7 | #[cfg(feature = "parallel")] 8 | 9 | impl Circuit { 10 | /// Adds a two-qubit gate to the circuit 11 | pub fn add_two_qubit_gate( 12 | &mut self, 13 | gate_matrix: Array2, 14 | qubit1: usize, 15 | qubit2: usize, 16 | name: &str, 17 | ) { 18 | assert_eq!( 19 | gate_matrix.shape(), 20 | &[4, 4], 21 | "Gate must be a 4x4 matrix for two-qubit operation" 22 | ); 23 | assert!( 24 | qubit1 < self.num_qubits && qubit2 < self.num_qubits, 25 | "Qubit indices out of range" 26 | ); 27 | assert!(qubit1 != qubit2, "Qubits must be different"); 28 | 29 | // Sort qubits for consistency 30 | let (q1, q2) = if qubit1 < qubit2 { 31 | (qubit1, qubit2) 32 | } else { 33 | (qubit2, qubit1) 34 | }; 35 | 36 | // For a 2-qubit system, add the matrix directly 37 | if self.num_qubits == 2 { 38 | self.add_matrix_gate(gate_matrix, vec![q1, q2], name); 39 | return; 40 | } 41 | 42 | // For larger systems, expand the matrix to act on the full Hilbert space 43 | let full_dim = 1 << self.num_qubits; // 2^n 44 | let mut full_matrix = Array2::zeros((full_dim, full_dim)); 45 | 46 | // Convert qubit indices to bit positions (MSB = 0, LSB = n-1) 47 | let bit_pos1 = self.num_qubits - 1 - q1; 48 | let bit_pos2 = self.num_qubits - 1 - q2; 49 | 50 | // Iterate through all basis states 51 | for i in 0..full_dim { 52 | // Extract bits for the target qubits 53 | let bit1 = (i >> bit_pos1) & 1; 54 | let bit2 = (i >> bit_pos2) & 1; 55 | 56 | // Calculate the 2-qubit subspace index (0-3) 57 | let subidx = (bit1 << 1) | bit2; 58 | 59 | for j in 0..4 { 60 | // Skip if matrix element is zero 61 | if gate_matrix[[j, subidx]] == Complex64::new(0.0, 0.0) { 62 | continue; 63 | } 64 | 65 | // Calculate new state after gate application 66 | let new_bit1 = (j >> 1) & 1; 67 | let new_bit2 = j & 1; 68 | 69 | // Create the new state by modifying the bits at the correct bit positions 70 | let mut new_state = i; 71 | new_state = (new_state & !(1 << bit_pos1)) | (new_bit1 << bit_pos1); 72 | new_state = (new_state & !(1 << bit_pos2)) | (new_bit2 << bit_pos2); 73 | 74 | // Set the matrix element 75 | full_matrix[[new_state, i]] = gate_matrix[[j, subidx]]; 76 | } 77 | } 78 | 79 | self.add_matrix_gate(full_matrix, (0..self.num_qubits).collect(), name); 80 | } 81 | 82 | /// Adds a CNOT gate to the circuit - OPTIMIZED 83 | pub fn cnot(&mut self, control: usize, target: usize) -> &mut Self { 84 | assert!( 85 | control < self.num_qubits && target < self.num_qubits, 86 | "Qubit indices out of range" 87 | ); 88 | assert!(control != target, "Control and target must be different"); 89 | 90 | let gate = CNOTGate { 91 | control, 92 | target, 93 | num_qubits: self.num_qubits, 94 | }; 95 | 96 | self.add_operation(gate, vec![control, target], "CNOT"); 97 | self 98 | } 99 | 100 | /// Adds a SWAP gate to the circuit - OPTIMIZED 101 | pub fn swap(&mut self, qubit1: usize, qubit2: usize) -> &mut Self { 102 | assert!( 103 | qubit1 < self.num_qubits && qubit2 < self.num_qubits, 104 | "Qubit indices out of range" 105 | ); 106 | assert!(qubit1 != qubit2, "Qubits must be different"); 107 | 108 | let gate = SWAPGate { 109 | qubit1, 110 | qubit2, 111 | num_qubits: self.num_qubits, 112 | }; 113 | 114 | self.add_operation(gate, vec![qubit1, qubit2], "SWAP"); 115 | self 116 | } 117 | 118 | /// Adds a CZ gate to the circuit - OPTIMIZED 119 | pub fn cz(&mut self, control: usize, target: usize) -> &mut Self { 120 | assert!( 121 | control < self.num_qubits && target < self.num_qubits, 122 | "Qubit indices out of range" 123 | ); 124 | assert!(control != target, "Control and target must be different"); 125 | 126 | let gate = CZGate { 127 | control, 128 | target, 129 | num_qubits: self.num_qubits, 130 | }; 131 | 132 | self.add_operation(gate, vec![control, target], "CZ"); 133 | self 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/circuits/utils.rs: -------------------------------------------------------------------------------- 1 | use ndarray::Array2; 2 | use num_complex::Complex64; 3 | 4 | // Import rayon conditionally 5 | #[cfg(feature = "parallel")] 6 | use rayon::prelude::*; 7 | 8 | /// Helper function for tensor product of matrices 9 | /// Automatically uses parallel execution when the "parallel" feature is enabled 10 | /// and matrices are large enough to benefit from it 11 | pub fn tensor_product(a: &Array2, b: &Array2) -> Array2 { 12 | let a_shape = a.shape(); 13 | let b_shape = b.shape(); 14 | let result_shape = [a_shape[0] * b_shape[0], a_shape[1] * b_shape[1]]; 15 | use std::sync::{Arc, Mutex}; 16 | let result = Arc::new(Mutex::new(Array2::zeros(result_shape))); 17 | 18 | // Use parallel execution for larger matrices where it's beneficial 19 | #[cfg(feature = "parallel")] 20 | if result_shape[0] * result_shape[1] > 256 { 21 | // Process each row in parallel 22 | (0..result_shape[0]).into_par_iter().for_each(|i| { 23 | let a_i = i / b_shape[0]; 24 | for j in 0..result_shape[1] { 25 | let a_j = j / b_shape[1]; 26 | let b_i = i % b_shape[0]; 27 | let b_j = j % b_shape[1]; 28 | let mut result_lock = result.lock().unwrap(); 29 | result_lock[[i, j]] = a[[a_i, a_j]] * b[[b_i, b_j]]; 30 | } 31 | }); 32 | 33 | return Arc::try_unwrap(result).unwrap().into_inner().unwrap(); 34 | } 35 | 36 | // Sequential implementation for smaller matrices or when parallel feature is disabled 37 | for i in 0..a_shape[0] { 38 | for j in 0..a_shape[1] { 39 | for k in 0..b_shape[0] { 40 | for l in 0..b_shape[1] { 41 | result.lock().unwrap()[[i * b_shape[0] + k, j * b_shape[1] + l]] = 42 | a[[i, j]] * b[[k, l]]; 43 | } 44 | } 45 | } 46 | } 47 | 48 | Arc::try_unwrap(result).unwrap().into_inner().unwrap() 49 | } 50 | -------------------------------------------------------------------------------- /src/gates/mod.rs: -------------------------------------------------------------------------------- 1 | // This file defines the gates module for the quantum computing library. 2 | // It exports types and functions for creating and manipulating various quantum gates. 3 | 4 | use crate::states::State; 5 | use ndarray::Array2; 6 | use num_complex::Complex64; 7 | use std::f64::consts::SQRT_2; 8 | 9 | /// Trait for quantum gates. 10 | pub trait Gate: Send + Sync { 11 | fn apply(&self, state: &mut State); 12 | } 13 | 14 | /// Implementation of a generic matrix gate. 15 | #[derive(Clone)] 16 | pub struct MatrixGate { 17 | pub matrix: Array2, 18 | } 19 | 20 | impl Gate for MatrixGate { 21 | fn apply(&self, state: &mut State) { 22 | // Check matrix dimensions 23 | assert_eq!( 24 | self.matrix.shape()[1], 25 | state.vector.len(), 26 | "Matrix columns must match state dimension" 27 | ); 28 | 29 | state.vector = self.matrix.dot(&state.vector); 30 | state.normalize(); 31 | } 32 | } 33 | 34 | // Optimized gate implementations 35 | pub mod optimized { 36 | use super::*; 37 | 38 | /// Optimized CNOT gate that directly manipulates state vector 39 | pub struct CNOTGate { 40 | pub control: usize, 41 | pub target: usize, 42 | pub num_qubits: usize, 43 | } 44 | 45 | impl Gate for CNOTGate { 46 | fn apply(&self, state: &mut State) { 47 | let n = state.num_qubits; 48 | let control_bit = n - 1 - self.control; 49 | let target_bit = n - 1 - self.target; 50 | 51 | let control_mask = 1 << control_bit; 52 | let target_mask = 1 << target_bit; 53 | 54 | let full_dim = 1 << n; 55 | let vector_slice = state.vector.as_slice_mut().unwrap(); 56 | 57 | // For each basis state where control=1 and target=0, swap with target=1 58 | for i in 0..full_dim { 59 | if (i & control_mask) != 0 && (i & target_mask) == 0 { 60 | let j = i ^ target_mask; // Flip target bit 61 | vector_slice.swap(i, j); 62 | } 63 | } 64 | } 65 | } 66 | 67 | /// Optimized SWAP gate 68 | pub struct SWAPGate { 69 | pub qubit1: usize, 70 | pub qubit2: usize, 71 | pub num_qubits: usize, 72 | } 73 | 74 | impl Gate for SWAPGate { 75 | fn apply(&self, state: &mut State) { 76 | let n = state.num_qubits; 77 | let bit1 = n - 1 - self.qubit1; 78 | let bit2 = n - 1 - self.qubit2; 79 | 80 | let mask1 = 1 << bit1; 81 | let mask2 = 1 << bit2; 82 | 83 | let full_dim = 1 << n; 84 | let vector_slice = state.vector.as_slice_mut().unwrap(); 85 | 86 | // For each basis state where bits differ, swap with the state where bits are swapped 87 | for i in 0..full_dim { 88 | let b1 = (i & mask1) != 0; 89 | let b2 = (i & mask2) != 0; 90 | 91 | // Only process if bits differ and we haven't processed this pair yet 92 | if b1 && !b2 { 93 | let j = i ^ mask1 ^ mask2; // Flip both bits 94 | vector_slice.swap(i, j); 95 | } 96 | } 97 | } 98 | } 99 | 100 | /// Optimized CZ gate 101 | pub struct CZGate { 102 | pub control: usize, 103 | pub target: usize, 104 | pub num_qubits: usize, 105 | } 106 | 107 | impl Gate for CZGate { 108 | fn apply(&self, state: &mut State) { 109 | let n = state.num_qubits; 110 | let control_bit = n - 1 - self.control; 111 | let target_bit = n - 1 - self.target; 112 | 113 | let control_mask = 1 << control_bit; 114 | let target_mask = 1 << target_bit; 115 | let both_mask = control_mask | target_mask; 116 | 117 | let full_dim = 1 << n; 118 | let vector_slice = state.vector.as_slice_mut().unwrap(); 119 | 120 | // Apply -1 phase where both control and target are 1 121 | for i in 0..full_dim { 122 | if (i & both_mask) == both_mask { 123 | vector_slice[i] = -vector_slice[i]; 124 | } 125 | } 126 | } 127 | } 128 | 129 | /// Optimized Controlled-Phase (CP) gate: applies exp(i * angle) when control=1 and target=1 130 | pub struct ControlledPhaseGate { 131 | pub control: usize, 132 | pub target: usize, 133 | pub angle: f64, 134 | pub num_qubits: usize, 135 | } 136 | 137 | impl Gate for ControlledPhaseGate { 138 | fn apply(&self, state: &mut State) { 139 | let n = state.num_qubits; 140 | let control_bit = n - 1 - self.control; 141 | let target_bit = n - 1 - self.target; 142 | 143 | let control_mask = 1 << control_bit; 144 | let target_mask = 1 << target_bit; 145 | let both_mask = control_mask | target_mask; 146 | 147 | let full_dim = 1 << n; 148 | let vector_slice = state.vector.as_slice_mut().unwrap(); 149 | let phase = Complex64::from_polar(1.0, self.angle); 150 | 151 | // Parallel path when enabled and dimension is large 152 | #[cfg(feature = "parallel")] 153 | { 154 | if full_dim > 1024 { 155 | use rayon::prelude::*; 156 | 157 | vector_slice 158 | .par_iter_mut() 159 | .enumerate() 160 | .filter(|(i, _)| (*i & both_mask) == both_mask) 161 | .for_each(|(_, val)| { 162 | *val = *val * phase; 163 | }); 164 | return; 165 | } 166 | } 167 | 168 | // Sequential fallback 169 | for i in 0..full_dim { 170 | if (i & both_mask) == both_mask { 171 | vector_slice[i] = vector_slice[i] * phase; 172 | } 173 | } 174 | } 175 | } 176 | 177 | /// Optimized Toffoli gate 178 | pub struct ToffoliGate { 179 | pub control1: usize, 180 | pub control2: usize, 181 | pub target: usize, 182 | pub num_qubits: usize, 183 | } 184 | 185 | impl Gate for ToffoliGate { 186 | fn apply(&self, state: &mut State) { 187 | let n = state.num_qubits; 188 | let c1_bit = n - 1 - self.control1; 189 | let c2_bit = n - 1 - self.control2; 190 | let target_bit = n - 1 - self.target; 191 | 192 | let c1_mask = 1 << c1_bit; 193 | let c2_mask = 1 << c2_bit; 194 | let target_mask = 1 << target_bit; 195 | let controls_mask = c1_mask | c2_mask; 196 | 197 | let full_dim = 1 << n; 198 | let vector_slice = state.vector.as_slice_mut().unwrap(); 199 | 200 | // For each basis state where both controls=1 and target=0, swap with target=1 201 | for i in 0..full_dim { 202 | if (i & controls_mask) == controls_mask && (i & target_mask) == 0 { 203 | let j = i ^ target_mask; // Flip target bit 204 | vector_slice.swap(i, j); 205 | } 206 | } 207 | } 208 | } 209 | } 210 | 211 | // Common single-qubit gates 212 | pub mod single_qubit { 213 | use super::*; 214 | 215 | /// Creates a Pauli-X (NOT) gate 216 | pub fn x_gate() -> MatrixGate { 217 | let matrix = Array2::from_shape_vec( 218 | (2, 2), 219 | vec![ 220 | Complex64::new(0.0, 0.0), 221 | Complex64::new(1.0, 0.0), 222 | Complex64::new(1.0, 0.0), 223 | Complex64::new(0.0, 0.0), 224 | ], 225 | ) 226 | .unwrap(); 227 | 228 | MatrixGate { matrix } 229 | } 230 | 231 | /// Creates a Pauli-Y gate 232 | pub fn y_gate() -> MatrixGate { 233 | let matrix = Array2::from_shape_vec( 234 | (2, 2), 235 | vec![ 236 | Complex64::new(0.0, 0.0), 237 | Complex64::new(0.0, -1.0), 238 | Complex64::new(0.0, 1.0), 239 | Complex64::new(0.0, 0.0), 240 | ], 241 | ) 242 | .unwrap(); 243 | 244 | MatrixGate { matrix } 245 | } 246 | 247 | /// Creates a Pauli-Z gate 248 | pub fn z_gate() -> MatrixGate { 249 | let matrix = Array2::from_shape_vec( 250 | (2, 2), 251 | vec![ 252 | Complex64::new(1.0, 0.0), 253 | Complex64::new(0.0, 0.0), 254 | Complex64::new(0.0, 0.0), 255 | Complex64::new(-1.0, 0.0), 256 | ], 257 | ) 258 | .unwrap(); 259 | 260 | MatrixGate { matrix } 261 | } 262 | 263 | /// Creates a Hadamard gate 264 | pub fn h_gate() -> MatrixGate { 265 | let matrix = Array2::from_shape_vec( 266 | (2, 2), 267 | vec![ 268 | Complex64::new(1.0 / SQRT_2, 0.0), 269 | Complex64::new(1.0 / SQRT_2, 0.0), 270 | Complex64::new(1.0 / SQRT_2, 0.0), 271 | Complex64::new(-1.0 / SQRT_2, 0.0), 272 | ], 273 | ) 274 | .unwrap(); 275 | 276 | MatrixGate { matrix } 277 | } 278 | 279 | /// Creates an S gate (phase gate) 280 | pub fn s_gate() -> MatrixGate { 281 | let matrix = Array2::from_shape_vec( 282 | (2, 2), 283 | vec![ 284 | Complex64::new(1.0, 0.0), 285 | Complex64::new(0.0, 0.0), 286 | Complex64::new(0.0, 0.0), 287 | Complex64::new(0.0, 1.0), 288 | ], 289 | ) 290 | .unwrap(); 291 | 292 | MatrixGate { matrix } 293 | } 294 | 295 | /// Creates a T gate (π/8 gate) 296 | pub fn t_gate() -> MatrixGate { 297 | let matrix = Array2::from_shape_vec( 298 | (2, 2), 299 | vec![ 300 | Complex64::new(1.0, 0.0), 301 | Complex64::new(0.0, 0.0), 302 | Complex64::new(0.0, 0.0), 303 | Complex64::new(1.0 / SQRT_2, 1.0 / SQRT_2), 304 | ], 305 | ) 306 | .unwrap(); 307 | 308 | MatrixGate { matrix } 309 | } 310 | 311 | /// Creates a rotation gate around the X axis 312 | pub fn rx_gate(theta: f64) -> MatrixGate { 313 | let cos = (theta / 2.0).cos(); 314 | let sin = (theta / 2.0).sin(); 315 | 316 | let matrix = Array2::from_shape_vec( 317 | (2, 2), 318 | vec![ 319 | Complex64::new(cos, 0.0), 320 | Complex64::new(0.0, -sin), 321 | Complex64::new(0.0, -sin), 322 | Complex64::new(cos, 0.0), 323 | ], 324 | ) 325 | .unwrap(); 326 | 327 | MatrixGate { matrix } 328 | } 329 | 330 | /// Creates a rotation gate around the Y axis 331 | pub fn ry_gate(theta: f64) -> MatrixGate { 332 | let cos = (theta / 2.0).cos(); 333 | let sin = (theta / 2.0).sin(); 334 | 335 | let matrix = Array2::from_shape_vec( 336 | (2, 2), 337 | vec![ 338 | Complex64::new(cos, 0.0), 339 | Complex64::new(-sin, 0.0), 340 | Complex64::new(sin, 0.0), 341 | Complex64::new(cos, 0.0), 342 | ], 343 | ) 344 | .unwrap(); 345 | 346 | MatrixGate { matrix } 347 | } 348 | 349 | /// Creates a rotation gate around the Z axis 350 | pub fn rz_gate(theta: f64) -> MatrixGate { 351 | let matrix = Array2::from_shape_vec( 352 | (2, 2), 353 | vec![ 354 | Complex64::new((theta / 2.0).cos(), -(theta / 2.0).sin()), 355 | Complex64::new(0.0, 0.0), 356 | Complex64::new(0.0, 0.0), 357 | Complex64::new((theta / 2.0).cos(), (theta / 2.0).sin()), 358 | ], 359 | ) 360 | .unwrap(); 361 | 362 | MatrixGate { matrix } 363 | } 364 | } 365 | 366 | // Multi-qubit gates (kept for compatibility, but not used internally) 367 | pub mod multi_qubit { 368 | use super::*; 369 | 370 | /// Creates a CNOT (Controlled-NOT) gate matrix 371 | pub fn cnot_gate() -> MatrixGate { 372 | let matrix = Array2::from_shape_vec( 373 | (4, 4), 374 | vec![ 375 | Complex64::new(1.0, 0.0), 376 | Complex64::new(0.0, 0.0), 377 | Complex64::new(0.0, 0.0), 378 | Complex64::new(0.0, 0.0), 379 | Complex64::new(0.0, 0.0), 380 | Complex64::new(1.0, 0.0), 381 | Complex64::new(0.0, 0.0), 382 | Complex64::new(0.0, 0.0), 383 | Complex64::new(0.0, 0.0), 384 | Complex64::new(0.0, 0.0), 385 | Complex64::new(0.0, 0.0), 386 | Complex64::new(1.0, 0.0), 387 | Complex64::new(0.0, 0.0), 388 | Complex64::new(0.0, 0.0), 389 | Complex64::new(1.0, 0.0), 390 | Complex64::new(0.0, 0.0), 391 | ], 392 | ) 393 | .unwrap(); 394 | 395 | MatrixGate { matrix } 396 | } 397 | 398 | /// Creates a SWAP gate matrix 399 | pub fn swap_gate() -> MatrixGate { 400 | let matrix = Array2::from_shape_vec( 401 | (4, 4), 402 | vec![ 403 | Complex64::new(1.0, 0.0), 404 | Complex64::new(0.0, 0.0), 405 | Complex64::new(0.0, 0.0), 406 | Complex64::new(0.0, 0.0), 407 | Complex64::new(0.0, 0.0), 408 | Complex64::new(0.0, 0.0), 409 | Complex64::new(1.0, 0.0), 410 | Complex64::new(0.0, 0.0), 411 | Complex64::new(0.0, 0.0), 412 | Complex64::new(1.0, 0.0), 413 | Complex64::new(0.0, 0.0), 414 | Complex64::new(0.0, 0.0), 415 | Complex64::new(0.0, 0.0), 416 | Complex64::new(0.0, 0.0), 417 | Complex64::new(0.0, 0.0), 418 | Complex64::new(1.0, 0.0), 419 | ], 420 | ) 421 | .unwrap(); 422 | 423 | MatrixGate { matrix } 424 | } 425 | 426 | /// Creates a controlled-Z gate matrix 427 | pub fn cz_gate() -> MatrixGate { 428 | let matrix = Array2::from_shape_vec( 429 | (4, 4), 430 | vec![ 431 | Complex64::new(1.0, 0.0), 432 | Complex64::new(0.0, 0.0), 433 | Complex64::new(0.0, 0.0), 434 | Complex64::new(0.0, 0.0), 435 | Complex64::new(0.0, 0.0), 436 | Complex64::new(1.0, 0.0), 437 | Complex64::new(0.0, 0.0), 438 | Complex64::new(0.0, 0.0), 439 | Complex64::new(0.0, 0.0), 440 | Complex64::new(0.0, 0.0), 441 | Complex64::new(1.0, 0.0), 442 | Complex64::new(0.0, 0.0), 443 | Complex64::new(0.0, 0.0), 444 | Complex64::new(0.0, 0.0), 445 | Complex64::new(0.0, 0.0), 446 | Complex64::new(-1.0, 0.0), 447 | ], 448 | ) 449 | .unwrap(); 450 | 451 | MatrixGate { matrix } 452 | } 453 | 454 | /// Creates a Toffoli (CCNOT) gate matrix 455 | pub fn toffoli_gate() -> MatrixGate { 456 | let mut matrix = Array2::from_elem((8, 8), Complex64::new(0.0, 0.0)); 457 | 458 | // Diagonal elements 459 | for i in 0..6 { 460 | matrix[[i, i]] = Complex64::new(1.0, 0.0); 461 | } 462 | 463 | // Off-diagonal elements for the controlled-not part 464 | matrix[[6, 7]] = Complex64::new(1.0, 0.0); 465 | matrix[[7, 6]] = Complex64::new(1.0, 0.0); 466 | 467 | MatrixGate { matrix } 468 | } 469 | } 470 | 471 | // Convenience re-exports 472 | pub use multi_qubit::*; 473 | pub use optimized::*; 474 | pub use single_qubit::*; 475 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! logosq: A quantum computing library in Rust 2 | //! 3 | //! This library provides tools for quantum computing simulation, including 4 | //! quantum states, gates, circuits, algorithms, and noise simulation. 5 | 6 | // Keep the modules 7 | pub mod algorithms; 8 | pub mod circuits; 9 | pub mod gates; 10 | pub mod noise; 11 | pub mod prelude; 12 | pub mod states; 13 | pub mod utils; 14 | pub mod vis; 15 | 16 | // Re-export common types at the crate root 17 | pub use gates::{Gate, MatrixGate}; 18 | pub use states::State; 19 | 20 | // Commonly used dependencies re-exported for convenience 21 | pub use ndarray; 22 | pub use num_complex; 23 | -------------------------------------------------------------------------------- /src/noise/mod.rs: -------------------------------------------------------------------------------- 1 | // This file defines the noise module for simulating noise in quantum systems. 2 | // It exports types and functions for adding noise to quantum operations. 3 | 4 | use crate::states::State; 5 | use ndarray::Array1; 6 | use num_complex::Complex64; 7 | use rand::Rng; 8 | 9 | /// Trait for quantum noise models 10 | pub trait NoiseModel { 11 | /// Apply noise to a quantum state 12 | fn apply(&self, state: &mut State); 13 | 14 | /// Return a descriptive name for this noise model 15 | fn name(&self) -> String; 16 | } 17 | 18 | /// Represents the common parameters for noise models 19 | pub struct NoiseParams { 20 | /// The probability of an error occurring (0.0 to 1.0) 21 | pub error_probability: f64, 22 | 23 | /// Which qubits this noise applies to (None means all qubits) 24 | pub target_qubits: Option>, 25 | } 26 | 27 | impl NoiseParams { 28 | pub fn new(error_probability: f64) -> Self { 29 | Self { 30 | error_probability, 31 | target_qubits: None, 32 | } 33 | } 34 | 35 | pub fn with_target_qubits(mut self, qubits: Vec) -> Self { 36 | self.target_qubits = Some(qubits); 37 | self 38 | } 39 | } 40 | 41 | /// Depolarizing noise model - replaces the state with a completely mixed state with probability p 42 | pub struct DepolarizingNoise { 43 | params: NoiseParams, 44 | } 45 | 46 | impl DepolarizingNoise { 47 | pub fn new(error_probability: f64) -> Self { 48 | Self { 49 | params: NoiseParams::new(error_probability), 50 | } 51 | } 52 | 53 | pub fn with_target_qubits(self, qubits: Vec) -> Self { 54 | Self { 55 | params: self.params.with_target_qubits(qubits), 56 | } 57 | } 58 | } 59 | 60 | impl NoiseModel for DepolarizingNoise { 61 | fn apply(&self, state: &mut State) { 62 | let mut rng = rand::thread_rng(); 63 | 64 | // Apply noise to specified qubits or all qubits 65 | let target_qubits = match &self.params.target_qubits { 66 | Some(qubits) => qubits.clone(), 67 | None => (0..state.num_qubits).collect(), 68 | }; 69 | 70 | for &qubit in &target_qubits { 71 | if rng.gen::() < self.params.error_probability { 72 | // Apply a random Pauli error (X, Y, or Z) 73 | let error_type = rng.gen_range(0..3); 74 | match error_type { 75 | 0 => apply_x_error(state, qubit), // X error 76 | 1 => apply_y_error(state, qubit), // Y error 77 | 2 => apply_z_error(state, qubit), // Z error 78 | _ => unreachable!(), 79 | } 80 | } 81 | } 82 | } 83 | 84 | fn name(&self) -> String { 85 | format!("Depolarizing(p={})", self.params.error_probability) 86 | } 87 | } 88 | 89 | /// Amplitude damping noise - models energy dissipation (e.g., |1⟩ → |0⟩ transitions) 90 | pub struct AmplitudeDampingNoise { 91 | params: NoiseParams, 92 | } 93 | 94 | impl AmplitudeDampingNoise { 95 | pub fn new(error_probability: f64) -> Self { 96 | Self { 97 | params: NoiseParams::new(error_probability), 98 | } 99 | } 100 | 101 | pub fn with_target_qubits(self, qubits: Vec) -> Self { 102 | Self { 103 | params: self.params.with_target_qubits(qubits), 104 | } 105 | } 106 | } 107 | 108 | impl NoiseModel for AmplitudeDampingNoise { 109 | fn apply(&self, state: &mut State) { 110 | let dimension = state.vector.len(); 111 | 112 | // Apply noise to specified qubits or all qubits 113 | let target_qubits = match &self.params.target_qubits { 114 | Some(qubits) => qubits.clone(), 115 | None => (0..state.num_qubits).collect(), 116 | }; 117 | 118 | // Create a new vector to store the updated state 119 | let mut new_vector = Array1::::zeros(dimension); 120 | 121 | for &qubit in &target_qubits { 122 | let gamma = self.params.error_probability; 123 | let sqrt_gamma = gamma.sqrt(); 124 | 125 | // Apply the Kraus operators for amplitude damping 126 | // K0 = |0⟩⟨0| + sqrt(1-γ)|1⟩⟨1| 127 | // K1 = sqrt(γ)|0⟩⟨1| 128 | 129 | for i in 0..dimension { 130 | if (i & (1 << qubit)) == 0 { 131 | // If qubit is |0⟩, apply K0 part |0⟩⟨0| 132 | new_vector[i] += state.vector[i]; 133 | } else { 134 | // If qubit is |1⟩, apply K0 part sqrt(1-γ)|1⟩⟨1| 135 | new_vector[i] += state.vector[i] * Complex64::new((1.0 - gamma).sqrt(), 0.0); 136 | 137 | // Apply K1 = sqrt(γ)|0⟩⟨1| 138 | let j = i & !(1 << qubit); // Flip qubit from |1⟩ to |0⟩ 139 | new_vector[j] += state.vector[i] * Complex64::new(sqrt_gamma, 0.0); 140 | } 141 | } 142 | } 143 | 144 | state.vector = new_vector; 145 | state.normalize(); 146 | } 147 | 148 | fn name(&self) -> String { 149 | format!("AmplitudeDamping(p={})", self.params.error_probability) 150 | } 151 | } 152 | 153 | /// Phase damping noise - causes phase decoherence without energy dissipation 154 | pub struct PhaseDampingNoise { 155 | params: NoiseParams, 156 | } 157 | 158 | impl PhaseDampingNoise { 159 | pub fn new(error_probability: f64) -> Self { 160 | Self { 161 | params: NoiseParams::new(error_probability), 162 | } 163 | } 164 | 165 | pub fn with_target_qubits(self, qubits: Vec) -> Self { 166 | Self { 167 | params: self.params.with_target_qubits(qubits), 168 | } 169 | } 170 | } 171 | 172 | impl NoiseModel for PhaseDampingNoise { 173 | fn apply(&self, state: &mut State) { 174 | let dimension = state.vector.len(); 175 | 176 | // Apply noise to specified qubits or all qubits 177 | let target_qubits = match &self.params.target_qubits { 178 | Some(qubits) => qubits.clone(), 179 | None => (0..state.num_qubits).collect(), 180 | }; 181 | 182 | // Create a new vector to store the updated state 183 | let mut new_vector = ndarray::Array1::zeros(dimension); 184 | 185 | for &qubit in &target_qubits { 186 | // Phase damping Kraus operators: 187 | // K0 = |0⟩⟨0| + sqrt(1-λ)|1⟩⟨1| 188 | // K1 = sqrt(λ)|1⟩⟨1| 189 | 190 | let lambda = self.params.error_probability; 191 | let sqrt_1_minus_lambda = (1.0 - lambda).sqrt(); 192 | 193 | for i in 0..dimension { 194 | // Copy original state 195 | new_vector[i] = state.vector[i]; 196 | 197 | // If qubit is in state |1⟩, apply the phase damping 198 | if (i & (1 << qubit)) != 0 { 199 | // The amplitude is reduced by sqrt(1-λ) but phase is preserved 200 | new_vector[i] *= Complex64::new(sqrt_1_minus_lambda, 0.0); 201 | } 202 | } 203 | } 204 | 205 | // Update the state vector and normalize 206 | state.vector = new_vector; 207 | state.normalize(); 208 | } 209 | 210 | fn name(&self) -> String { 211 | format!("PhaseDamping(p={})", self.params.error_probability) 212 | } 213 | } 214 | 215 | /// Thermal relaxation noise model - combines amplitude and phase damping 216 | pub struct ThermalRelaxationNoise { 217 | /// T1 relaxation time (amplitude damping) 218 | t1: f64, 219 | 220 | /// T2 relaxation time (phase damping) 221 | t2: f64, 222 | 223 | /// Gate time (how long the gate takes to execute) 224 | gate_time: f64, 225 | 226 | /// Target qubits 227 | target_qubits: Option>, 228 | } 229 | 230 | impl ThermalRelaxationNoise { 231 | pub fn new(t1: f64, t2: f64, gate_time: f64) -> Self { 232 | // Ensure t2 <= 2*t1 (physical constraint) 233 | let t2 = t2.min(2.0 * t1); 234 | 235 | Self { 236 | t1, 237 | t2, 238 | gate_time, 239 | target_qubits: None, 240 | } 241 | } 242 | 243 | pub fn with_target_qubits(mut self, qubits: Vec) -> Self { 244 | self.target_qubits = Some(qubits); 245 | self 246 | } 247 | } 248 | 249 | impl NoiseModel for ThermalRelaxationNoise { 250 | fn apply(&self, state: &mut State) { 251 | // Calculate probabilities from relaxation times 252 | // For amplitude damping, p_a = 1 - exp(-gate_time/T1) 253 | let p_amplitude = 1.0 - (-self.gate_time / self.t1).exp(); 254 | 255 | // For pure dephasing (not including relaxation-induced dephasing) 256 | // Pure dephasing rate is calculated as 1/T_phi = 1/T2 - 1/(2*T1) 257 | let pure_dephasing_rate = if self.t2 <= 2.0 * self.t1 { 258 | 1.0 / self.t2 - 1.0 / (2.0 * self.t1) 259 | } else { 260 | // If T2 > 2*T1, which is physically impossible, default to 0 261 | 0.0 262 | }; 263 | 264 | // Phase damping probability p_p = 1 - exp(-gate_time/T_phi) 265 | let p_phase = if pure_dephasing_rate > 0.0 { 266 | 1.0 - (-self.gate_time * pure_dephasing_rate).exp() 267 | } else { 268 | 0.0 269 | }; 270 | 271 | // Target qubits for noise application 272 | let target_qubits = self 273 | .target_qubits 274 | .clone() 275 | .unwrap_or_else(|| (0..state.num_qubits).collect()); 276 | 277 | // Apply amplitude damping first 278 | if p_amplitude > 0.0 { 279 | let amplitude_noise = 280 | AmplitudeDampingNoise::new(p_amplitude).with_target_qubits(target_qubits.clone()); 281 | amplitude_noise.apply(state); 282 | } 283 | 284 | // Then apply phase damping 285 | if p_phase > 0.0 { 286 | let phase_noise = PhaseDampingNoise::new(p_phase).with_target_qubits(target_qubits); 287 | phase_noise.apply(state); 288 | } 289 | } 290 | 291 | fn name(&self) -> String { 292 | format!( 293 | "ThermalRelaxation(T1={}, T2={}, gate_time={})", 294 | self.t1, self.t2, self.gate_time 295 | ) 296 | } 297 | } 298 | 299 | /// Composite noise model - combines multiple noise models 300 | pub struct CompositeNoise { 301 | noise_models: Vec>, 302 | } 303 | 304 | impl CompositeNoise { 305 | pub fn new() -> Self { 306 | Self { 307 | noise_models: Vec::new(), 308 | } 309 | } 310 | 311 | pub fn add_noise(&mut self, noise_model: N) -> &mut Self { 312 | self.noise_models.push(Box::new(noise_model)); 313 | self 314 | } 315 | } 316 | 317 | impl NoiseModel for CompositeNoise { 318 | fn apply(&self, state: &mut State) { 319 | // Apply each noise model in sequence 320 | for noise_model in &self.noise_models { 321 | // Create a copy of the state to avoid borrow issues 322 | let mut temp_state = state.clone(); 323 | noise_model.apply(&mut temp_state); 324 | *state = temp_state; 325 | } 326 | } 327 | 328 | fn name(&self) -> String { 329 | let names: Vec = self.noise_models.iter().map(|model| model.name()).collect(); 330 | format!("Composite({})", names.join(", ")) 331 | } 332 | } 333 | 334 | // Helper functions to apply specific Pauli errors 335 | fn apply_x_error(state: &mut State, qubit: usize) { 336 | let dimension = state.vector.len(); 337 | let mut new_vector = state.vector.clone(); 338 | 339 | for i in 0..dimension { 340 | let j = i ^ (1 << qubit); // Flip the qubit bit 341 | new_vector[i] = state.vector[j]; 342 | } 343 | 344 | state.vector = new_vector; 345 | } 346 | 347 | fn apply_y_error(state: &mut State, qubit: usize) { 348 | let dimension = state.vector.len(); 349 | let mut new_vector = state.vector.clone(); 350 | let imag_i = Complex64::new(0.0, 1.0); 351 | 352 | for i in 0..dimension { 353 | let j = i ^ (1 << qubit); // Flip the qubit bit 354 | 355 | // Check if the qubit is 1 in state i 356 | if (i & (1 << qubit)) != 0 { 357 | new_vector[i] = -imag_i * state.vector[j]; 358 | } else { 359 | new_vector[i] = imag_i * state.vector[j]; 360 | } 361 | } 362 | 363 | state.vector = new_vector; 364 | } 365 | 366 | fn apply_z_error(state: &mut State, qubit: usize) { 367 | let dimension = state.vector.len(); 368 | 369 | for i in 0..dimension { 370 | // Apply phase flip if qubit is in state |1⟩ 371 | if (i & (1 << qubit)) != 0 { 372 | state.vector[i] = -state.vector[i]; 373 | } 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Common types and traits, re-exported for convenience. 2 | //! 3 | //! Users can import everything needed with: `use logosq::prelude::*;` 4 | 5 | // Re-export entire modules 6 | pub use crate::circuits; 7 | pub use crate::gates; 8 | pub use crate::states; 9 | 10 | // Re-export core types for direct access 11 | pub use crate::circuits::Circuit; 12 | pub use crate::gates::{Gate, MatrixGate}; 13 | pub use crate::states::State; 14 | 15 | // Re-export common gate functions 16 | pub use crate::gates::{ 17 | // Multi-qubit gates 18 | cnot_gate, 19 | cz_gate, 20 | h_gate, 21 | rx_gate, 22 | ry_gate, 23 | rz_gate, 24 | 25 | s_gate, 26 | swap_gate, 27 | t_gate, 28 | toffoli_gate, 29 | // Single-qubit gates 30 | x_gate, 31 | y_gate, 32 | z_gate, 33 | }; 34 | 35 | // When you implement these modules, add their exports here 36 | // pub use crate::algorithms::{Grover, QFT, QAOA}; 37 | // pub use crate::noise::NoiseModel; 38 | -------------------------------------------------------------------------------- /src/states/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::gates::Gate; 2 | use ndarray::Array1; 3 | use num_complex::Complex64; 4 | use rand::distributions::{Distribution, WeightedIndex}; 5 | use rand::thread_rng; 6 | use std::f64::consts::SQRT_2; 7 | 8 | // conditional compilation for parallel features 9 | use rayon::prelude::*; 10 | /// Represents a quantum state vector using complex amplitudes. 11 | /// 2^n amplitudes for n qubits. 12 | 13 | pub struct State { 14 | pub vector: Array1, 15 | pub num_qubits: usize, 16 | } 17 | 18 | impl State { 19 | /// Creates a new quantum state from a complex vector (auto-normalized). 20 | pub fn new(vector: Array1, num_qubits: Option) -> Self { 21 | // Calculate num_qubits before moving vector 22 | let actual_qubits = num_qubits.unwrap_or_else(|| (vector.len() as f64).log2() as usize); 23 | 24 | let mut state = State { 25 | vector, 26 | num_qubits: actual_qubits, 27 | }; 28 | state.normalize(); 29 | state 30 | } 31 | 32 | // Parallel state normalization 33 | pub fn normalize(&mut self) { 34 | let norm = self 35 | .vector 36 | .par_iter() 37 | .map(|c| c.norm_sqr()) 38 | .sum::() 39 | .sqrt(); 40 | 41 | if norm > 1e-10 { 42 | self.vector.par_mapv_inplace(|c| c / norm); 43 | } 44 | } 45 | 46 | /// Creates standard basis state |0...0⟩ 47 | pub fn zero_state(num_qubits: usize) -> Self { 48 | let size = 1 << num_qubits; 49 | let mut vector = Array1::zeros(size); 50 | vector[0] = Complex64::new(1.0, 0.0); 51 | Self { vector, num_qubits } 52 | } 53 | 54 | /// Creates standard basis state |1...1⟩ 55 | pub fn one_state(num_qubits: usize) -> Self { 56 | let size = 1 << num_qubits; 57 | let mut vector = Array1::zeros(size); 58 | vector[size - 1] = Complex64::new(1.0, 0.0); 59 | Self { vector, num_qubits } 60 | } 61 | 62 | /// Creates |+⟩ state (equal superposition) 63 | pub fn plus_state(num_qubits: usize) -> Self { 64 | let size = 1 << num_qubits; 65 | let norm = 1.0 / (size as f64).sqrt(); 66 | let vector = Array1::from_elem(size, Complex64::new(norm, 0.0)); 67 | Self { vector, num_qubits } 68 | } 69 | 70 | /// Creates Bell state (maximally entangled two-qubit state) 71 | pub fn bell_state() -> Self { 72 | let mut vector = Array1::zeros(4); 73 | vector[0] = Complex64::new(1.0 / SQRT_2, 0.0); 74 | vector[3] = Complex64::new(1.0 / SQRT_2, 0.0); 75 | Self { 76 | vector, 77 | num_qubits: 2, 78 | } 79 | } 80 | 81 | /// Applies a quantum gate to the state. 82 | pub fn apply_gate(&mut self, gate: &G) { 83 | gate.apply(self); 84 | } 85 | 86 | /// Measures the entire state, returning the observed basis state index. 87 | pub fn measure(&self) -> usize { 88 | let probs: Vec = self.vector.iter().map(|x| x.norm_sqr()).collect(); 89 | let dist = WeightedIndex::new(&probs).unwrap(); 90 | let mut rng = thread_rng(); 91 | dist.sample(&mut rng) 92 | } 93 | 94 | /// Measures a specific qubit, collapsing the state. 95 | /// Returns the measurement result (0 or 1). 96 | pub fn measure_qubit(&mut self, qubit_idx: usize) -> usize { 97 | assert!(qubit_idx < self.num_qubits, "Qubit index out of range"); 98 | 99 | let mask = 1 << qubit_idx; 100 | let mut prob_one = 0.0; 101 | 102 | // Calculate probability of measuring |1⟩ 103 | for (i, amp) in self.vector.iter().enumerate() { 104 | if (i & mask) != 0 { 105 | prob_one += amp.norm_sqr(); 106 | } 107 | } 108 | 109 | // Determine measurement outcome 110 | let result = if rand::random::() < prob_one { 111 | 1 112 | } else { 113 | 0 114 | }; 115 | 116 | // Project state according to measurement 117 | let projection_value = if result == 1 { mask } else { 0 }; 118 | let mut new_vector = Array1::zeros(self.vector.len()); 119 | 120 | for (i, amp) in self.vector.iter().enumerate() { 121 | if (i & mask) == projection_value { 122 | new_vector[i] = *amp; 123 | } 124 | } 125 | 126 | self.vector = new_vector; 127 | self.normalize(); 128 | result 129 | } 130 | 131 | // Parallel measurement for multiple shots 132 | pub fn measure_shots_parallel( 133 | &self, 134 | n_shots: usize, 135 | ) -> std::collections::HashMap { 136 | let results: Vec = (0..n_shots) 137 | .into_par_iter() 138 | .map(|_| self.measure()) 139 | .collect(); 140 | 141 | let mut counts = std::collections::HashMap::new(); 142 | for result in results { 143 | *counts.entry(result).or_insert(0) += 1; 144 | } 145 | counts 146 | } 147 | 148 | /// Calculates probability of measuring a particular basis state. 149 | pub fn probability(&self, basis_state: usize) -> f64 { 150 | if basis_state >= self.vector.len() { 151 | return 0.0; 152 | } 153 | self.vector[basis_state].norm_sqr() 154 | } 155 | 156 | /// Tensor product with another state. 157 | pub fn tensor_product(&self, other: &State) -> State { 158 | let n1 = self.vector.len(); 159 | let n2 = other.vector.len(); 160 | let mut result = Array1::zeros(n1 * n2); 161 | 162 | for i in 0..n1 { 163 | for j in 0..n2 { 164 | result[i * n2 + j] = self.vector[i] * other.vector[j]; 165 | } 166 | } 167 | 168 | State { 169 | vector: result, 170 | num_qubits: self.num_qubits + other.num_qubits, 171 | } 172 | } 173 | 174 | /// A simple print function to display the quantum state 175 | pub fn print(&self) -> String { 176 | let mut output = format!( 177 | "State: {} qubit{}\n", 178 | self.num_qubits, 179 | if self.num_qubits != 1 { "s" } else { "" } 180 | ); 181 | 182 | // Show states with non-negligible probability 183 | for (i, amplitude) in self.vector.iter().enumerate() { 184 | let probability = amplitude.norm_sqr(); 185 | if probability > 1e-10 { 186 | let basis = format!("|{:0width$b}⟩", i, width = self.num_qubits); 187 | output.push_str(&format!( 188 | "{} : {:.4}{:+.4}i (p={:.4})\n", 189 | basis, amplitude.re, amplitude.im, probability 190 | )); 191 | } 192 | } 193 | 194 | output 195 | } 196 | 197 | // Parallel state inner product 198 | pub fn inner_product_parallel(&self, other: &State) -> Complex64 { 199 | use ndarray::Zip; 200 | assert_eq!(self.vector.len(), other.vector.len()); 201 | 202 | Zip::from(&self.vector) 203 | .and(&other.vector) 204 | .par_map_collect(|a, b| a.conj() * b) 205 | .into_iter() 206 | .sum() 207 | } 208 | } 209 | 210 | impl Clone for State { 211 | fn clone(&self) -> Self { 212 | State { 213 | vector: self.vector.clone(), 214 | num_qubits: self.num_qubits, 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | // This file contains utility functions and types that support the other modules, such as mathematical functions and helper methods for state and gate operations. 2 | -------------------------------------------------------------------------------- /src/vis/gate.rs: -------------------------------------------------------------------------------- 1 | //! Gate visualization module (placeholder for future implementation). 2 | 3 | // Future implementation will go here 4 | -------------------------------------------------------------------------------- /src/vis/mod.rs: -------------------------------------------------------------------------------- 1 | //! Visualization module for quantum components including circuits, states, gates and noise models. 2 | 3 | pub mod circuit; 4 | 5 | // Placeholder modules for future implementation 6 | pub mod gate; 7 | pub mod state; 8 | // pub mod noise; 9 | 10 | // Re-export main visualization functions 11 | pub use circuit::{ 12 | save_svg_diagram as save_circuit_svg, svg_diagram as circuit_svg, text_diagram as circuit_text, 13 | view_circuit, 14 | }; 15 | 16 | // Main visualization function that determines the type and renders accordingly 17 | pub fn visualize(item: &T) -> String { 18 | item.visualize() 19 | } 20 | 21 | // Trait for types that can be visualized 22 | pub trait Visualizable { 23 | fn visualize(&self) -> String; 24 | fn visualize_as_svg(&self) -> String; 25 | fn save_visualization(&self, filename: &str) -> std::io::Result<()>; 26 | fn view(&self) -> std::io::Result<()>; 27 | } 28 | -------------------------------------------------------------------------------- /src/vis/state.rs: -------------------------------------------------------------------------------- 1 | //! State visualization module with 3D Bloch sphere for quantum states. 2 | 3 | use crate::states::State; 4 | use crate::vis::Visualizable; 5 | use plotters::prelude::*; 6 | use std::f64::consts::PI; 7 | use std::io::{Error, ErrorKind, Result}; 8 | 9 | impl Visualizable for State { 10 | fn visualize(&self) -> String { 11 | // Simple text-based visualization 12 | let mut output = format!( 13 | "Quantum State: {} qubit{}\n\n", 14 | self.num_qubits, 15 | if self.num_qubits != 1 { "s" } else { "" } 16 | ); 17 | 18 | // Show only states with non-negligible probability 19 | for (i, amplitude) in self.vector.iter().enumerate() { 20 | let probability = amplitude.norm_sqr(); 21 | if probability > 1e-10 { 22 | let basis = format!( 23 | "{}{}{}", 24 | "|", 25 | format!("{:0width$b}", i, width = self.num_qubits), 26 | ">" 27 | ); 28 | output.push_str(&format!( 29 | "{}: {:.4}{:+.4}i (p={:.4})\n", 30 | basis, amplitude.re, amplitude.im, probability 31 | )); 32 | } 33 | } 34 | 35 | // Add Bloch coordinates for single-qubit states 36 | if self.num_qubits == 1 { 37 | let (x, y, z) = self.calculate_bloch_coordinates(); 38 | output.push_str(&format!( 39 | "\nBloch coordinates: (x={:.4}, y={:.4}, z={:.4})\n", 40 | x, y, z 41 | )); 42 | } 43 | 44 | output 45 | } 46 | 47 | fn visualize_as_svg(&self) -> String { 48 | if self.num_qubits != 1 { 49 | return "3D Bloch sphere visualization is only available for single-qubit states." 50 | .to_string(); 51 | } 52 | 53 | "SVG visualization requires saving to a file.".to_string() 54 | } 55 | 56 | fn save_visualization(&self, filename: &str) -> Result<()> { 57 | if self.num_qubits == 1 { 58 | self.plot_bloch_sphere_3d(filename) 59 | } else { 60 | // Return an error for multi-qubit states 61 | Err(Error::new( 62 | ErrorKind::InvalidInput, 63 | "3D Bloch sphere visualization is only available for single-qubit states.", 64 | )) 65 | } 66 | } 67 | 68 | fn view(&self) -> Result<()> { 69 | let temp_file = "/tmp/quantum_state.svg"; 70 | 71 | // Handle the visualization error explicitly 72 | if let Err(e) = self.save_visualization(temp_file) { 73 | return Err(e); 74 | } 75 | 76 | // Open with system's default viewer 77 | #[cfg(target_os = "linux")] 78 | std::process::Command::new("xdg-open") 79 | .arg(temp_file) 80 | .spawn()?; 81 | 82 | #[cfg(target_os = "macos")] 83 | std::process::Command::new("open").arg(temp_file).spawn()?; 84 | 85 | #[cfg(target_os = "windows")] 86 | std::process::Command::new("cmd") 87 | .args(&["/C", "start", temp_file]) 88 | .spawn()?; 89 | 90 | Ok(()) 91 | } 92 | } 93 | 94 | impl State { 95 | // 3D Bloch sphere visualization for single qubit states 96 | fn plot_bloch_sphere_3d(&self, filename: &str) -> Result<()> { 97 | // Create drawing area 98 | let root = SVGBackend::new(filename, (800, 600)).into_drawing_area(); 99 | 100 | if let Err(e) = root.fill(&WHITE) { 101 | return Err(Error::new( 102 | ErrorKind::Other, 103 | format!("Drawing error: {:?}", e), 104 | )); 105 | } 106 | 107 | // Calculate Bloch coordinates 108 | let (x, y, z) = self.calculate_bloch_coordinates(); 109 | 110 | // Create 3D chart 111 | let mut chart = match ChartBuilder::on(&root) 112 | .caption("3D Bloch Sphere", ("sans-serif", 30).into_font()) 113 | .margin(40) 114 | .build_cartesian_3d(-1.2..1.2, -1.2..1.2, -1.2..1.2) 115 | { 116 | Ok(chart) => chart, 117 | Err(e) => { 118 | return Err(Error::new( 119 | ErrorKind::Other, 120 | format!("Chart error: {:?}", e), 121 | )) 122 | } 123 | }; 124 | 125 | // Configure axes (view_angle is not available in plotters) 126 | if let Err(e) = chart 127 | .configure_axes() 128 | .light_grid_style(BLACK.mix(0.1)) 129 | .max_light_lines(3) 130 | .draw() 131 | { 132 | return Err(Error::new(ErrorKind::Other, format!("Axes error: {:?}", e))); 133 | } 134 | 135 | // Draw Bloch sphere wireframe - generate and draw each line separately 136 | // Longitude lines (lines of constant φ) 137 | for i in 0..12 { 138 | let phi = 2.0 * PI * (i as f64) / 12.0; 139 | let line_points: Vec<(f64, f64, f64)> = (0..=12) 140 | .map(|j| { 141 | let theta = PI * (j as f64) / 12.0; 142 | let x = 0.98 * theta.sin() * phi.cos(); 143 | let y = 0.98 * theta.sin() * phi.sin(); 144 | let z = 0.98 * theta.cos(); 145 | (x, y, z) 146 | }) 147 | .collect(); 148 | 149 | if let Err(e) = 150 | chart.draw_series(LineSeries::new(line_points, BLACK.mix(0.2).stroke_width(1))) 151 | { 152 | return Err(Error::new( 153 | ErrorKind::Other, 154 | format!("Longitude line error: {:?}", e), 155 | )); 156 | } 157 | } 158 | 159 | // Latitude lines (lines of constant θ) 160 | for j in 1..12 { 161 | let theta = PI * (j as f64) / 12.0; 162 | let line_points: Vec<(f64, f64, f64)> = (0..=16) 163 | .map(|i| { 164 | let phi = 2.0 * PI * (i as f64) / 16.0; 165 | let x = 0.98 * theta.sin() * phi.cos(); 166 | let y = 0.98 * theta.sin() * phi.sin(); 167 | let z = 0.98 * theta.cos(); 168 | (x, y, z) 169 | }) 170 | .collect(); 171 | 172 | if let Err(e) = 173 | chart.draw_series(LineSeries::new(line_points, BLACK.mix(0.2).stroke_width(1))) 174 | { 175 | return Err(Error::new( 176 | ErrorKind::Other, 177 | format!("Latitude line error: {:?}", e), 178 | )); 179 | } 180 | } 181 | 182 | // Draw coordinate axes 183 | if let Err(e) = chart.draw_series(LineSeries::new( 184 | vec![(-1.0, 0.0, 0.0), (1.0, 0.0, 0.0)], 185 | BLACK.stroke_width(2), 186 | )) { 187 | return Err(Error::new( 188 | ErrorKind::Other, 189 | format!("X-axis drawing error: {:?}", e), 190 | )); 191 | } 192 | 193 | if let Err(e) = chart.draw_series(LineSeries::new( 194 | vec![(0.0, -1.0, 0.0), (0.0, 1.0, 0.0)], 195 | BLACK.stroke_width(2), 196 | )) { 197 | return Err(Error::new( 198 | ErrorKind::Other, 199 | format!("Y-axis drawing error: {:?}", e), 200 | )); 201 | } 202 | 203 | if let Err(e) = chart.draw_series(LineSeries::new( 204 | vec![(0.0, 0.0, -1.0), (0.0, 0.0, 1.0)], 205 | BLACK.stroke_width(2), 206 | )) { 207 | return Err(Error::new( 208 | ErrorKind::Other, 209 | format!("Z-axis drawing error: {:?}", e), 210 | )); 211 | } 212 | 213 | // Draw axis labels 214 | if let Err(e) = chart.draw_series(PointSeries::of_element( 215 | vec![(1.1, 0.0, 0.0)], 216 | 0, 217 | BLACK, 218 | &|coord, _size, _style| Text::new("X", coord, ("sans-serif", 20).into_font()), 219 | )) { 220 | return Err(Error::new( 221 | ErrorKind::Other, 222 | format!("X label error: {:?}", e), 223 | )); 224 | } 225 | 226 | if let Err(e) = chart.draw_series(PointSeries::of_element( 227 | vec![(0.0, 1.1, 0.0)], 228 | 0, 229 | BLACK, 230 | &|coord, _size, _style| Text::new("Y", coord, ("sans-serif", 20).into_font()), 231 | )) { 232 | return Err(Error::new( 233 | ErrorKind::Other, 234 | format!("Y label error: {:?}", e), 235 | )); 236 | } 237 | 238 | if let Err(e) = chart.draw_series(PointSeries::of_element( 239 | vec![(0.0, 0.0, 1.1)], 240 | 0, 241 | BLACK, 242 | &|coord, _size, _style| Text::new("Z", coord, ("sans-serif", 20).into_font()), 243 | )) { 244 | return Err(Error::new( 245 | ErrorKind::Other, 246 | format!("Z label error: {:?}", e), 247 | )); 248 | } 249 | 250 | // Add reference points for |0⟩, |1⟩, |+⟩, etc. to help with orientation 251 | let reference_points = [ 252 | ((0.0, 0.0, 1.0), "|0⟩"), // |0⟩ state (North pole) 253 | ((0.0, 0.0, -1.0), "|1⟩"), // |1⟩ state (South pole) 254 | ((1.0, 0.0, 0.0), "|+⟩"), // |+⟩ state (+X axis) 255 | ((-1.0, 0.0, 0.0), "|-⟩"), // |-⟩ state (-X axis) 256 | ((0.0, 1.0, 0.0), "|i⟩"), // |i⟩ state (+Y axis) 257 | ((0.0, -1.0, 0.0), "|-i⟩"), // |-i⟩ state (-Y axis) 258 | ]; 259 | 260 | for (point, label) in reference_points.iter() { 261 | // Draw small points at reference states 262 | if let Err(e) = chart.draw_series(PointSeries::of_element( 263 | vec![*point], 264 | 3, 265 | BLACK.mix(0.5), 266 | &|c, s, st| EmptyElement::at(c) + Circle::new((0, 0), s, st.filled()), 267 | )) { 268 | return Err(Error::new( 269 | ErrorKind::Other, 270 | format!("Reference point error: {:?}", e), 271 | )); 272 | } 273 | 274 | // Add labels 275 | if let Err(e) = chart.draw_series(PointSeries::of_element( 276 | vec![(point.0 * 1.1, point.1 * 1.1, point.2 * 1.1)], 277 | 0, 278 | BLACK, 279 | &|coord, _size, _style| { 280 | Text::new(label.to_string(), coord, ("sans-serif", 15).into_font()) 281 | }, 282 | )) { 283 | return Err(Error::new( 284 | ErrorKind::Other, 285 | format!("Reference label error: {:?}", e), 286 | )); 287 | } 288 | } 289 | 290 | // Normalize coordinates to ensure they're exactly on the sphere surface 291 | let norm = (x * x + y * y + z * z).sqrt(); 292 | let (x_norm, y_norm, z_norm) = if norm > 1e-10 { 293 | (x / norm, y / norm, z / norm) 294 | } else { 295 | (0.0, 0.0, 1.0) // Default to |0⟩ if state is undefined 296 | }; 297 | 298 | // Draw the state vector using normalized coordinates 299 | if let Err(e) = chart.draw_series(LineSeries::new( 300 | vec![ 301 | (0.0, 0.0, 0.0), 302 | (x_norm * 0.98, y_norm * 0.98, z_norm * 0.98), 303 | ], 304 | RED.stroke_width(3), 305 | )) { 306 | return Err(Error::new( 307 | ErrorKind::Other, 308 | format!("Vector drawing error: {:?}", e), 309 | )); 310 | } 311 | 312 | // Draw the state point using normalized coordinates 313 | if let Err(e) = chart.draw_series(PointSeries::of_element( 314 | vec![(x_norm * 0.98, y_norm * 0.98, z_norm * 0.98)], 315 | 6, 316 | RED.filled(), 317 | &|c, s, st| EmptyElement::at(c) + Circle::new((0, 0), s, st.filled()), 318 | )) { 319 | return Err(Error::new( 320 | ErrorKind::Other, 321 | format!("Point drawing error: {:?}", e), 322 | )); 323 | } 324 | 325 | // Show both original and normalized coordinates 326 | let coords_text = format!( 327 | "Bloch coordinates: (x={:.3}, y={:.3}, z={:.3}) [norm={:.3}]", 328 | x, y, z, norm 329 | ); 330 | if let Err(e) = root.draw_text( 331 | &coords_text, 332 | &TextStyle::from(("sans-serif", 20).into_font()).color(&BLACK), 333 | (50, 550), 334 | ) { 335 | return Err(Error::new( 336 | ErrorKind::Other, 337 | format!("Coordinates text error: {:?}", e), 338 | )); 339 | } 340 | 341 | // Add state info 342 | let alpha = self.vector[0]; 343 | let beta = self.vector[1]; 344 | let state_text = format!( 345 | "|ψ⟩ = {:.3}{:+.3}i|0⟩ + {:.3}{:+.3}i|1⟩", 346 | alpha.re, alpha.im, beta.re, beta.im 347 | ); 348 | 349 | if let Err(e) = root.draw_text( 350 | &state_text, 351 | &TextStyle::from(("sans-serif", 20).into_font()).color(&BLACK), 352 | (50, 50), 353 | ) { 354 | return Err(Error::new( 355 | ErrorKind::Other, 356 | format!("State text error: {:?}", e), 357 | )); 358 | } 359 | 360 | if let Err(e) = root.present() { 361 | return Err(Error::new( 362 | ErrorKind::Other, 363 | format!("SVG rendering error: {:?}", e), 364 | )); 365 | } 366 | 367 | Ok(()) 368 | } 369 | 370 | // Helper method to calculate Bloch coordinates 371 | fn calculate_bloch_coordinates(&self) -> (f64, f64, f64) { 372 | assert_eq!( 373 | self.num_qubits, 1, 374 | "Bloch coordinates only defined for 1 qubit" 375 | ); 376 | 377 | let alpha = self.vector[0]; 378 | let beta = self.vector[1]; 379 | 380 | let x = 2.0 * (alpha.conj() * beta + alpha * beta.conj()).re; 381 | let y = 2.0 * (alpha.conj() * beta - alpha * beta.conj()).im; 382 | let z = alpha.norm_sqr() - beta.norm_sqr(); 383 | 384 | (x, y, z) 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /tests/algorithms/test_rqc.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazabap/LogosQ/bc4ca10795ac8adac35b9f37782b2904621f73bc/tests/algorithms/test_rqc.rs -------------------------------------------------------------------------------- /tests/algorithms/test_xyz_heisenberg.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazabap/LogosQ/bc4ca10795ac8adac35b9f37782b2904621f73bc/tests/algorithms/test_xyz_heisenberg.rs -------------------------------------------------------------------------------- /tests/test_circuits.rs: -------------------------------------------------------------------------------- 1 | use logosq::circuits::Circuit; 2 | use logosq::gates::*; 3 | use logosq::states::State; 4 | use ndarray::Array2; 5 | use num_complex::Complex64; 6 | use std::f64::consts::PI; 7 | 8 | mod tests { 9 | use super::*; 10 | #[test] 11 | fn test_circuit_creation() { 12 | let circuit = Circuit::new(2); 13 | assert_eq!(circuit.num_qubits, 2); 14 | assert_eq!(circuit.operations.len(), 0); 15 | assert_eq!(circuit.name, None); 16 | 17 | let named_circuit = Circuit::new(3).with_name("Test Circuit"); 18 | assert_eq!(named_circuit.num_qubits, 3); 19 | assert_eq!(named_circuit.name, Some("Test Circuit".to_string())); 20 | } 21 | 22 | #[test] 23 | fn test_adding_operations() { 24 | let mut circuit = Circuit::new(2); 25 | let x = x_gate(); 26 | 27 | // Add X gate to qubit 0 28 | circuit.add_operation(x.clone(), vec![0], "X on 0"); 29 | assert_eq!(circuit.operations.len(), 1); 30 | assert_eq!(circuit.operations[0].qubits, vec![0]); 31 | assert_eq!(circuit.operations[0].name, "X on 0"); 32 | 33 | // Add X gate to qubit 1 34 | circuit.add_operation(x.clone(), vec![1], "X on 1"); 35 | assert_eq!(circuit.operations.len(), 2); 36 | assert_eq!(circuit.operations[1].qubits, vec![1]); 37 | assert_eq!(circuit.operations[1].name, "X on 1"); 38 | } 39 | 40 | #[test] 41 | #[should_panic(expected = "Qubit index 2 out of range")] 42 | fn test_invalid_qubit_index() { 43 | let mut circuit = Circuit::new(2); 44 | let x = x_gate(); 45 | 46 | // This should panic because qubit 2 is out of range for a 2-qubit circuit 47 | circuit.add_operation(x, vec![2], "X on 2"); 48 | } 49 | 50 | #[test] 51 | fn test_execute_circuit() { 52 | let mut circuit = Circuit::new(1); 53 | let x = x_gate(); 54 | 55 | // Add X gate to qubit 0 56 | circuit.add_operation(x, vec![0], "X on 0"); 57 | 58 | // Execute on |0⟩ state 59 | let mut state = State::zero_state(1); 60 | circuit.execute(&mut state); 61 | 62 | // Should be |1⟩ state 63 | assert!(state.probability(0) < 1e-10); 64 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 65 | } 66 | 67 | #[test] 68 | fn test_bell_state_circuit() { 69 | // Circuit to create Bell state |00⟩ + |11⟩ / sqrt(2) 70 | let mut circuit = Circuit::new(2); 71 | let h = h_gate(); 72 | let cnot = cnot_gate(); 73 | 74 | circuit.add_single_qubit_gate(h.matrix, 0, "H"); 75 | circuit.add_two_qubit_gate(cnot.matrix, 0, 1, "CNOT"); 76 | 77 | // Execute on |00⟩ state 78 | let mut state = State::zero_state(2); 79 | circuit.execute(&mut state); 80 | 81 | // Should be Bell state 82 | assert!((state.probability(0) - 0.5).abs() < 1e-10); 83 | assert!(state.probability(1) < 1e-10); 84 | assert!(state.probability(2) < 1e-10); 85 | assert!((state.probability(3) - 0.5).abs() < 1e-10); 86 | } 87 | 88 | #[test] 89 | fn test_bell_state_circuit_flipped() { 90 | // Circuit to create Bell state |00⟩ + |11⟩ / sqrt(2) 91 | let mut circuit = Circuit::new(2); 92 | let h = h_gate(); 93 | let x = x_gate(); 94 | let cnot = cnot_gate(); 95 | 96 | // Create |10⟩ state first 97 | circuit.add_single_qubit_gate(x.matrix.clone(), 0, "X"); 98 | // Then create Bell state 99 | circuit.add_single_qubit_gate(h.matrix, 0, "H"); 100 | 101 | circuit.add_two_qubit_gate(cnot.matrix, 0, 1, "CNOT"); 102 | 103 | // Execute on |00⟩ state 104 | let mut state = State::zero_state(2); 105 | circuit.execute(&mut state); 106 | 107 | // Should be Bell state 108 | assert!((state.probability(0) - 0.5).abs() < 1e-10); 109 | assert!(state.probability(1) < 1e-10); 110 | assert!(state.probability(2) < 1e-10); 111 | assert!((state.probability(3) - 0.5).abs() < 1e-10); 112 | } 113 | 114 | #[test] 115 | fn test_three_state_circuit() { 116 | // Circuit to create |000⟩ + |111⟩ / sqrt(2) 117 | let mut circuit = Circuit::new(3); 118 | let h = h_gate(); 119 | let cnot = cnot_gate(); 120 | 121 | // Create |000⟩ state 122 | circuit.add_single_qubit_gate(h.matrix, 0, "H"); 123 | circuit.add_two_qubit_gate(cnot.matrix.clone(), 0, 1, "CNOT"); 124 | circuit.add_two_qubit_gate(cnot.matrix.clone(), 0, 2, "CNOT"); 125 | 126 | // Execute on |000⟩ state 127 | let mut state = State::zero_state(3); 128 | circuit.execute(&mut state); 129 | 130 | // Should be |000⟩ + |111⟩ / sqrt(2) 131 | assert!((state.probability(0) - 0.5).abs() < 1e-10); 132 | assert!(state.probability(1) < 1e-10); 133 | assert!(state.probability(2) < 1e-10); 134 | assert!((state.probability(7) - 0.5).abs() < 1e-10); 135 | } 136 | 137 | #[test] 138 | fn test_fluent_bell_state() { 139 | // Create Bell state using fluent interface 140 | let mut circuit = Circuit::new(2); 141 | circuit.h(0).cnot(0, 1); 142 | 143 | // Execute on |00⟩ state 144 | let mut state = State::zero_state(2); 145 | circuit.execute(&mut state); 146 | 147 | // Should be Bell state 148 | assert!((state.probability(0) - 0.5).abs() < 1e-10); 149 | assert!(state.probability(1) < 1e-10); 150 | assert!(state.probability(2) < 1e-10); 151 | assert!((state.probability(3) - 0.5).abs() < 1e-10); 152 | } 153 | 154 | #[test] 155 | fn test_circuit_composition() { 156 | // First circuit: X on qubit 0 157 | let mut circuit1 = Circuit::new(2); 158 | circuit1.x(0); 159 | 160 | // Second circuit: H on qubit 1 161 | let mut circuit2 = Circuit::new(2); 162 | circuit2.h(1); 163 | 164 | // Compose them 165 | circuit1.compose(&circuit2); 166 | assert_eq!(circuit1.operations.len(), 2); 167 | 168 | // Execute the combined circuit 169 | let mut state = State::zero_state(2); 170 | circuit1.execute(&mut state); 171 | 172 | // Should have X on qubit 0 and H on qubit 1 173 | // |10⟩ + |11⟩ / sqrt(2) 174 | assert!(state.probability(0) < 1e-10); 175 | assert!(state.probability(1) < 1e-10); 176 | assert!((state.probability(2) - 0.5).abs() < 1e-10); 177 | assert!((state.probability(3) - 0.5).abs() < 1e-10); 178 | } 179 | 180 | #[test] 181 | fn test_circuit_reversal() { 182 | // Create a circuit X -> H on a single qubit 183 | let mut circuit = Circuit::new(1); 184 | circuit.x(0).h(0); 185 | 186 | // Reverse it to get H -> X 187 | let reversed = circuit.reversed(); 188 | assert_eq!(reversed.operations.len(), 2); 189 | assert_eq!(reversed.operations[0].name, "reversed_H"); 190 | assert_eq!(reversed.operations[1].name, "reversed_X"); 191 | 192 | // Execute original circuit on |0⟩ 193 | let mut state1 = State::zero_state(1); 194 | circuit.execute(&mut state1); 195 | 196 | // Execute reversed circuit on |0⟩ 197 | let mut state2 = State::zero_state(1); 198 | reversed.execute(&mut state2); 199 | 200 | // Results should be different 201 | assert!((state1.probability(0) - 0.5).abs() < 1e-10); 202 | assert!((state1.probability(1) - 0.5).abs() < 1e-10); 203 | 204 | assert!((state2.probability(0) - 0.5).abs() < 1e-10); 205 | assert!((state2.probability(1) - 0.5).abs() < 1e-10); 206 | // The phases would be different, but probabilities are the same 207 | } 208 | 209 | #[test] 210 | fn test_expectation_value() { 211 | // Create a circuit that prepares |+⟩ state 212 | let mut circuit = Circuit::new(1); 213 | circuit.h(0); 214 | 215 | // Measure expectation of X operator 216 | let x = x_gate(); 217 | let expectation = circuit.expectation(&x); 218 | 219 | // <+|X|+> = 1 220 | assert!((expectation - 1.0).abs() < 1e-10); 221 | 222 | // Measure expectation of Z operator 223 | let z = z_gate(); 224 | let expectation = circuit.expectation(&z); 225 | 226 | // <+|Z|+> = 0 227 | assert!(expectation.abs() < 1e-10); 228 | } 229 | 230 | #[test] 231 | fn test_single_qubit_gates() { 232 | let mut circuit = Circuit::new(1); 233 | 234 | // Test each single-qubit gate method 235 | circuit.x(0); 236 | circuit.y(0); 237 | circuit.z(0); 238 | circuit.h(0); 239 | circuit.s(0); 240 | circuit.t(0); 241 | circuit.rx(0, PI / 2.0); 242 | circuit.ry(0, PI / 2.0); 243 | circuit.rz(0, PI / 2.0); 244 | 245 | assert_eq!(circuit.operations.len(), 9); 246 | } 247 | 248 | #[test] 249 | fn test_two_qubit_gates() { 250 | let mut circuit = Circuit::new(2); 251 | 252 | // Test each two-qubit gate method 253 | circuit.cnot(0, 1); 254 | circuit.swap(0, 1); 255 | circuit.cz(0, 1); 256 | 257 | assert_eq!(circuit.operations.len(), 3); 258 | } 259 | 260 | #[test] 261 | fn test_toffoli_gate() { 262 | let mut circuit = Circuit::new(3); 263 | 264 | // Add Toffoli gate 265 | circuit.toffoli(0, 1, 2); 266 | 267 | assert_eq!(circuit.operations.len(), 1); 268 | 269 | // Test with |110⟩ state (should flip to |111⟩) 270 | let mut state = State::zero_state(3); 271 | 272 | // Apply X to first two qubits to get |110⟩ 273 | let x = x_gate(); 274 | let mut prep_circuit = Circuit::new(3); 275 | prep_circuit.add_single_qubit_gate(x.matrix.clone(), 0, "X"); 276 | prep_circuit.add_single_qubit_gate(x.matrix.clone(), 1, "X"); 277 | prep_circuit.execute(&mut state); 278 | 279 | // Apply Toffoli gate 280 | circuit.execute(&mut state); 281 | 282 | // Result should be |111⟩ 283 | assert!(state.probability(7) > 0.99); // 111 in binary = 7 in decimal 284 | } 285 | 286 | #[test] 287 | fn test_ghz_state() { 288 | // Create a GHZ state |000⟩ + |111⟩ / sqrt(2) 289 | let mut circuit = Circuit::new(3); 290 | 291 | // Apply H to qubit 0 292 | circuit.h(0); 293 | 294 | // Manually create expanded CNOT(0,1) for a 3-qubit system 295 | let mut cnot01_full = Array2::zeros((8, 8)); 296 | 297 | // Identity on states where control qubit 0 is |0⟩ 298 | cnot01_full[[0, 0]] = Complex64::new(1.0, 0.0); 299 | cnot01_full[[1, 1]] = Complex64::new(1.0, 0.0); 300 | cnot01_full[[2, 2]] = Complex64::new(1.0, 0.0); 301 | cnot01_full[[3, 3]] = Complex64::new(1.0, 0.0); 302 | 303 | // CNOT action where control qubit 0 is |1⟩ 304 | cnot01_full[[4, 6]] = Complex64::new(1.0, 0.0); // |100⟩ → |110⟩ 305 | cnot01_full[[5, 7]] = Complex64::new(1.0, 0.0); // |101⟩ → |111⟩ 306 | cnot01_full[[6, 4]] = Complex64::new(1.0, 0.0); // |110⟩ → |100⟩ 307 | cnot01_full[[7, 5]] = Complex64::new(1.0, 0.0); // |111⟩ → |101⟩ 308 | 309 | circuit.add_matrix_gate(cnot01_full, vec![0, 1, 2], "CNOT_01"); 310 | 311 | // Manually create expanded CNOT(1,2) for a 3-qubit system 312 | let mut cnot12_full = Array2::zeros((8, 8)); 313 | 314 | // Identity for states where control qubit 1 is |0⟩ 315 | cnot12_full[[0, 0]] = Complex64::new(1.0, 0.0); 316 | cnot12_full[[1, 1]] = Complex64::new(1.0, 0.0); 317 | cnot12_full[[4, 4]] = Complex64::new(1.0, 0.0); 318 | cnot12_full[[5, 5]] = Complex64::new(1.0, 0.0); 319 | 320 | // CNOT action where control qubit 1 is |1⟩ 321 | cnot12_full[[2, 3]] = Complex64::new(1.0, 0.0); // |010⟩ → |011⟩ 322 | cnot12_full[[3, 2]] = Complex64::new(1.0, 0.0); // |011⟩ → |010⟩ 323 | cnot12_full[[6, 7]] = Complex64::new(1.0, 0.0); // |110⟩ → |111⟩ 324 | cnot12_full[[7, 6]] = Complex64::new(1.0, 0.0); // |111⟩ → |110⟩ 325 | 326 | circuit.add_matrix_gate(cnot12_full, vec![0, 1, 2], "CNOT_12"); 327 | 328 | // Execute on |000⟩ state 329 | let mut state = State::zero_state(3); 330 | circuit.execute(&mut state); 331 | 332 | // Should be GHZ state 333 | assert!((state.probability(0) - 0.5).abs() < 1e-10); 334 | assert!(state.probability(1) < 1e-10); 335 | assert!(state.probability(2) < 1e-10); 336 | assert!(state.probability(3) < 1e-10); 337 | assert!(state.probability(4) < 1e-10); 338 | assert!(state.probability(5) < 1e-10); 339 | assert!(state.probability(6) < 1e-10); 340 | assert!((state.probability(7) - 0.5).abs() < 1e-10); 341 | } 342 | 343 | #[test] 344 | fn test_execute_and_measure() { 345 | // Create a deterministic circuit that always gives |1⟩ 346 | let mut circuit = Circuit::new(1); 347 | circuit.x(0); 348 | 349 | // Execute and measure 350 | let results = circuit.execute_and_measure(); 351 | assert_eq!(results.len(), 1); 352 | assert_eq!(results[0], 1); 353 | 354 | // Create a circuit that creates an equal superposition 355 | let mut circuit = Circuit::new(1); 356 | circuit.h(0); 357 | 358 | // Execute and measure multiple times to check probabilistic behavior 359 | let mut zeros = 0; 360 | let mut ones = 0; 361 | for _ in 0..100 { 362 | let results = circuit.execute_and_measure(); 363 | if results[0] == 0 { 364 | zeros += 1; 365 | } else { 366 | ones += 1; 367 | } 368 | } 369 | 370 | // Should get roughly equal numbers (allowing some statistical variation) 371 | assert!(zeros > 30 && zeros < 70); 372 | assert!(ones > 30 && ones < 70); 373 | } 374 | 375 | #[test] 376 | fn test_circuit_clone() { 377 | // Create a circuit with some operations 378 | let mut original = Circuit::new(2); 379 | original.h(0).cnot(0, 1); 380 | 381 | // Clone it 382 | let cloned = original.clone(); 383 | 384 | // Check they have the same properties 385 | assert_eq!(original.num_qubits, cloned.num_qubits); 386 | assert_eq!(original.operations.len(), cloned.operations.len()); 387 | assert_eq!(original.name, cloned.name); 388 | 389 | // Modify original and check that cloned is unaffected 390 | original.x(0); 391 | assert_eq!(original.operations.len(), 3); 392 | assert_eq!(cloned.operations.len(), 2); 393 | } 394 | 395 | #[test] 396 | fn test_debug_format() { 397 | // Create a simple circuit 398 | let mut circuit = Circuit::new(1).with_name("Test Circuit"); 399 | circuit.x(0).h(0); 400 | 401 | // Get the debug string 402 | let debug_str = format!("{:?}", circuit); 403 | 404 | // Check it contains the expected information 405 | assert!(debug_str.contains("Test Circuit")); 406 | assert!(debug_str.contains("(1 qubits)")); 407 | assert!(debug_str.contains("0: X")); 408 | assert!(debug_str.contains("1: H")); 409 | } 410 | 411 | #[test] 412 | fn test_swap_entangled_states() { 413 | // Test SWAP on entangled states 414 | let mut circuit = Circuit::new(3); 415 | 416 | // Create Bell state on qubits 0 and 1 417 | circuit.h(0).cnot(0, 1); 418 | // Now we have (|00⟩ + |11⟩)/√2 on first two qubits, and |0⟩ on third 419 | 420 | // Swap qubits 1 and 2 421 | circuit.swap(1, 2); 422 | 423 | // Execute circuit 424 | let mut state = State::zero_state(3); 425 | circuit.execute(&mut state); 426 | 427 | // Expected: Bell state now between qubits 0 and 2 428 | // State should be (|000⟩ + |101⟩)/√2 429 | assert!((state.probability(0) - 0.5).abs() < 1e-10); // |000⟩ 430 | assert!(state.probability(1) < 1e-10); // |001⟩ 431 | assert!(state.probability(2) < 1e-10); // |010⟩ 432 | assert!(state.probability(3) < 1e-10); // |011⟩ 433 | assert!(state.probability(4) < 1e-10); // |100⟩ 434 | assert!((state.probability(5) - 0.5).abs() < 1e-10); // |101⟩ 435 | assert!(state.probability(6) < 1e-10); // |110⟩ 436 | assert!(state.probability(7) < 1e-10); // |111⟩ 437 | } 438 | 439 | // Fully test the CNOT gate functionality including edge cases 440 | // ensuring it behaves as expected in various scenarios 441 | } 442 | -------------------------------------------------------------------------------- /tests/test_circuits_cnot.rs: -------------------------------------------------------------------------------- 1 | use logosq::circuits::Circuit; 2 | use logosq::states::State; 3 | 4 | mod tests { 5 | use super::*; 6 | #[test] 7 | fn test_cnot_basic_functionality() { 8 | // Create a 3-qubit circuit 9 | let mut circuit = Circuit::new(3); 10 | 11 | // Prepare initial state |100⟩ (first qubit is 1) 12 | circuit.x(0); 13 | 14 | // Add CNOT with control=0, target=2 (should flip qubit 2) 15 | circuit.cnot(0, 2); 16 | 17 | // Execute on initial zero state 18 | let mut state = State::zero_state(3); 19 | circuit.execute(&mut state); 20 | 21 | // Expected result: |101⟩ (first and third qubits are 1) 22 | assert!(state.probability(0) < 1e-10); // |000⟩ 23 | assert!(state.probability(1) < 1e-10); // |001⟩ 24 | assert!(state.probability(2) < 1e-10); // |010⟩ 25 | assert!(state.probability(3) < 1e-10); // |011⟩ 26 | assert!(state.probability(4) < 1e-10); // |100⟩ 27 | assert!(state.probability(5) > 0.99); // |101⟩ (binary 101 = decimal 5) 28 | assert!(state.probability(6) < 1e-10); // |110⟩ 29 | assert!(state.probability(7) < 1e-10); // |111⟩ 30 | 31 | // Test CNOT with control=0 but now with control qubit in state |0⟩ 32 | let mut circuit2 = Circuit::new(3); 33 | circuit2.x(0); // Set qubit 0 to |1⟩ 34 | circuit2.cnot(0, 2); // Flip qubit 2 if qubit 0 is |1⟩ 35 | circuit2.x(0); // Flip qubit 0 back to |0⟩ 36 | circuit2.cnot(0, 2); // Should NOT flip qubit 2 as control is |0⟩ 37 | 38 | let mut state2 = State::zero_state(3); 39 | circuit2.execute(&mut state2); 40 | 41 | // Expected result: |001⟩ (only third qubit is 1, flipped once) 42 | assert!(state2.probability(1) > 0.99); // |001⟩ 43 | } 44 | 45 | #[test] 46 | fn test_cnot_control_target_order() { 47 | // Test that CNOT(a,b) != CNOT(b,a) 48 | 49 | // Case 1: CNOT(0,1) 50 | let mut circuit1 = Circuit::new(2); 51 | circuit1.x(0); // Set control to |1⟩ → state is |10⟩ 52 | circuit1.cnot(0, 1); // control=0 is |1⟩, so flip target (qubit 1) 53 | 54 | let mut state1 = State::zero_state(2); 55 | circuit1.execute(&mut state1); 56 | 57 | // Expected: |11⟩ (both qubits are 1) 58 | assert!(state1.probability(3) > 0.99); // |11⟩ = decimal 3 59 | 60 | println!( 61 | "State1 probabilities: |00⟩: {}, |01⟩: {}, |10⟩: {}, |11⟩: {}", 62 | state1.probability(0), 63 | state1.probability(1), 64 | state1.probability(2), 65 | state1.probability(3) 66 | ); 67 | 68 | // Case 2: CNOT(1,0) 69 | let mut circuit2 = Circuit::new(2); 70 | circuit2.x(0); // Set qubit 0 to |1⟩ → state is |10⟩ 71 | circuit2.cnot(1, 0); // control=1 is |0⟩, so do NOT flip target (qubit 0) 72 | 73 | let mut state2 = State::zero_state(2); 74 | circuit2.execute(&mut state2); 75 | 76 | println!( 77 | "State2 probabilities: |00⟩: {}, |01⟩: {}, |10⟩: {}, |11⟩: {}", 78 | state2.probability(0), 79 | state2.probability(1), 80 | state2.probability(2), 81 | state2.probability(3) 82 | ); 83 | 84 | // Expected: |10⟩ (target NOT flipped because control=0) 85 | assert!(state2.probability(2) > 0.99); // |10⟩ = decimal 2 86 | } 87 | 88 | #[test] 89 | fn test_cnot_hadamard() { 90 | // Test CNOT in combination with Hadamard to create entanglement 91 | 92 | // Create a 2-qubit circuit 93 | let mut circuit = Circuit::new(2); 94 | circuit.h(0); // Put qubit 0 in superposition 95 | circuit.cnot(0, 1); // Entangle qubits 0 and 1 96 | 97 | let mut state = State::zero_state(2); 98 | circuit.execute(&mut state); 99 | 100 | // Expected: |00⟩ + |11⟩ / √2 101 | assert!((state.probability(0) - 0.5).abs() < 1e-10); // |00⟩ 102 | assert!(state.probability(1) < 1e-10); // |01⟩ 103 | assert!(state.probability(2) < 1e-10); // |10⟩ 104 | assert!((state.probability(3) - 0.5).abs() < 1e-10); // |11⟩ 105 | } 106 | 107 | #[test] 108 | fn test_cnot_distant_qubits() { 109 | // Create a 5-qubit circuit to test CNOT with distant qubits 110 | let mut circuit = Circuit::new(5); 111 | 112 | // Prepare control qubit 0 113 | circuit.x(0); 114 | 115 | // Apply CNOT between qubits 0 and 4 (the furthest apart) 116 | circuit.cnot(0, 4); 117 | 118 | let mut state = State::zero_state(5); 119 | circuit.execute(&mut state); 120 | 121 | // Expected result: |10001⟩ 122 | let expected_state = 0b10001; // Binary 10001 = decimal 17 123 | assert!(state.probability(expected_state) > 0.99); 124 | 125 | // Verify all other states have negligible probability 126 | for i in 0..32 { 127 | // 2^5 = 32 possible states 128 | if i != expected_state { 129 | assert!(state.probability(i) < 1e-10); 130 | } 131 | } 132 | } 133 | 134 | #[test] 135 | fn test_cnot_entanglement_creation() { 136 | // Test CNOT's ability to create entanglement 137 | 138 | // Create a circuit to generate a Bell state 139 | let mut circuit = Circuit::new(2); 140 | circuit.h(0); // Put qubit 0 in superposition 141 | circuit.cnot(0, 1); // Entangle qubits 0 and 1 142 | 143 | let mut state = State::zero_state(2); 144 | circuit.execute(&mut state); 145 | 146 | // Expected: |00⟩ + |11⟩ / √2 147 | assert!((state.probability(0) - 0.5).abs() < 1e-10); // |00⟩ 148 | assert!(state.probability(1) < 1e-10); // |01⟩ 149 | assert!(state.probability(2) < 1e-10); // |10⟩ 150 | assert!((state.probability(3) - 0.5).abs() < 1e-10); // |11⟩ 151 | 152 | // Now test with 3 qubits to create a GHZ state 153 | let mut ghz_circuit = Circuit::new(3); 154 | ghz_circuit.h(0); 155 | ghz_circuit.cnot(0, 1); 156 | ghz_circuit.cnot(0, 2); // Control=0, target=2 (non-adjacent) 157 | 158 | let mut ghz_state = State::zero_state(3); 159 | ghz_circuit.execute(&mut ghz_state); 160 | 161 | // Expected: |000⟩ + |111⟩ / √2 162 | assert!((ghz_state.probability(0) - 0.5).abs() < 1e-10); // |000⟩ 163 | assert!(ghz_state.probability(1) < 1e-10); // |001⟩ 164 | assert!(ghz_state.probability(2) < 1e-10); // |010⟩ 165 | assert!(ghz_state.probability(3) < 1e-10); // |011⟩ 166 | assert!(ghz_state.probability(4) < 1e-10); // |100⟩ 167 | assert!(ghz_state.probability(5) < 1e-10); // |101⟩ 168 | assert!(ghz_state.probability(6) < 1e-10); // |110⟩ 169 | assert!((ghz_state.probability(7) - 0.5).abs() < 1e-10); // |111⟩ 170 | } 171 | 172 | #[test] 173 | fn test_cnot_chain() { 174 | // Test a chain of CNOT gates 175 | let mut circuit = Circuit::new(4); 176 | 177 | // Put first qubit in superposition 178 | circuit.h(0); 179 | 180 | // Create a chain: qubit i controls qubit i+1 181 | circuit.cnot(0, 1).cnot(1, 2).cnot(2, 3); 182 | 183 | let mut state = State::zero_state(4); 184 | circuit.execute(&mut state); 185 | 186 | // Expected: |0000⟩ + |1111⟩ / √2 187 | assert!((state.probability(0) - 0.5).abs() < 1e-10); // |0000⟩ 188 | assert!((state.probability(15) - 0.5).abs() < 1e-10); // |1111⟩ 189 | 190 | // All other states should have zero probability 191 | for i in 1..15 { 192 | assert!( 193 | state.probability(i) < 1e-10, 194 | "Unexpected probability for state {}: {}", 195 | i, 196 | state.probability(i) 197 | ); 198 | } 199 | } 200 | 201 | #[test] 202 | fn test_cnot_with_preexisting_entanglement() { 203 | // Create a Bell state on qubits 0 and 1 204 | let mut circuit = Circuit::new(3); 205 | circuit.h(0).cnot(0, 1); 206 | 207 | // Add CNOT from entangled qubit 1 to qubit 2 208 | circuit.cnot(1, 2); 209 | 210 | let mut state = State::zero_state(3); 211 | circuit.execute(&mut state); 212 | 213 | // Expected: |000⟩ + |111⟩ / √2 214 | // (Qubit 2 is flipped only when qubit 1 is |1⟩, which happens when qubit 0 is |1⟩) 215 | assert!((state.probability(0) - 0.5).abs() < 1e-10); // |000⟩ 216 | assert!(state.probability(1) < 1e-10); // |001⟩ 217 | assert!(state.probability(2) < 1e-10); // |010⟩ 218 | assert!(state.probability(3) < 1e-10); // |011⟩ 219 | assert!(state.probability(4) < 1e-10); // |100⟩ 220 | assert!(state.probability(5) < 1e-10); // |101⟩ 221 | assert!(state.probability(6) < 1e-10); // |110⟩ 222 | assert!((state.probability(7) - 0.5).abs() < 1e-10); // |111⟩ 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /tests/test_circuits_swap.rs: -------------------------------------------------------------------------------- 1 | use logosq::circuits::Circuit; 2 | use logosq::states::State; 3 | 4 | use logosq::vis::circuit::text_diagram; 5 | 6 | mod tests { 7 | use super::*; 8 | // Adding test for swap gate functionality 9 | // for multiple qubits and ensuring it behaves as expected 10 | #[test] 11 | fn test_swap_gate_functionality() { 12 | // Create a 3-qubit circuit 13 | let mut circuit = Circuit::new(3); 14 | // Prepare initial state |100⟩ (first qubit is 1) 15 | circuit.x(0); 16 | 17 | println!("{}", text_diagram(&circuit)); 18 | 19 | // Add a SWAP between qubits 0 and 2 20 | circuit.swap(0, 2); 21 | 22 | println!("{}", text_diagram(&circuit)); 23 | 24 | // Execute on initial zero state 25 | let mut state = State::zero_state(3); 26 | circuit.execute(&mut state); 27 | 28 | // Expected result: |001⟩ (third qubit is now 1) 29 | assert!(state.probability(0) < 1e-10); // |000⟩ 30 | assert!(state.probability(1) > 0.99); // |001⟩ (binary 001 = decimal 1) 31 | assert!(state.probability(2) < 1e-10); // |010⟩ 32 | assert!(state.probability(3) < 1e-10); // |011⟩ 33 | assert!(state.probability(4) < 1e-10); // |100⟩ 34 | assert!(state.probability(5) < 1e-10); // |101⟩ 35 | assert!(state.probability(6) < 1e-10); // |110⟩ 36 | assert!(state.probability(7) < 1e-10); // |111⟩ 37 | 38 | // Test that swap is its own inverse 39 | let mut inverse_circuit = Circuit::new(3); 40 | inverse_circuit.x(0); 41 | inverse_circuit.swap(0, 2); 42 | inverse_circuit.swap(0, 2); // Apply swap again 43 | 44 | let mut state2 = State::zero_state(3); 45 | inverse_circuit.execute(&mut state2); 46 | 47 | // Expected result: |100⟩ (back to original state) 48 | assert!(state2.probability(4) > 0.99); // |100⟩ (binary 100 = decimal 4) 49 | } 50 | 51 | #[test] 52 | fn test_multiple_swap_operations() { 53 | // Create a 4-qubit circuit for more complex swap operations 54 | let mut circuit = Circuit::new(4); 55 | 56 | // Prepare initial state |1010⟩ (qubits 0 and 2 are 1) 57 | circuit.x(0).x(2); 58 | 59 | // Series of SWAP operations 60 | circuit 61 | .swap(0, 1) // |1010⟩ -> |0110⟩ 62 | .swap(2, 3) // |0110⟩ -> |0101⟩ 63 | .swap(1, 2); // |0101⟩ -> |0011⟩ 64 | 65 | // Execute circuit 66 | let mut state = State::zero_state(4); 67 | circuit.execute(&mut state); 68 | 69 | // Expected result: |0011⟩ (binary 0011 = decimal 3) 70 | assert!(state.probability(3) > 0.99); 71 | 72 | // Verify all other states have ~0 probability 73 | for i in 0..16 { 74 | if i != 3 { 75 | assert!( 76 | state.probability(i) < 1e-10, 77 | "Unexpected probability for state {}: {}", 78 | i, 79 | state.probability(i) 80 | ); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/test_circuits_toffoli.rs: -------------------------------------------------------------------------------- 1 | use logosq::circuits::Circuit; 2 | use logosq::states::State; 3 | use logosq::vis::circuit::text_diagram; 4 | 5 | mod tests { 6 | use super::*; 7 | 8 | #[test] 9 | fn test_toffoli_basic_functionality() { 10 | // Create a 3-qubit circuit 11 | let mut circuit = Circuit::new(3); 12 | 13 | // Prepare initial state |110⟩ (both control qubits are 1) 14 | circuit.x(0).x(1); 15 | 16 | // Add Toffoli with controls=0,1 and target=2 17 | circuit.toffoli(0, 1, 2); 18 | 19 | // Execute on initial zero state 20 | let mut state = State::zero_state(3); 21 | circuit.execute(&mut state); 22 | 23 | // Expected result: |111⟩ (all qubits are 1) 24 | assert!(state.probability(0) < 1e-10); // |000⟩ 25 | assert!(state.probability(1) < 1e-10); // |001⟩ 26 | assert!(state.probability(2) < 1e-10); // |010⟩ 27 | assert!(state.probability(3) < 1e-10); // |011⟩ 28 | assert!(state.probability(4) < 1e-10); // |100⟩ 29 | assert!(state.probability(5) < 1e-10); // |101⟩ 30 | assert!(state.probability(6) < 1e-10); // |110⟩ 31 | assert!(state.probability(7) > 0.99); // |111⟩ (binary 111 = decimal 7) 32 | 33 | // Test with only one control qubit set to |1⟩ (should NOT flip target) 34 | let mut circuit2 = Circuit::new(3); 35 | circuit2.x(0); // Only first control is 1 36 | circuit2.toffoli(0, 1, 2); 37 | 38 | let mut state2 = State::zero_state(3); 39 | circuit2.execute(&mut state2); 40 | 41 | // Expected result: |100⟩ (only first qubit is 1) 42 | assert!(state2.probability(4) > 0.99); // |100⟩ (binary 100 = decimal 4) 43 | } 44 | 45 | #[test] 46 | fn test_toffoli_control_order() { 47 | // Test that order of control qubits doesn't matter 48 | 49 | // Case 1: Toffoli(0,1,2) 50 | let mut circuit1 = Circuit::new(3); 51 | circuit1.x(0).x(1); // Set both controls to |1⟩ 52 | circuit1.toffoli(0, 1, 2); 53 | 54 | let mut state1 = State::zero_state(3); 55 | circuit1.execute(&mut state1); 56 | 57 | // Expected: |111⟩ (target flipped) 58 | assert!(state1.probability(7) > 0.99); 59 | 60 | // Case 2: Toffoli(1,0,2) - reversed control order 61 | let mut circuit2 = Circuit::new(3); 62 | circuit2.x(0).x(1); // Set both controls to |1⟩ 63 | circuit2.toffoli(1, 0, 2); 64 | 65 | let mut state2 = State::zero_state(3); 66 | circuit2.execute(&mut state2); 67 | 68 | // Expected: |111⟩ (target should still be flipped) 69 | assert!(state2.probability(7) > 0.99); 70 | } 71 | 72 | #[test] 73 | fn test_toffoli_distant_qubits() { 74 | // Create a 5-qubit circuit to test Toffoli with distant qubits 75 | let mut circuit = Circuit::new(5); 76 | 77 | // Prepare control qubits 0 and 4 (the furthest apart) 78 | circuit.x(0).x(4); 79 | 80 | // Apply Toffoli between qubits 0,4 (controls) and 2 (target in the middle) 81 | circuit.toffoli(0, 4, 2); 82 | 83 | let mut state = State::zero_state(5); 84 | circuit.execute(&mut state); 85 | 86 | // Expected result: |10101⟩ (qubits 0, 2, and 4 are 1) 87 | // Binary: qubit 0=1, qubit 1=0, qubit 2=1, qubit 3=0, qubit 4=1 88 | // As decimal: 16 + 4 + 1 = 21 89 | let expected_state = 21; // Binary 10101 = decimal 21 90 | assert!( 91 | state.probability(expected_state) > 0.99, 92 | "Expected state 21 (10101), got probabilities: {}", 93 | (0..32) 94 | .map(|i| format!("{}: {:.4}", i, state.probability(i))) 95 | .collect::>() 96 | .join(", ") 97 | ); 98 | 99 | // Verify all other states have negligible probability 100 | for i in 0..32 { 101 | // 2^5 = 32 possible states 102 | if i != expected_state { 103 | assert!( 104 | state.probability(i) < 1e-10, 105 | "Unexpected probability for state {}: {}", 106 | i, 107 | state.probability(i) 108 | ); 109 | } 110 | } 111 | } 112 | 113 | #[test] 114 | fn test_toffoli_with_superposition() { 115 | // Test Toffoli with a control qubit in superposition 116 | let mut circuit = Circuit::new(3); 117 | 118 | // Prepare |+10⟩ state (first qubit in superposition, second qubit is 1) 119 | circuit.h(0).x(1); 120 | 121 | // Apply Toffoli 122 | circuit.toffoli(0, 1, 2); 123 | 124 | let mut state = State::zero_state(3); 125 | circuit.execute(&mut state); 126 | 127 | // Expected: 1/√2 (|010⟩ + |111⟩) 128 | // The target flips only when first control is |1⟩ 129 | assert!((state.probability(2) - 0.5).abs() < 1e-10); // |010⟩ 130 | assert!((state.probability(7) - 0.5).abs() < 1e-10); // |111⟩ 131 | 132 | // All other states should have zero probability 133 | for i in 0..8 { 134 | if i != 2 && i != 7 { 135 | assert!( 136 | state.probability(i) < 1e-10, 137 | "Unexpected probability for state {}: {}", 138 | i, 139 | state.probability(i) 140 | ); 141 | } 142 | } 143 | } 144 | 145 | #[test] 146 | fn test_toffoli_quantum_addition() { 147 | // Test Toffoli in a quantum half-adder circuit 148 | // Proper half-adder needs to compute carry BEFORE modifying inputs 149 | 150 | let mut half_adder = Circuit::new(4); // Use 4 qubits to preserve inputs 151 | 152 | // Prepare input |11⟩ on first two qubits (a=1, b=1) 153 | half_adder.x(0).x(1); 154 | 155 | // Compute carry first: Toffoli(0,1,3) -> qubit 3 gets carry 156 | half_adder.toffoli(0, 1, 3); 157 | 158 | // Compute sum: CNOT(0,1) -> qubit 1 gets sum 159 | half_adder.cnot(0, 1); 160 | 161 | let mut state = State::zero_state(4); 162 | half_adder.execute(&mut state); 163 | 164 | // For inputs a=1, b=1: 165 | // Step by step: 166 | // |0000⟩ -> X(0) -> |1000⟩ -> X(1) -> |1100⟩ 167 | // |1100⟩ -> Toffoli(0,1,3) -> |1101⟩ (both controls are 1, flip target) 168 | // |1101⟩ -> CNOT(0,1) -> |1001⟩ (control 0 is 1, flip target 1) 169 | // 170 | // Result: |1001⟩ 171 | // qubit 0 (a) = 1 172 | // qubit 1 (sum) = 0 (was 1, flipped by CNOT) 173 | // qubit 2 (unused) = 0 174 | // qubit 3 (carry) = 1 175 | let expected_state = 0b1001; // Binary 1001 = decimal 9 176 | assert!( 177 | state.probability(expected_state) > 0.99, 178 | "Expected state 9 (1001), got probabilities: {}", 179 | (0..16) 180 | .map(|i| format!("{}: {:.4}", i, state.probability(i))) 181 | .collect::>() 182 | .join(", ") 183 | ); 184 | } 185 | 186 | #[test] 187 | fn test_toffoli_as_universal_gate() { 188 | // Test Toffoli as a universal gate by constructing NOT and CNOT 189 | 190 | // NOT gate using Toffoli with controls set to |1⟩ 191 | let mut not_circuit = Circuit::new(3); 192 | not_circuit.x(0).x(1); // Set first two qubits to |1⟩ 193 | not_circuit.toffoli(0, 1, 2); // Apply Toffoli (acts as NOT on qubit 2) 194 | 195 | let mut not_state = State::zero_state(3); 196 | not_circuit.execute(&mut not_state); 197 | 198 | // Expected: |111⟩ (target qubit flipped) 199 | assert!(not_state.probability(7) > 0.99); 200 | 201 | // CNOT gate using Toffoli with one control always set to |1⟩ 202 | let mut cnot_circuit = Circuit::new(3); 203 | cnot_circuit.x(0); // Set first qubit to |1⟩ 204 | cnot_circuit.x(1); // Set second qubit (which acts as control) to |1⟩ 205 | cnot_circuit.toffoli(0, 1, 2); // Apply Toffoli (acts as CNOT from qubit 1 to 2) 206 | 207 | let mut cnot_state = State::zero_state(3); 208 | cnot_circuit.execute(&mut cnot_state); 209 | 210 | // Expected: |111⟩ (target qubit flipped because both controls were |1⟩) 211 | assert!( 212 | cnot_state.probability(7) > 0.99, 213 | "Expected state 7 (111), got probabilities: {}", 214 | (0..8) 215 | .map(|i| format!("{}: {:.4}", i, cnot_state.probability(i))) 216 | .collect::>() 217 | .join(", ") 218 | ); 219 | } 220 | 221 | #[test] 222 | fn test_toffoli_chain() { 223 | // Test a chain of Toffoli gates to create a multi-controlled operation 224 | let mut circuit = Circuit::new(5); 225 | 226 | // Set qubits 0, 1, 2 to |1⟩ 227 | circuit.x(0).x(1).x(2); 228 | 229 | // Chain of Toffoli gates to implement a 3-controlled NOT on qubit 4 230 | // First Toffoli: controls=0,1, target=3 (temporary ancilla) 231 | circuit.toffoli(0, 1, 3); 232 | // Second Toffoli: controls=2,3, target=4 233 | circuit.toffoli(2, 3, 4); 234 | 235 | let mut state = State::zero_state(5); 236 | circuit.execute(&mut state); 237 | 238 | // After first Toffoli: |11110⟩ (qubit 3 flipped because 0,1 are both 1) 239 | // After second Toffoli: |11111⟩ (qubit 4 flipped because 2,3 are both 1) 240 | let expected_state = 0b11111; // Binary 11111 = decimal 31 241 | assert!( 242 | state.probability(expected_state) > 0.99, 243 | "Expected state 31 (11111), got probabilities: {}", 244 | (0..32) 245 | .map(|i| format!("{}: {:.4}", i, state.probability(i))) 246 | .collect::>() 247 | .join(", ") 248 | ); 249 | } 250 | 251 | #[test] 252 | fn test_toffoli_with_entanglement() { 253 | // Test Toffoli with one control being part of a Bell state 254 | let mut circuit = Circuit::new(4); 255 | 256 | // Create Bell state on qubits 0,1: (|00⟩ + |11⟩)/√2 257 | circuit.h(0).cnot(0, 1); 258 | 259 | // Set second control to |1⟩ 260 | circuit.x(2); 261 | 262 | // Apply Toffoli with controls=1,2 and target=3 263 | circuit.toffoli(1, 2, 3); 264 | 265 | let mut state = State::zero_state(4); 266 | circuit.execute(&mut state); 267 | 268 | // After Bell state: (|0000⟩ + |1100⟩)/√2 269 | // After X(2): (|0010⟩ + |1110⟩)/√2 270 | // After Toffoli(1,2,3): 271 | // - |0010⟩: controls are 0,1 -> no flip -> |0010⟩ 272 | // - |1110⟩: controls are 1,1 -> flip -> |1111⟩ 273 | // Result: (|0010⟩ + |1111⟩)/√2 274 | 275 | assert!( 276 | (state.probability(2) - 0.5).abs() < 1e-10, // |0010⟩ = decimal 2 277 | "State 2 probability: {}", 278 | state.probability(2) 279 | ); 280 | assert!( 281 | (state.probability(15) - 0.5).abs() < 1e-10, // |1111⟩ = decimal 15 282 | "State 15 probability: {}", 283 | state.probability(15) 284 | ); 285 | 286 | // All other states should have zero probability 287 | for i in 0..16 { 288 | if i != 2 && i != 15 { 289 | assert!( 290 | state.probability(i) < 1e-10, 291 | "Unexpected probability for state {}: {}", 292 | i, 293 | state.probability(i) 294 | ); 295 | } 296 | } 297 | } 298 | 299 | #[test] 300 | fn test_toffoli_self_inverse() { 301 | // Test that applying Toffoli twice restores the original state 302 | let mut circuit = Circuit::new(3); 303 | 304 | // Prepare |110⟩ 305 | circuit.x(0).x(1); 306 | 307 | // Apply Toffoli twice 308 | circuit.toffoli(0, 1, 2).toffoli(0, 1, 2); 309 | 310 | let mut state = State::zero_state(3); 311 | circuit.execute(&mut state); 312 | 313 | // Expected result: |110⟩ (back to the original state) 314 | assert!(state.probability(6) > 0.99); // |110⟩ = decimal 6 315 | } 316 | 317 | #[test] 318 | fn test_toffoli_incremental_control() { 319 | // Test behavior when control qubits change incrementally 320 | let mut circuit = Circuit::new(3); 321 | 322 | // Start with |000⟩ -> Toffoli -> |000⟩ (no flip, controls are 0,0) 323 | circuit.toffoli(0, 1, 2); 324 | 325 | // X(0): |000⟩ -> |100⟩ 326 | // Toffoli: |100⟩ -> |100⟩ (no flip, controls are 1,0) 327 | circuit.x(0); 328 | circuit.toffoli(0, 1, 2); 329 | 330 | // X(1): |100⟩ -> |110⟩ 331 | // Toffoli: |110⟩ -> |111⟩ (flip, controls are 1,1) 332 | circuit.x(1); 333 | circuit.toffoli(0, 1, 2); 334 | 335 | // X(0): |111⟩ -> |011⟩ 336 | // Toffoli: |011⟩ -> |011⟩ (no flip, controls are 0,1) 337 | circuit.x(0); 338 | circuit.toffoli(0, 1, 2); 339 | 340 | let mut state = State::zero_state(3); 341 | circuit.execute(&mut state); 342 | 343 | // Expected result: |011⟩ (first qubit is 0, second and third are 1) 344 | assert!( 345 | state.probability(3) > 0.99, // |011⟩ = decimal 3 346 | "Expected state 3 (011), got probabilities: {}", 347 | (0..8) 348 | .map(|i| format!("{}: {:.4}", i, state.probability(i))) 349 | .collect::>() 350 | .join(", ") 351 | ); 352 | } 353 | 354 | #[test] 355 | fn test_toffoli_circuit_diagram() { 356 | // Verify the circuit diagram properly shows the Toffoli gate 357 | let mut circuit = Circuit::new(3); 358 | circuit.x(0).x(1); 359 | circuit.toffoli(0, 1, 2); 360 | 361 | let diagram = text_diagram(&circuit); 362 | println!("Toffoli circuit diagram:\n{}", diagram); 363 | 364 | // Just print the diagram for visual inspection, no assertions 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /tests/test_gates.rs: -------------------------------------------------------------------------------- 1 | use logosq::gates::*; 2 | use logosq::states::State; 3 | use num_complex::Complex64; 4 | use std::f64::consts::{PI, SQRT_2}; 5 | 6 | #[test] 7 | fn test_x_gate() { 8 | // Create X gate 9 | let x = x_gate(); 10 | 11 | // Test matrix representation 12 | assert_eq!(x.matrix.shape(), &[2, 2]); 13 | assert!((x.matrix[[0, 0]] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 14 | assert!((x.matrix[[0, 1]] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 15 | assert!((x.matrix[[1, 0]] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 16 | assert!((x.matrix[[1, 1]] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 17 | 18 | // Test application to |0⟩ (should get |1⟩) 19 | let mut state = State::zero_state(1); 20 | x.apply(&mut state); 21 | assert!(state.probability(0) < 1e-10); 22 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 23 | 24 | // Test application to |1⟩ (should get |0⟩) 25 | let mut state = State::one_state(1); 26 | x.apply(&mut state); 27 | assert!((state.probability(0) - 1.0).abs() < 1e-10); 28 | assert!(state.probability(1) < 1e-10); 29 | } 30 | 31 | #[test] 32 | fn test_y_gate() { 33 | // Create Y gate 34 | let y = y_gate(); 35 | 36 | // Test matrix representation 37 | assert_eq!(y.matrix.shape(), &[2, 2]); 38 | assert!((y.matrix[[0, 0]] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 39 | assert!((y.matrix[[0, 1]] - Complex64::new(0.0, -1.0)).norm() < 1e-10); 40 | assert!((y.matrix[[1, 0]] - Complex64::new(0.0, 1.0)).norm() < 1e-10); 41 | assert!((y.matrix[[1, 1]] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 42 | 43 | // Test application to |0⟩ (should get i|1⟩) 44 | let mut state = State::zero_state(1); 45 | y.apply(&mut state); 46 | assert!(state.probability(0) < 1e-10); 47 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 48 | assert!((state.vector[1] - Complex64::new(0.0, 1.0)).norm() < 1e-10); 49 | 50 | // Test application to |1⟩ (should get -i|0⟩) 51 | let mut state = State::one_state(1); 52 | y.apply(&mut state); 53 | assert!((state.probability(0) - 1.0).abs() < 1e-10); 54 | assert!(state.probability(1) < 1e-10); 55 | assert!((state.vector[0] - Complex64::new(0.0, -1.0)).norm() < 1e-10); 56 | } 57 | 58 | #[test] 59 | fn test_z_gate() { 60 | // Create Z gate 61 | let z = z_gate(); 62 | 63 | // Test matrix representation 64 | assert_eq!(z.matrix.shape(), &[2, 2]); 65 | assert!((z.matrix[[0, 0]] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 66 | assert!((z.matrix[[0, 1]] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 67 | assert!((z.matrix[[1, 0]] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 68 | assert!((z.matrix[[1, 1]] - Complex64::new(-1.0, 0.0)).norm() < 1e-10); 69 | 70 | // Test application to |0⟩ (should remain |0⟩) 71 | let mut state = State::zero_state(1); 72 | z.apply(&mut state); 73 | assert!((state.probability(0) - 1.0).abs() < 1e-10); 74 | assert!(state.probability(1) < 1e-10); 75 | 76 | // Test application to |1⟩ (should get -|1⟩) 77 | let mut state = State::one_state(1); 78 | z.apply(&mut state); 79 | assert!(state.probability(0) < 1e-10); 80 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 81 | assert!((state.vector[1] - Complex64::new(-1.0, 0.0)).norm() < 1e-10); 82 | 83 | // Test application to |+⟩ (should get |−⟩) 84 | let mut state = State::plus_state(1); 85 | z.apply(&mut state); 86 | // |−⟩ has equal probability for |0⟩ and |1⟩ 87 | assert!((state.probability(0) - 0.5).abs() < 1e-10); 88 | assert!((state.probability(1) - 0.5).abs() < 1e-10); 89 | // But opposite phases 90 | assert!((state.vector[0] - Complex64::new(1.0 / SQRT_2, 0.0)).norm() < 1e-10); 91 | assert!((state.vector[1] - Complex64::new(-1.0 / SQRT_2, 0.0)).norm() < 1e-10); 92 | } 93 | 94 | #[test] 95 | fn test_hadamard_gate() { 96 | // Create H gate 97 | let h = h_gate(); 98 | 99 | // Test matrix representation 100 | assert_eq!(h.matrix.shape(), &[2, 2]); 101 | assert!((h.matrix[[0, 0]] - Complex64::new(1.0 / SQRT_2, 0.0)).norm() < 1e-10); 102 | assert!((h.matrix[[0, 1]] - Complex64::new(1.0 / SQRT_2, 0.0)).norm() < 1e-10); 103 | assert!((h.matrix[[1, 0]] - Complex64::new(1.0 / SQRT_2, 0.0)).norm() < 1e-10); 104 | assert!((h.matrix[[1, 1]] - Complex64::new(-1.0 / SQRT_2, 0.0)).norm() < 1e-10); 105 | 106 | // Test application to |0⟩ (should get |+⟩) 107 | let mut state = State::zero_state(1); 108 | h.apply(&mut state); 109 | assert!((state.probability(0) - 0.5).abs() < 1e-10); 110 | assert!((state.probability(1) - 0.5).abs() < 1e-10); 111 | 112 | // Test application to |1⟩ (should get |−⟩) 113 | let mut state = State::one_state(1); 114 | h.apply(&mut state); 115 | assert!((state.probability(0) - 0.5).abs() < 1e-10); 116 | assert!((state.probability(1) - 0.5).abs() < 1e-10); 117 | 118 | // Test H·H = I (applying H twice should give back the original state) 119 | let mut state = State::zero_state(1); 120 | h.apply(&mut state); 121 | h.apply(&mut state); 122 | assert!((state.probability(0) - 1.0).abs() < 1e-10); 123 | assert!(state.probability(1) < 1e-10); 124 | } 125 | 126 | #[test] 127 | fn test_s_gate() { 128 | // Create S gate 129 | let s = s_gate(); 130 | 131 | // Test matrix representation 132 | assert_eq!(s.matrix.shape(), &[2, 2]); 133 | assert!((s.matrix[[0, 0]] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 134 | assert!((s.matrix[[0, 1]] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 135 | assert!((s.matrix[[1, 0]] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 136 | assert!((s.matrix[[1, 1]] - Complex64::new(0.0, 1.0)).norm() < 1e-10); 137 | 138 | // Test application to |0⟩ (should remain |0⟩) 139 | let mut state = State::zero_state(1); 140 | s.apply(&mut state); 141 | assert!((state.probability(0) - 1.0).abs() < 1e-10); 142 | assert!(state.probability(1) < 1e-10); 143 | 144 | // Test application to |1⟩ (should get i|1⟩) 145 | let mut state = State::one_state(1); 146 | s.apply(&mut state); 147 | assert!(state.probability(0) < 1e-10); 148 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 149 | assert!((state.vector[1] - Complex64::new(0.0, 1.0)).norm() < 1e-10); 150 | 151 | // Test S·S = Z 152 | let mut state = State::one_state(1); 153 | s.apply(&mut state); 154 | s.apply(&mut state); 155 | assert!(state.probability(0) < 1e-10); 156 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 157 | assert!((state.vector[1] - Complex64::new(-1.0, 0.0)).norm() < 1e-10); 158 | } 159 | 160 | #[test] 161 | fn test_t_gate() { 162 | // Create T gate 163 | let t = t_gate(); 164 | 165 | // Test matrix representation 166 | assert_eq!(t.matrix.shape(), &[2, 2]); 167 | assert!((t.matrix[[0, 0]] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 168 | 169 | // Test application to |1⟩ (phase should change) 170 | let mut state = State::one_state(1); 171 | t.apply(&mut state); 172 | assert!(state.probability(0) < 1e-10); 173 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 174 | 175 | // Test T·T·T·T = Z 176 | let mut state = State::one_state(1); 177 | t.apply(&mut state); 178 | t.apply(&mut state); 179 | t.apply(&mut state); 180 | t.apply(&mut state); 181 | assert!(state.probability(0) < 1e-10); 182 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 183 | assert!((state.vector[1] - Complex64::new(-1.0, 0.0)).norm() < 1e-10); 184 | } 185 | 186 | #[test] 187 | fn test_rotation_gates() { 188 | // Test RX gate 189 | let rx_pi = rx_gate(PI); 190 | 191 | // RX(π) should be equivalent to X gate 192 | let mut state = State::zero_state(1); 193 | rx_pi.apply(&mut state); 194 | assert!(state.probability(0) < 1e-10); 195 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 196 | 197 | // Test RY gate 198 | let ry_pi = ry_gate(PI); 199 | 200 | // RY(π) should flip bits with a phase 201 | let mut state = State::zero_state(1); 202 | ry_pi.apply(&mut state); 203 | assert!(state.probability(0) < 1e-10); 204 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 205 | 206 | // Test RZ gate 207 | let rz_2pi = rz_gate(2.0 * PI); 208 | 209 | // RZ(2π) should be equivalent to Z gate 210 | let mut state = State::one_state(1); 211 | rz_2pi.apply(&mut state); 212 | assert!(state.probability(0) < 1e-10); 213 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 214 | assert!((state.vector[1] - Complex64::new(-1.0, 0.0)).norm() < 1e-10); 215 | } 216 | 217 | #[test] 218 | fn test_cnot_gate() { 219 | // Create CNOT gate 220 | let cnot = cnot_gate(); 221 | 222 | // Test matrix representation 223 | assert_eq!(cnot.matrix.shape(), &[4, 4]); 224 | 225 | // Set up a 2-qubit circuit with CNOT 226 | let mut circuit = logosq::circuits::Circuit::new(2); 227 | circuit.add_matrix_gate(cnot.matrix.clone(), vec![0, 1], "CNOT"); 228 | 229 | // Test |00⟩ → |00⟩ (no flip when control is 0) 230 | let mut state = State::zero_state(2); 231 | circuit.execute(&mut state); 232 | assert!((state.probability(0) - 1.0).abs() < 1e-10); 233 | assert!(state.probability(1) < 1e-10); 234 | assert!(state.probability(2) < 1e-10); 235 | assert!(state.probability(3) < 1e-10); 236 | 237 | // Test |10⟩ → |11⟩ (flip when control is 1) 238 | let mut state = State::zero_state(2); 239 | let x = x_gate(); 240 | let mut x_circuit = logosq::circuits::Circuit::new(2); 241 | x_circuit.add_single_qubit_gate(x.matrix.clone(), 0, "X"); 242 | x_circuit.execute(&mut state); 243 | 244 | circuit.execute(&mut state); 245 | assert!(state.probability(0) < 1e-10); 246 | assert!(state.probability(1) < 1e-10); 247 | assert!(state.probability(2) < 1e-10); 248 | assert!((state.probability(3) - 1.0).abs() < 1e-10); 249 | 250 | // Test |+0⟩ → Bell state (1/√2)(|00⟩ + |11⟩) 251 | let mut state = State::zero_state(2); 252 | let h = h_gate(); 253 | let mut h_circuit = logosq::circuits::Circuit::new(2); 254 | h_circuit.add_single_qubit_gate(h.matrix.clone(), 0, "H"); 255 | h_circuit.execute(&mut state); 256 | 257 | circuit.execute(&mut state); 258 | assert!((state.probability(0) - 0.5).abs() < 1e-10); 259 | assert!(state.probability(1) < 1e-10); 260 | assert!(state.probability(2) < 1e-10); 261 | assert!((state.probability(3) - 0.5).abs() < 1e-10); 262 | } 263 | 264 | #[test] 265 | fn test_swap_gate() { 266 | // Create SWAP gate 267 | let swap = swap_gate(); 268 | 269 | // Test matrix representation 270 | assert_eq!(swap.matrix.shape(), &[4, 4]); 271 | 272 | // Set up a 2-qubit circuit with SWAP 273 | let mut circuit = logosq::circuits::Circuit::new(2); 274 | circuit.add_matrix_gate(swap.matrix.clone(), vec![0, 1], "SWAP"); 275 | 276 | // Test |01⟩ → |10⟩ (swap states) 277 | let mut state = State::zero_state(2); 278 | let x = x_gate(); 279 | let mut x_circuit = logosq::circuits::Circuit::new(2); 280 | x_circuit.add_single_qubit_gate(x.matrix.clone(), 1, "X"); 281 | x_circuit.execute(&mut state); 282 | 283 | assert!(state.probability(0) < 1e-10); 284 | assert!((state.probability(1) - 1.0).abs() < 1e-10); 285 | assert!(state.probability(2) < 1e-10); 286 | assert!(state.probability(3) < 1e-10); 287 | 288 | circuit.execute(&mut state); 289 | assert!(state.probability(0) < 1e-10); 290 | assert!(state.probability(1) < 1e-10); 291 | assert!((state.probability(2) - 1.0).abs() < 1e-10); 292 | assert!(state.probability(3) < 1e-10); 293 | } 294 | 295 | #[test] 296 | fn test_cz_gate() { 297 | // Create CZ gate 298 | let cz = cz_gate(); 299 | 300 | // Test matrix representation 301 | assert_eq!(cz.matrix.shape(), &[4, 4]); 302 | 303 | // Set up a 2-qubit circuit with CZ 304 | let mut circuit = logosq::circuits::Circuit::new(2); 305 | circuit.add_matrix_gate(cz.matrix.clone(), vec![0, 1], "CZ"); 306 | 307 | // Test |11⟩ → -|11⟩ (phase flip when both qubits are 1) 308 | let mut state = State::zero_state(2); 309 | let x = x_gate(); 310 | let mut x_circuit = logosq::circuits::Circuit::new(2); 311 | x_circuit.add_single_qubit_gate(x.matrix.clone(), 0, "X"); 312 | x_circuit.add_single_qubit_gate(x.matrix.clone(), 1, "X"); 313 | x_circuit.execute(&mut state); 314 | 315 | assert!(state.probability(0) < 1e-10); 316 | assert!(state.probability(1) < 1e-10); 317 | assert!(state.probability(2) < 1e-10); 318 | assert!((state.probability(3) - 1.0).abs() < 1e-10); 319 | 320 | circuit.execute(&mut state); 321 | assert!(state.probability(0) < 1e-10); 322 | assert!(state.probability(1) < 1e-10); 323 | assert!(state.probability(2) < 1e-10); 324 | assert!((state.probability(3) - 1.0).abs() < 1e-10); 325 | assert!((state.vector[3] - Complex64::new(-1.0, 0.0)).norm() < 1e-10); 326 | } 327 | 328 | #[test] 329 | fn test_toffoli_gate() { 330 | // Create Toffoli gate 331 | let toffoli = toffoli_gate(); 332 | 333 | // Test matrix representation 334 | assert_eq!(toffoli.matrix.shape(), &[8, 8]); 335 | 336 | // Set up a 3-qubit circuit with Toffoli 337 | let mut circuit = logosq::circuits::Circuit::new(3); 338 | circuit.add_matrix_gate(toffoli.matrix.clone(), vec![0, 1, 2], "Toffoli"); 339 | 340 | // Test |110⟩ → |111⟩ (flip target when both controls are 1) 341 | let mut state = State::zero_state(3); 342 | let x = x_gate(); 343 | let mut x_circuit = logosq::circuits::Circuit::new(3); 344 | x_circuit.add_single_qubit_gate(x.matrix.clone(), 0, "X"); 345 | x_circuit.add_single_qubit_gate(x.matrix.clone(), 1, "X"); 346 | x_circuit.execute(&mut state); 347 | 348 | // |110⟩ corresponds to binary 110 = decimal 6 349 | assert!(state.probability(0) < 1e-10); 350 | assert!(state.probability(1) < 1e-10); 351 | assert!(state.probability(2) < 1e-10); 352 | assert!(state.probability(3) < 1e-10); 353 | assert!(state.probability(4) < 1e-10); 354 | assert!(state.probability(5) < 1e-10); 355 | assert!((state.probability(6) - 1.0).abs() < 1e-10); 356 | assert!(state.probability(7) < 1e-10); 357 | 358 | circuit.execute(&mut state); 359 | 360 | // |111⟩ corresponds to binary 111 = decimal 7 361 | assert!(state.probability(0) < 1e-10); 362 | assert!(state.probability(1) < 1e-10); 363 | assert!(state.probability(2) < 1e-10); 364 | assert!(state.probability(3) < 1e-10); 365 | assert!(state.probability(4) < 1e-10); 366 | assert!(state.probability(5) < 1e-10); 367 | assert!(state.probability(6) < 1e-10); 368 | assert!((state.probability(7) - 1.0).abs() < 1e-10); 369 | } 370 | -------------------------------------------------------------------------------- /tests/test_normalize.rs: -------------------------------------------------------------------------------- 1 | use logosq::states::State; 2 | use ndarray::Array1; 3 | use num_complex::Complex64; 4 | use std::time::Instant; 5 | 6 | // Define a larger test case for performance comparison 7 | const LARGE_STATE_QUBITS: usize = 20; // 2^20 amplitudes - large enough to see parallelism benefits 8 | 9 | mod tests { 10 | use super::*; 11 | 12 | // Basic normalization test that works with both parallel and non-parallel builds 13 | #[test] 14 | fn test_normalize_correctness() { 15 | // Create a non-normalized state 16 | let vector = Array1::from_vec(vec![ 17 | Complex64::new(2.0, 1.0), 18 | Complex64::new(1.0, 3.0), 19 | Complex64::new(-1.0, 2.0), 20 | Complex64::new(0.5, -0.5), 21 | ]); 22 | 23 | let state = State::new(vector.clone(), Some(2)); 24 | 25 | // Calculate expected normalized values manually 26 | let norm = vector.iter().map(|x| x.norm_sqr()).sum::().sqrt(); 27 | let expected = vector.mapv(|x| x / norm); 28 | 29 | // Check that normalization worked 30 | let state_norm = state.vector.iter().map(|c| c.norm_sqr()).sum::(); 31 | assert!((state_norm - 1.0).abs() < 1e-10, "Normalization failed"); 32 | 33 | // Compare each element with expected value 34 | for i in 0..state.vector.len() { 35 | assert!( 36 | (state.vector[i] - expected[i]).norm() < 1e-10, 37 | "Normalized value differs from expected at index {}", 38 | i 39 | ); 40 | } 41 | 42 | // Test edge cases 43 | 44 | // 1. Already normalized state 45 | let mut state_norm = State::zero_state(2); 46 | let norm_before = state_norm.vector.iter().map(|c| c.norm_sqr()).sum::(); 47 | state_norm.normalize(); // Normalize again 48 | let norm_after = state_norm.vector.iter().map(|c| c.norm_sqr()).sum::(); 49 | 50 | assert!( 51 | (norm_before - norm_after).abs() < 1e-10, 52 | "Normalizing an already normalized state changed it" 53 | ); 54 | 55 | // 2. Zero state 56 | let zero_vector = Array1::zeros(4); 57 | let zero_state = State::new(zero_vector, Some(2)); 58 | 59 | // State should remain zeros 60 | for i in 0..zero_state.vector.len() { 61 | assert!( 62 | zero_state.vector[i].norm() < 1e-10, 63 | "Zero state normalization failed" 64 | ); 65 | } 66 | } 67 | 68 | // Performance test for parallel feature 69 | #[cfg(feature = "parallel")] 70 | #[test] 71 | fn test_normalize_parallel_performance() { 72 | println!("RUNNING WITH PARALLEL FEATURE ENABLED"); 73 | 74 | // Create a large state for testing parallelism 75 | let large_state = State::plus_state(LARGE_STATE_QUBITS); 76 | println!( 77 | "Created state with {} qubits ({} amplitudes)", 78 | LARGE_STATE_QUBITS, 79 | large_state.vector.len() 80 | ); 81 | 82 | // Measure performance 83 | let start = Instant::now(); 84 | let mut state = large_state.clone(); 85 | state.normalize(); 86 | let duration = start.elapsed(); 87 | 88 | println!("Normalization with parallel feature: {:?}", duration); 89 | 90 | // We don't assert anything about performance, just report it 91 | assert!( 92 | state.vector.iter().map(|c| c.norm_sqr()).sum::() - 1.0 < 1e-10, 93 | "Normalized state should have norm 1.0" 94 | ); 95 | } 96 | 97 | // Feature detection test - just reports which implementation is active 98 | #[test] 99 | fn test_feature_detection() { 100 | // This test just reports which feature is active 101 | #[cfg(feature = "parallel")] 102 | println!("Test running with PARALLEL feature ENABLED"); 103 | 104 | #[cfg(not(feature = "parallel"))] 105 | println!("Test running with PARALLEL feature DISABLED"); 106 | 107 | // Create a state and normalize it 108 | let mut state = State::plus_state(4); 109 | let start = Instant::now(); 110 | state.normalize(); 111 | let duration = start.elapsed(); 112 | 113 | println!("Normalization took: {:?}", duration); 114 | } 115 | 116 | // This test compares the normalize implementation to a simple manual normalization 117 | #[test] 118 | fn test_normalize_against_manual() { 119 | // Create random-like state vector 120 | let mut vector = Array1::zeros(16); // 4 qubits 121 | for i in 0..16 { 122 | let re = ((i * 17) % 100) as f64 / 50.0 - 1.0; 123 | let im = ((i * 23) % 100) as f64 / 50.0 - 1.0; 124 | vector[i] = Complex64::new(re, im); 125 | } 126 | 127 | // Create a state with this vector 128 | let state = State::new(vector.clone(), Some(4)); 129 | 130 | // Create a manually normalized version for comparison 131 | let norm = vector.iter().map(|x| x.norm_sqr()).sum::().sqrt(); 132 | let manual_normalized = vector.mapv(|x| x / norm); 133 | 134 | // Compare results 135 | for i in 0..state.vector.len() { 136 | assert!( 137 | (state.vector[i] - manual_normalized[i]).norm() < 1e-10, 138 | "State normalization differs from manual at index {}: {:?} vs {:?}", 139 | i, 140 | state.vector[i], 141 | manual_normalized[i] 142 | ); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tests/test_parallel.rs: -------------------------------------------------------------------------------- 1 | use logosq::states::State; 2 | use ndarray::Array1; 3 | use num_complex::Complex64; 4 | use std::f64::consts::SQRT_2; 5 | 6 | // Import conditionally 7 | #[cfg(feature = "parallel")] 8 | use std::time::Instant; 9 | 10 | mod tests { 11 | use super::*; 12 | 13 | #[test] 14 | fn test_normalize() { 15 | // Test with a non-normalized state 16 | let vector = Array1::from_vec(vec![ 17 | Complex64::new(2.0, 1.0), 18 | Complex64::new(1.0, 3.0), 19 | Complex64::new(-1.0, 2.0), 20 | Complex64::new(0.5, -0.5), 21 | ]); 22 | 23 | // Create two identical states for testing 24 | let state = State::new(vector.clone(), Some(2)); 25 | 26 | // Get the normalized values for comparison 27 | let expected = vector.mapv(|x| x / vector.mapv(|x| x.norm_sqr()).sum().sqrt()); 28 | 29 | // Check that normalization worked 30 | let norm = state.vector.iter().map(|c| c.norm_sqr()).sum::(); 31 | assert!((norm - 1.0).abs() < 1e-10, "Normalization failed"); 32 | 33 | // Check against expected values 34 | for i in 0..state.vector.len() { 35 | assert!( 36 | (state.vector[i] - expected[i]).norm() < 1e-10, 37 | "Normalization result differs from expected at index {}", 38 | i 39 | ); 40 | } 41 | 42 | // Test with an already normalized state 43 | let mut state_norm = State::zero_state(2); 44 | let norm_before = state_norm.vector.iter().map(|c| c.norm_sqr()).sum::(); 45 | state_norm.normalize(); // Call normalize again 46 | let norm_after = state_norm.vector.iter().map(|c| c.norm_sqr()).sum::(); 47 | 48 | assert!( 49 | (norm_before - norm_after).abs() < 1e-10, 50 | "Normalizing an already normalized state changed it" 51 | ); 52 | 53 | // Test with zero state 54 | let zero_vector = Array1::zeros(4); 55 | let zero_state = State::new(zero_vector, Some(2)); 56 | 57 | // State should remain zeros 58 | for i in 0..zero_state.vector.len() { 59 | assert!( 60 | zero_state.vector[i].norm() < 1e-10, 61 | "Zero state normalization failed" 62 | ); 63 | } 64 | } 65 | 66 | #[test] 67 | fn test_inner_product_parallel() { 68 | // Create test states 69 | let state1 = State::zero_state(2); // |00⟩ 70 | let state2 = State::one_state(2); // |11⟩ 71 | let state3 = State::plus_state(2); // |++⟩ 72 | let state4 = State::bell_state(); // (|00⟩ + |11⟩)/√2 73 | 74 | // Test orthogonal states 75 | let prod1 = state1.inner_product_parallel(&state2); 76 | assert!( 77 | prod1.norm() < 1e-10, 78 | "Orthogonal states should have zero inner product" 79 | ); 80 | 81 | // Test with self (should be 1) 82 | let prod2 = state1.inner_product_parallel(&state1); 83 | assert!( 84 | (prod2 - Complex64::new(1.0, 0.0)).norm() < 1e-10, 85 | "Inner product with self should be 1" 86 | ); 87 | 88 | // Test with plus state 89 | let prod3 = state1.inner_product_parallel(&state3); 90 | assert!( 91 | (prod3 - Complex64::new(0.5, 0.0)).norm() < 1e-10, 92 | "Inner product of |00⟩ with |++⟩ should be 0.5" 93 | ); 94 | 95 | // Test Bell state 96 | let prod4 = state1.inner_product_parallel(&state4); 97 | assert!( 98 | (prod4 - Complex64::new(1.0 / SQRT_2, 0.0)).norm() < 1e-10, 99 | "Inner product of |00⟩ with Bell state should be 1/√2" 100 | ); 101 | 102 | // Compare with manual calculation 103 | // Create custom states for comparison 104 | let v1 = Array1::from_vec(vec![ 105 | Complex64::new(0.5, 0.0), 106 | Complex64::new(0.0, 0.5), 107 | Complex64::new(-0.5, 0.0), 108 | Complex64::new(0.0, -0.5), 109 | ]); 110 | 111 | let v2 = Array1::from_vec(vec![ 112 | Complex64::new(0.5, 0.0), 113 | Complex64::new(0.5, 0.0), 114 | Complex64::new(0.5, 0.0), 115 | Complex64::new(0.5, 0.0), 116 | ]); 117 | 118 | let custom1 = State::new(v1, Some(2)); 119 | let custom2 = State::new(v2, Some(2)); 120 | 121 | // Calculate inner product manually 122 | let mut manual_result = Complex64::new(0.0, 0.0); 123 | for i in 0..custom1.vector.len() { 124 | manual_result += custom1.vector[i].conj() * custom2.vector[i]; 125 | } 126 | 127 | let parallel_result = custom1.inner_product_parallel(&custom2); 128 | 129 | assert!( 130 | (manual_result - parallel_result).norm() < 1e-10, 131 | "Parallel inner product doesn't match manual calculation" 132 | ); 133 | } 134 | 135 | #[test] 136 | fn test_measure_shots_parallel() { 137 | // Test with a simple state - |0⟩ 138 | let state0 = State::zero_state(1); 139 | let results0 = state0.measure_shots_parallel(100); 140 | 141 | // All measurements should be 0 142 | assert_eq!(results0.len(), 1, "Zero state should only measure to 0"); 143 | assert_eq!( 144 | results0.get(&0), 145 | Some(&100), 146 | "Zero state measured incorrectly" 147 | ); 148 | 149 | // Test with a simple state - |1⟩ 150 | let state1 = State::one_state(1); 151 | let results1 = state1.measure_shots_parallel(100); 152 | 153 | // All measurements should be 1 154 | assert_eq!(results1.len(), 1, "One state should only measure to 1"); 155 | assert_eq!( 156 | results1.get(&1), 157 | Some(&100), 158 | "One state measured incorrectly" 159 | ); 160 | 161 | // Test with equal superposition - |+⟩ 162 | let state_plus = State::plus_state(1); 163 | let n_shots = 1000; // More shots for statistical significance 164 | let results_plus = state_plus.measure_shots_parallel(n_shots); 165 | 166 | // Should be roughly equal measurements of 0 and 1 167 | assert!( 168 | results_plus.len() <= 2, 169 | "Plus state should only measure to 0 or 1" 170 | ); 171 | 172 | if let (Some(&count0), Some(&count1)) = (results_plus.get(&0), results_plus.get(&1)) { 173 | let ratio = count0 as f64 / n_shots as f64; 174 | assert!( 175 | ratio > 0.3 && ratio < 0.7, 176 | "Measurements of |+⟩ state should be roughly 50/50, got {}/{}", 177 | count0, 178 | count1 179 | ); 180 | } 181 | 182 | // Test with Bell state 183 | let bell = State::bell_state(); 184 | let results_bell = bell.measure_shots_parallel(200); 185 | 186 | // Should only have measurements of |00⟩ and |11⟩ 187 | for &idx in &[1, 2] { 188 | assert!( 189 | results_bell.get(&idx).is_none() || results_bell[&idx] == 0, 190 | "Bell state measured to unexpected state |{:02b}⟩", 191 | idx 192 | ); 193 | } 194 | 195 | // Check that measurements are roughly distributed as expected 196 | let count0 = *results_bell.get(&0).unwrap_or(&0); 197 | let count3 = *results_bell.get(&3).unwrap_or(&0); 198 | let total = count0 + count3; 199 | 200 | assert!( 201 | count0 > 0 && count3 > 0, 202 | "Bell state should measure to both |00⟩ and |11⟩" 203 | ); 204 | let ratio0 = count0 as f64 / total as f64; 205 | assert!( 206 | ratio0 > 0.3 && ratio0 < 0.7, 207 | "Bell state measurements should be roughly balanced" 208 | ); 209 | } 210 | 211 | // Only run the performance test when the parallel feature is enabled 212 | #[test] 213 | fn test_parallel_performance() { 214 | // This is more of a benchmark than a test, but helps verify performance gain 215 | let n_qubits = 16; // 2^16 = 65536 amplitudes 216 | let large_state = State::plus_state(n_qubits); 217 | 218 | // Create two identical large states 219 | let mut state1 = large_state.clone(); 220 | let state2 = large_state.clone(); 221 | 222 | // Test with and without parallel feature 223 | let start = Instant::now(); 224 | state1.normalize(); // Uses parallel implementation when feature is enabled 225 | let time = start.elapsed(); 226 | 227 | // Just log the time, no assertions needed 228 | println!("Normalization of 2^{} state: {:?}", n_qubits, time); 229 | 230 | // Test inner product performance 231 | let start = Instant::now(); 232 | let _ = state1.inner_product_parallel(&state2); 233 | let time = start.elapsed(); 234 | 235 | println!("Inner product of 2^{} states: {:?}", n_qubits, time); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /tests/test_qft.rs: -------------------------------------------------------------------------------- 1 | use approx::assert_relative_eq; 2 | use logosq::algorithms::qft; 3 | use logosq::circuits::Circuit; 4 | use logosq::states::State; 5 | use logosq::vis::circuit_text; 6 | use std::f64::consts::PI; 7 | 8 | mod tests { 9 | use super::*; 10 | 11 | #[test] 12 | fn test_qft_circuit_creation() { 13 | let num_qubits = 3; 14 | let circuit = qft::create_circuit(num_qubits); 15 | 16 | // Check circuit properties 17 | assert_eq!(circuit.num_qubits, num_qubits); 18 | assert!(circuit.name.is_some()); 19 | assert!(circuit 20 | .name 21 | .as_ref() 22 | .unwrap() 23 | .contains("Quantum Fourier Transform")); 24 | 25 | // Verify circuit has operations (specific count depends on implementation) 26 | assert!(!circuit.operations.is_empty()); 27 | println!("QFT Circuit:\n{}", circuit_text(&circuit)); 28 | } 29 | 30 | #[test] 31 | fn test_inverse_qft_circuit_creation() { 32 | let num_qubits = 3; 33 | let circuit = qft::create_inverse_circuit(num_qubits); 34 | 35 | // Check circuit properties 36 | assert_eq!(circuit.num_qubits, num_qubits); 37 | assert!(circuit.name.is_some()); 38 | assert!(circuit.name.unwrap().contains("Inverse")); 39 | 40 | // Verify circuit has operations 41 | assert!(!circuit.operations.is_empty()); 42 | } 43 | 44 | #[test] 45 | fn test_qft_on_zero_state() { 46 | // QFT on |0⟩ should create an equal superposition of all states 47 | let mut state = State::zero_state(2); 48 | qft::apply(&mut state); 49 | 50 | // Expected result: equal superposition with specific phases 51 | let n = state.vector.len(); 52 | for i in 0..n { 53 | assert_relative_eq!( 54 | state.vector[i].norm(), 55 | 1.0 / (n as f64).sqrt(), 56 | epsilon = 1e-5 57 | ); 58 | } 59 | } 60 | 61 | #[test] 62 | fn test_qft_on_basis_state() { 63 | // Create a basis state |1⟩ 64 | let mut state = State::zero_state(1); 65 | let mut circuit = Circuit::new(1); 66 | circuit.x(0); 67 | circuit.execute(&mut state); 68 | 69 | // Apply QFT 70 | qft::apply(&mut state); 71 | 72 | // For 1 qubit, QFT of |1⟩ = |0⟩ - |1⟩)/√2 73 | assert_relative_eq!(state.vector[0].re, 1.0 / 2.0_f64.sqrt(), epsilon = 1e-5); 74 | assert_relative_eq!(state.vector[0].im, 0.0, epsilon = 1e-5); 75 | 76 | assert_relative_eq!(state.vector[1].re, -1.0 / 2.0_f64.sqrt(), epsilon = 1e-5); 77 | assert_relative_eq!(state.vector[1].im, 0.0, epsilon = 1e-5); 78 | } 79 | 80 | #[test] 81 | fn test_qft_inverse_qft_identity() { 82 | // Test that QFT followed by inverse QFT returns the original state 83 | 84 | // Test on a few different states 85 | for n_qubits in 1..=4 { 86 | println!("Testing {}-qubit state", n_qubits); 87 | // Start with a non-trivial state (apply some gates to |0⟩) 88 | let mut state = State::zero_state(n_qubits); 89 | println!("Initial state ({} qubits): ", state.num_qubits); 90 | let mut prep_circuit = Circuit::new(n_qubits); 91 | 92 | // Apply some gates to create a test state 93 | for i in 0..n_qubits { 94 | if i % 2 == 0 { 95 | prep_circuit.h(i); 96 | } else { 97 | prep_circuit.x(i); 98 | } 99 | } 100 | prep_circuit.execute(&mut state); 101 | 102 | // Save original state 103 | let original_state = state.clone(); 104 | 105 | println!("Original state ({} qubits): ", original_state.num_qubits); 106 | 107 | // Apply QFT followed by inverse QFT 108 | qft::apply(&mut state); 109 | println!("Finished QFT and inverse QFT on {} qubits", n_qubits); 110 | 111 | qft::apply_inverse(&mut state); 112 | 113 | // Verify state is the same as original 114 | for i in 0..state.vector.len() { 115 | assert_relative_eq!( 116 | state.vector[i].re, 117 | original_state.vector[i].re, 118 | epsilon = 1e-5 119 | ); 120 | assert_relative_eq!( 121 | state.vector[i].im, 122 | original_state.vector[i].im, 123 | epsilon = 1e-5 124 | ); 125 | } 126 | } 127 | } 128 | 129 | #[test] 130 | fn test_qft_known_transformation() { 131 | // Test QFT on a known state with known output 132 | 133 | // Create state |0⟩ + |1⟩ (equivalent to applying H to |0⟩) 134 | let mut state = State::zero_state(1); 135 | let mut prep_circuit = Circuit::new(1); 136 | prep_circuit.h(0); 137 | prep_circuit.execute(&mut state); 138 | 139 | // Apply QFT 140 | qft::apply(&mut state); 141 | 142 | // Expected: |0⟩ 143 | assert_relative_eq!(state.vector[0].norm(), 1.0, epsilon = 1e-5); 144 | assert_relative_eq!(state.vector[1].norm(), 0.0, epsilon = 1e-5); 145 | } 146 | 147 | #[test] 148 | fn test_qft_multi_qubit_transformation() { 149 | // Test QFT on a 2-qubit state 150 | let mut state = State::zero_state(2); 151 | 152 | // Create state |00⟩ + |01⟩ + |10⟩ + |11⟩ with equal amplitudes 153 | let mut prep_circuit = Circuit::new(2); 154 | prep_circuit.h(0); 155 | prep_circuit.h(1); 156 | prep_circuit.execute(&mut state); 157 | 158 | // Apply QFT 159 | qft::apply(&mut state); 160 | 161 | // Expected result: |00⟩ 162 | assert_relative_eq!(state.vector[0].norm(), 1.0, epsilon = 1e-5); 163 | assert_relative_eq!(state.vector[1].norm(), 0.0, epsilon = 1e-5); 164 | assert_relative_eq!(state.vector[2].norm(), 0.0, epsilon = 1e-5); 165 | assert_relative_eq!(state.vector[3].norm(), 0.0, epsilon = 1e-5); 166 | } 167 | 168 | #[test] 169 | fn test_controlled_phase_gate() { 170 | // Test the controlled phase gate with various angles 171 | 172 | for angle in [PI / 4.0, PI / 2.0, PI] { 173 | let mut circuit = Circuit::new(2); 174 | qft::controlled_phase(&mut circuit, 0, 1, angle); 175 | 176 | // Verify the effect on a superposition state 177 | let mut state = State::zero_state(2); 178 | circuit.h(0); 179 | circuit.h(1); 180 | circuit.execute(&mut state); 181 | 182 | // The transformation should maintain equal probability for all states 183 | // but introduce relative phases 184 | for i in 0..4 { 185 | assert_relative_eq!(state.vector[i].norm(), 0.5, epsilon = 1e-5); 186 | } 187 | } 188 | } 189 | 190 | #[test] 191 | fn test_qft_correctness() { 192 | // Test QFT on a specific 3-qubit input and check expected output 193 | 194 | // Create state |001⟩ 195 | let mut state = State::zero_state(3); 196 | let mut prep_circuit = Circuit::new(3); 197 | prep_circuit.x(2); // Note: qubit 2 is the least significant in the state vector 198 | prep_circuit.execute(&mut state); 199 | 200 | // Apply QFT 201 | qft::apply(&mut state); 202 | 203 | // Expected result: (1/√8) * (|0⟩ + e^(2πi*1/8)|1⟩ + e^(2πi*2/8)|2⟩ + ... + e^(2πi*7/8)|7⟩) 204 | let n = state.vector.len(); 205 | let norm_factor = 1.0 / (n as f64).sqrt(); 206 | 207 | // Check amplitudes - all should be 1/√8 in magnitude 208 | for i in 0..n { 209 | assert_relative_eq!(state.vector[i].norm(), norm_factor, epsilon = 1e-5); 210 | } 211 | 212 | // Check phases - the phase of the i-th amplitude should be 2πi*k/8 213 | // where k is the input state (1 in this case) 214 | let k = 1; 215 | for i in 0..n { 216 | let expected_phase = 2.0 * PI * (i as f64) * (k as f64) / (n as f64); 217 | let actual_phase = state.vector[i].arg(); 218 | 219 | // Normalize phase comparison to account for 2π periodicity 220 | let phase_diff = (expected_phase - actual_phase).abs() % (2.0 * PI); 221 | let normalized_diff = phase_diff.min(2.0 * PI - phase_diff); 222 | 223 | assert_relative_eq!(normalized_diff, 0.0, epsilon = 1e-5); 224 | } 225 | } 226 | 227 | #[test] 228 | fn test_qft_on_zero_states_multiple_sizes() { 229 | // Test QFT on |0⟩ for increasing qubit counts 230 | // For large qubit counts (>8), we'll only verify a subset of amplitudes to save memory and time 231 | 232 | println!("Testing QFT on |0⟩ states with varying qubit counts:"); 233 | 234 | for num_qubits in 1..=10 { 235 | println!(" Testing {}-qubit zero state", num_qubits); 236 | 237 | // Create zero state with specified number of qubits 238 | let mut state = State::zero_state(num_qubits); 239 | 240 | // Apply QFT 241 | qft::apply(&mut state); 242 | 243 | // Expected result: equal superposition with specific phases 244 | let n = state.vector.len(); 245 | let expected_magnitude = 1.0 / (n as f64).sqrt(); 246 | 247 | // For smaller states, verify all amplitudes 248 | if num_qubits <= 8 { 249 | for i in 0..n { 250 | assert_relative_eq!( 251 | state.vector[i].norm(), 252 | expected_magnitude, 253 | epsilon = 1e-5, 254 | max_relative = 1e-4 255 | ); 256 | } 257 | } else { 258 | // For larger states, sample a few amplitudes to verify 259 | let sample_indices = [0, 1, n / 4, n / 2, n - 2, n - 1]; 260 | for &i in &sample_indices { 261 | assert_relative_eq!( 262 | state.vector[i].norm(), 263 | expected_magnitude, 264 | epsilon = 1e-5, 265 | max_relative = 1e-4 266 | ); 267 | } 268 | } 269 | 270 | // Additional test: QFT on |0⟩ should have all real, positive amplitudes 271 | if num_qubits <= 6 { 272 | // Limit detailed phase checking to smaller states 273 | for i in 0..n { 274 | // All amplitudes should be real and positive 275 | assert_relative_eq!(state.vector[i].im, 0.0, epsilon = 1e-5); 276 | assert!(state.vector[i].re > 0.0); 277 | } 278 | } 279 | 280 | println!(" ✓ {}-qubit zero state QFT verified", num_qubits); 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /tests/test_states.rs: -------------------------------------------------------------------------------- 1 | use logosq::states::State; 2 | use logosq::MatrixGate; 3 | use ndarray::{Array1, Array2}; 4 | use num_complex::Complex64; 5 | use std::f64::consts::SQRT_2; 6 | 7 | mod tests { 8 | use super::*; 9 | 10 | #[test] 11 | fn test_state_creation_and_normalization() { 12 | // Create a non-normalized state 13 | let vector = Array1::from_vec(vec![Complex64::new(2.0, 0.0), Complex64::new(0.0, 0.0)]); 14 | 15 | let state = State::new(vector, Some(1)); 16 | 17 | // Should be normalized 18 | assert!((state.vector[0].norm_sqr() - 1.0).abs() < 1e-10); 19 | assert_eq!(state.num_qubits, 1); 20 | 21 | // Test automatic qubit count calculation 22 | let vector = Array1::from_vec(vec![ 23 | Complex64::new(1.0, 0.0), 24 | Complex64::new(1.0, 0.0), 25 | Complex64::new(1.0, 0.0), 26 | Complex64::new(1.0, 0.0), 27 | ]); 28 | 29 | let state = State::new(vector, None); 30 | assert_eq!(state.num_qubits, 2); // 4 = 2^2 amplitudes 31 | 32 | // Test more than 2 qubits 33 | let vector_1 = Array1::from_vec(vec![ 34 | Complex64::new(1.0, 0.0), // |000⟩ 35 | Complex64::new(0.0, 0.0), // |001⟩ 36 | Complex64::new(0.0, 0.0), // |010⟩ 37 | Complex64::new(0.0, 0.0), // |011⟩ 38 | Complex64::new(0.0, 0.0), // |100⟩ 39 | Complex64::new(0.0, 0.0), // |101⟩ 40 | Complex64::new(0.0, 0.0), // |110⟩ 41 | Complex64::new(0.0, 0.0), // |111⟩ 42 | ]); 43 | 44 | let state_1 = State::new(vector_1, Some(3)); 45 | assert!((state_1.vector[0].norm_sqr() - 1.0).abs() < 1e-10); 46 | assert_eq!(state_1.num_qubits, 3); // 8 = 2^3 47 | } 48 | 49 | #[test] 50 | fn test_zero_state() { 51 | let state = State::zero_state(2); 52 | 53 | assert_eq!(state.vector.len(), 4); 54 | assert_eq!(state.num_qubits, 2); 55 | assert!((state.vector[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 56 | for i in 1..4 { 57 | assert!((state.vector[i] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 58 | } 59 | } 60 | 61 | #[test] 62 | fn test_one_state() { 63 | let state = State::one_state(2); 64 | 65 | assert_eq!(state.vector.len(), 4); 66 | assert_eq!(state.num_qubits, 2); 67 | assert!((state.vector[3] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 68 | for i in 0..3 { 69 | assert!((state.vector[i] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 70 | } 71 | } 72 | 73 | #[test] 74 | fn test_plus_state() { 75 | let state = State::plus_state(2); 76 | let expected_amplitude = 0.5; // 1/√4 = 1/2 77 | 78 | assert_eq!(state.vector.len(), 4); 79 | assert_eq!(state.num_qubits, 2); 80 | 81 | for i in 0..4 { 82 | assert!((state.vector[i] - Complex64::new(expected_amplitude, 0.0)).norm() < 1e-10); 83 | } 84 | } 85 | 86 | #[test] 87 | fn test_bell_state() { 88 | let state = State::bell_state(); 89 | let expected_amplitude = 1.0 / SQRT_2; 90 | 91 | assert_eq!(state.vector.len(), 4); 92 | assert_eq!(state.num_qubits, 2); 93 | 94 | assert!((state.vector[0] - Complex64::new(expected_amplitude, 0.0)).norm() < 1e-10); 95 | assert!((state.vector[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 96 | assert!((state.vector[2] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 97 | assert!((state.vector[3] - Complex64::new(expected_amplitude, 0.0)).norm() < 1e-10); 98 | } 99 | 100 | #[test] 101 | fn test_deterministic_measurement() { 102 | // State |0⟩ 103 | let state = State::zero_state(1); 104 | assert_eq!(state.measure(), 0); 105 | 106 | // State |1⟩ 107 | let state = State::one_state(1); 108 | assert_eq!(state.measure(), 1); 109 | } 110 | 111 | #[test] 112 | fn test_measure_qubit() { 113 | // For |0⟩, measuring qubit 0 should always give 0 114 | let mut state = State::zero_state(1); 115 | assert_eq!(state.measure_qubit(0), 0); 116 | 117 | // For |1⟩, measuring qubit 0 should always give 1 118 | let mut state = State::one_state(1); 119 | assert_eq!(state.measure_qubit(0), 1); 120 | 121 | // For |+⟩ state, after measurement, state should collapse to |0⟩ or |1⟩ 122 | let mut state = State::plus_state(1); 123 | let result = state.measure_qubit(0); 124 | assert!(result == 0 || result == 1); 125 | 126 | if result == 0 { 127 | assert!((state.vector[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 128 | assert!((state.vector[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 129 | } else { 130 | assert!((state.vector[0] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 131 | assert!((state.vector[1] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 132 | } 133 | } 134 | 135 | #[test] 136 | fn test_probability() { 137 | // For |0⟩, P(|0⟩) = 1, P(|1⟩) = 0 138 | let state = State::zero_state(1); 139 | assert!((state.probability(0) - 1.0).abs() < 1e-10); 140 | assert!(state.probability(1).abs() < 1e-10); 141 | 142 | // For |+⟩, P(|0⟩) = P(|1⟩) = 0.5 143 | let state = State::plus_state(1); 144 | assert!((state.probability(0) - 0.5).abs() < 1e-10); 145 | assert!((state.probability(1) - 0.5).abs() < 1e-10); 146 | 147 | // For Bell state, P(|00⟩) = P(|11⟩) = 0.5, P(|01⟩) = P(|10⟩) = 0 148 | let state = State::bell_state(); 149 | assert!((state.probability(0) - 0.5).abs() < 1e-10); 150 | assert!(state.probability(1).abs() < 1e-10); 151 | assert!(state.probability(2).abs() < 1e-10); 152 | assert!((state.probability(3) - 0.5).abs() < 1e-10); 153 | } 154 | 155 | #[test] 156 | fn test_tensor_product() { 157 | // |0⟩ ⊗ |0⟩ = |00⟩ 158 | let state0 = State::zero_state(1); 159 | let state00 = state0.tensor_product(&state0); 160 | 161 | assert_eq!(state00.num_qubits, 2); 162 | assert!((state00.vector[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 163 | for i in 1..4 { 164 | assert!((state00.vector[i] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 165 | } 166 | 167 | // |0⟩ ⊗ |1⟩ = |01⟩ 168 | let state0 = State::zero_state(1); 169 | let state1 = State::one_state(1); 170 | let state01 = state0.tensor_product(&state1); 171 | 172 | assert_eq!(state01.num_qubits, 2); 173 | assert!((state01.vector[1] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 174 | for i in [0, 2, 3] { 175 | assert!((state01.vector[i] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 176 | } 177 | } 178 | 179 | #[test] 180 | fn test_gate_application() { 181 | // Create X-gate (Pauli-X, NOT gate) 182 | let x_matrix = Array2::from_shape_vec( 183 | (2, 2), 184 | vec![ 185 | Complex64::new(0.0, 0.0), 186 | Complex64::new(1.0, 0.0), 187 | Complex64::new(1.0, 0.0), 188 | Complex64::new(0.0, 0.0), 189 | ], 190 | ) 191 | .unwrap(); 192 | 193 | let x_gate = MatrixGate { matrix: x_matrix }; 194 | 195 | // Apply X to |0⟩, should get |1⟩ 196 | let mut state = State::zero_state(1); 197 | state.apply_gate(&x_gate); 198 | 199 | assert!((state.vector[0] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 200 | assert!((state.vector[1] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 201 | 202 | // Apply X to |1⟩, should get |0⟩ 203 | let mut state = State::one_state(1); 204 | state.apply_gate(&x_gate); 205 | 206 | assert!((state.vector[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10); 207 | assert!((state.vector[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10); 208 | } 209 | 210 | #[test] 211 | fn test_print() { 212 | let state = State::plus_state(1); 213 | let output = state.print(); 214 | println!("{}", output); 215 | assert!(output.contains("State: 1 qubit")); 216 | // assert!(output.contains("State: 1 qubit")); 217 | assert!(output.contains("|0⟩ : 0.7071+0.0000i (p=0.5000)")); 218 | assert!(output.contains("|1⟩ : 0.7071+0.0000i (p=0.5000)")); 219 | 220 | let state = State::bell_state(); 221 | let output = state.print(); 222 | // Explicit result for the bell state printout 223 | println!("{}", output); 224 | assert!(output.contains("State: 2 qubits")); 225 | assert!(output.contains("|00⟩ : 0.7071+0.0000i (p=0.5000)")); 226 | assert!(output.contains("|11⟩ : 0.7071+0.0000i (p=0.5000)")); 227 | } 228 | } 229 | --------------------------------------------------------------------------------