From 88efa3d6d0b00c1bac48840af9cd3bd0790d144f Mon Sep 17 00:00:00 2001 From: Jonhas Colina Date: Wed, 6 Aug 2025 00:54:47 -0400 Subject: [PATCH 01/12] pcg rng crate instead of performing clib -> rust lib -> python binding conversion --- Cargo.lock | 30 ++---- Cargo.toml | 4 +- Makefile | 33 ------- clib/pecos-rng/CMakeLists.txt | 35 ------- clib/pecos-rng/README.md | 0 clib/pecos-rng/pecos_pcg/__init__.py | 10 -- clib/pecos-rng/pyproject.toml | 22 ----- clib/pecos-rng/src/rng_pcg.c | 96 ------------------- clib/pecos-rng/src/rng_pcg.h | 42 -------- clib/pecos-rng/src/wrapper.cpp | 12 --- crates/pecos-clib-pcg/README.md | 36 ------- crates/pecos-clib-pcg/build.rs | 63 ------------ crates/pecos-clib-pcg/src/lib.rs | 51 ---------- crates/pecos-clib-pcg/tests/pcg_tests.rs | 89 ----------------- .../{pecos-clib-pcg => pecos-rng}/Cargo.toml | 10 +- crates/pecos-rng/src/lib.rs | 71 ++++++++++++++ crates/pecos-rng/src/rng_pcg.rs | 62 ++++++++++++ crates/pecos/Cargo.toml | 3 +- crates/pecos/src/prelude.rs | 5 +- python/pecos-rslib/rust/src/lib.rs | 4 +- .../src/pecos/engines/cvm/rng_model.py | 11 ++- 21 files changed, 158 insertions(+), 531 deletions(-) delete mode 100644 clib/pecos-rng/CMakeLists.txt delete mode 100644 clib/pecos-rng/README.md delete mode 100644 clib/pecos-rng/pecos_pcg/__init__.py delete mode 100644 clib/pecos-rng/pyproject.toml delete mode 100644 clib/pecos-rng/src/rng_pcg.c delete mode 100644 clib/pecos-rng/src/rng_pcg.h delete mode 100644 clib/pecos-rng/src/wrapper.cpp delete mode 100644 crates/pecos-clib-pcg/README.md delete mode 100644 crates/pecos-clib-pcg/build.rs delete mode 100644 crates/pecos-clib-pcg/src/lib.rs delete mode 100644 crates/pecos-clib-pcg/tests/pcg_tests.rs rename crates/{pecos-clib-pcg => pecos-rng}/Cargo.toml (54%) create mode 100644 crates/pecos-rng/src/lib.rs create mode 100644 crates/pecos-rng/src/rng_pcg.rs diff --git a/Cargo.lock b/Cargo.lock index c111a59dc..2f3cc7037 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1756,13 +1756,13 @@ name = "pecos" version = "0.1.1" dependencies = [ "log", - "pecos-clib-pcg", "pecos-core", "pecos-engines", "pecos-phir", "pecos-qasm", "pecos-qir", "pecos-qsim", + "pecos-rng", "serde_json", "tempfile", ] @@ -1791,14 +1791,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "pecos-clib-pcg" -version = "0.1.1" -dependencies = [ - "cc", - "ureq", -] - [[package]] name = "pecos-core" version = "0.1.1" @@ -1921,6 +1913,13 @@ dependencies = [ "rand_chacha", ] +[[package]] +name = "pecos-rng" +version = "0.1.1" +dependencies = [ + "pyo3", +] + [[package]] name = "pecos-rslib" version = "0.1.1" @@ -2993,19 +2992,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" -[[package]] -name = "ureq" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" -dependencies = [ - "base64", - "log", - "native-tls", - "once_cell", - "url", -] - [[package]] name = "url" version = "2.5.4" diff --git a/Cargo.toml b/Cargo.toml index 04e088be5..ab8017b0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ default-members = [ "crates/pecos-phir", "crates/pecos-qir", "crates/pecos-qec", - "crates/pecos-clib-pcg", + "crates/pecos-rng", "crates/pecos", "crates/pecos-cli", "python/pecos-rslib/rust", @@ -98,7 +98,7 @@ pecos-phir = { version = "0.1.1", path = "crates/pecos-phir" } pecos-engines = { version = "0.1.1", path = "crates/pecos-engines" } pecos-qir = { version = "0.1.1", path = "crates/pecos-qir" } pecos-qec = { version = "0.1.1", path = "crates/pecos-qec" } -pecos-clib-pcg = { version = "0.1.1", path = "crates/pecos-clib-pcg" } +pecos-rng = { version = "0.1.0", path = "crates/pecos-rng" } pecos = { version = "0.1.1", path = "crates/pecos" } pecos-cli = { version = "0.1.1", path = "crates/pecos-cli" } pecos-rslib = { version = "0.1.1", path = "python/pecos-rslib/rust" } diff --git a/Makefile b/Makefile index db7f8e790..99189f037 100644 --- a/Makefile +++ b/Makefile @@ -24,42 +24,27 @@ installreqs: ## Install Python project requirements to root .venv uv sync --project .; \ fi -.PHONY: buildrng -buildrng: - @echo "Skipping RNG library build (using Rust fallback)..." - # @echo "Building and installing RNG library..." - # uv pip install nanobind - # @if [ -z "$(CC)" ] && [ -z "$(CXX)" ]; then \ - # cd clib/pecos-rng && CC=gcc CXX=g++ uv pip install -e .; \ - # else \ - # cd clib/pecos-rng && uv pip install -e .; \ - # fi - # Building development environments # --------------------------------- .PHONY: build build: installreqs ## Compile and install for development @unset CONDA_PREFIX && cd python/pecos-rslib/ && uv run maturin develop --uv - $(MAKE) buildrng @unset CONDA_PREFIX && uv pip install -e "./python/quantum-pecos[all]" .PHONY: build-basic build-basic: installreqs ## Compile and install for development but do not include install extras @unset CONDA_PREFIX && cd python/pecos-rslib/ && uv run maturin develop --uv - $(MAKE) buildrng @unset CONDA_PREFIX && uv pip install -e ./python/quantum-pecos .PHONY: build-release build-release: installreqs ## Build a faster version of binaries @unset CONDA_PREFIX && cd python/pecos-rslib/ && uv run maturin develop --uv --release - $(MAKE) buildrng @unset CONDA_PREFIX && uv pip install -e "./python/quantum-pecos[all]" .PHONY: build-native build-native: installreqs ## Build a faster version of binaries with native CPU optimization @unset CONDA_PREFIX && cd python/pecos-rslib/ && RUSTFLAGS='-C target-cpu=native' \ && uv run maturin develop --uv --release - $(MAKE) buildrng @unset CONDA_PREFIX && uv pip install -e "./python/quantum-pecos[all]" # Documentation @@ -228,12 +213,6 @@ clean-unix: @find . -type d -name "junit" -exec rm -rf {} + @find python -name "*.so" -delete @find python -name "*.pyd" -delete - @# Clean clib build artifacts - @find clib -type d -name "build" -exec rm -rf {} + - @find clib -type d -name "dist" -exec rm -rf {} + - @find clib -type d -name "*.egg-info" -exec rm -rf {} + - @find clib -type d -name ".venv" -exec rm -rf {} + - @find clib -name "uv.lock" -delete @# Clean all target directories in crates (in case they were built independently) @find crates -type d -name "target" -exec rm -rf {} + @find python -type d -name "target" -exec rm -rf {} + @@ -255,12 +234,6 @@ clean-windows-ps: @powershell -Command "Get-ChildItem -Path . -Recurse -Directory -Filter '.hypothesis' | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue" @powershell -Command "Get-ChildItem -Path . -Recurse -Directory -Filter 'junit' | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue" @powershell -Command "Get-ChildItem -Path python -Recurse -File -Include '*.so','*.pyd' | Remove-Item -Force -ErrorAction SilentlyContinue" - @# Clean clib build artifacts - @powershell -Command "Get-ChildItem -Path clib -Recurse -Directory -Filter 'build' | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue" - @powershell -Command "Get-ChildItem -Path clib -Recurse -Directory -Filter 'dist' | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue" - @powershell -Command "Get-ChildItem -Path clib -Recurse -Directory -Filter '*.egg-info' | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue" - @powershell -Command "Get-ChildItem -Path clib -Recurse -Directory -Filter '.venv' | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue" - @powershell -Command "Get-ChildItem -Path clib -Recurse -File -Filter 'uv.lock' | Remove-Item -Force -ErrorAction SilentlyContinue" @# Clean all target directories in crates @powershell -Command "Get-ChildItem -Path crates -Recurse -Directory -Filter 'target' | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue" @powershell -Command "Get-ChildItem -Path python -Recurse -Directory -Filter 'target' | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue" @@ -281,12 +254,6 @@ clean-windows-cmd: -@for /f "delims=" %%d in ('dir /s /b /ad .hypothesis 2^>nul') do @rd /s /q "%%d" 2>nul -@for /f "delims=" %%d in ('dir /s /b /ad junit 2^>nul') do @rd /s /q "%%d" 2>nul -@for /f "delims=" %%f in ('dir /s /b python\*.so python\*.pyd 2^>nul') do @del "%%f" 2>nul - -@REM Clean clib build artifacts - -@for /f "delims=" %%d in ('dir /s /b /ad clib\build 2^>nul') do @rd /s /q "%%d" 2>nul - -@for /f "delims=" %%d in ('dir /s /b /ad clib\dist 2^>nul') do @rd /s /q "%%d" 2>nul - -@for /f "delims=" %%d in ('dir /s /b /ad clib\*.egg-info 2^>nul') do @rd /s /q "%%d" 2>nul - -@for /f "delims=" %%d in ('dir /s /b /ad clib\.venv 2^>nul') do @rd /s /q "%%d" 2>nul - -@for /f "delims=" %%f in ('dir /s /b clib\uv.lock 2^>nul') do @del "%%f" 2>nul -@REM Clean all target directories in crates -@for /f "delims=" %%d in ('dir /s /b /ad crates\target 2^>nul') do @rd /s /q "%%d" 2>nul -@for /f "delims=" %%d in ('dir /s /b /ad python\target 2^>nul') do @rd /s /q "%%d" 2>nul diff --git a/clib/pecos-rng/CMakeLists.txt b/clib/pecos-rng/CMakeLists.txt deleted file mode 100644 index e727b747a..000000000 --- a/clib/pecos-rng/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -# CMakeLists.txt -cmake_minimum_required(VERSION 3.15...3.27) -project(pecos_rng LANGUAGES C CXX) -# Don't force universal binaries in CI - let the system decide -# set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") - - -if (CMAKE_VERSION VERSION_LESS 3.18) - set(DEV_MODULE Development) -else() - set(DEV_MODULE Development.Module) -endif() - -# Find Python (for Python 3.13) -find_package(Python REQUIRED COMPONENTS Interpreter ${DEV_MODULE}) - -# Detect the installed nanobind package and import it into CMake -# This executes a Python command to get the nanobind CMake directory -execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE - OUTPUT_VARIABLE nanobind_ROOT -) - -set(CMAKE_PREFIX_PATH "${nanobind_ROOT}" ${CMAKE_PREFIX_PATH}) - -# This sets up nanobind -find_package(nanobind CONFIG REQUIRED) - -# Add your C source file as a library -add_library(rng_pcg STATIC src/rng_pcg.c) # Compile test.c as a static library -nanobind_add_module(pecos_rng src/wrapper.cpp) -target_link_libraries(pecos_rng PRIVATE rng_pcg) -# Install the module so it can be found by pip install -install(TARGETS pecos_rng DESTINATION "pecos_pcg") diff --git a/clib/pecos-rng/README.md b/clib/pecos-rng/README.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/clib/pecos-rng/pecos_pcg/__init__.py b/clib/pecos-rng/pecos_pcg/__init__.py deleted file mode 100644 index 5580f3ca4..000000000 --- a/clib/pecos-rng/pecos_pcg/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Python Package responsible for generating random numbers using pcg_rng.""" - -from .pecos_rng import ( - pcg32_boundedrand, - pcg32_frandom, - pcg32_random, - pcg32_srandom, -) - -__all__ = ["pcg32_boundedrand", "pcg32_frandom", "pcg32_random", "pcg32_srandom"] diff --git a/clib/pecos-rng/pyproject.toml b/clib/pecos-rng/pyproject.toml deleted file mode 100644 index 278a330d5..000000000 --- a/clib/pecos-rng/pyproject.toml +++ /dev/null @@ -1,22 +0,0 @@ -[build-system] -requires = ["scikit-build-core[pyproject]", "nanobind"] -build-backend = "scikit_build_core.build" - -[project] -name = "pecos-pcg" -version = "0.6.0.dev8" -description = "A Python package with a nanobind-based C++ extension" -license = { text = "MIT" } -readme = "README.md" -requires-python = ">=3.8" -classifiers = [ - "Programming Language :: Python :: 3", - "Programming Language :: C++", - "License :: OSI Approved :: MIT License" -] - -[tool.scikit-build] -wheel.packages = ["pecos_pcg"] -# Optional: set cmake minimum version or other CMake arguments -cmake.minimum-version = "3.18" -cmake.verbose = true diff --git a/clib/pecos-rng/src/rng_pcg.c b/clib/pecos-rng/src/rng_pcg.c deleted file mode 100644 index 8ddb23d2e..000000000 --- a/clib/pecos-rng/src/rng_pcg.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * PCG Random Number Generation for C. - * - * Copyright 2014 Melissa O'Neill - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * For additional information about the PCG random number generation scheme, - * including its license and other licensing options, visit - * - * http://www.pcg-random.org - */ - -#include -#include "rng_pcg.h" - -// RNG state structure -typedef struct pcg_state_setseq_64 { - uint64_t state; - uint64_t inc; -} pcg32_random_t; - -// global RNG state -static pcg32_random_t pcg32_global = { - 0x853c49e6748fea9bULL, - 0xda3e39cb94b95bdbULL -}; - -// default multi[plier] -#define PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL - -// helper functions -static inline uint32_t pcg_rotr_32(uint32_t value, unsigned int urot) { - int rot = (int)urot; - return (value >> rot) | (value << ((-rot) & 31)); -} - -static inline void pcg_setseq_64_step_r(pcg32_random_t* rng) { - rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_64 + rng->inc; -} - -static inline uint32_t pcg_output_xsh_rr_64_32(uint64_t state) { - return pcg_rotr_32(((state >> 18u) ^ state) >> 27u, state >> 59u); -} - -static inline uint32_t pcg32_random_r(pcg32_random_t* rng) { - const uint64_t oldstate = rng->state; - pcg_setseq_64_step_r(rng); - return pcg_output_xsh_rr_64_32(oldstate); -} - -static inline uint32_t pcg32_boundedrand_r(pcg32_random_t* rng, uint32_t ubound) { - int32_t bound = (int32_t)ubound; - uint32_t threshold = -bound % bound; - for (;;) { - const uint32_t r = pcg32_random_r(rng); - if (r >= threshold) - return r % bound; - } -} - -static inline void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq) { - rng->state = 0U; - rng->inc = (initseq << 1u) | 1u; - pcg_setseq_64_step_r(rng); - rng->state += initstate; - pcg_setseq_64_step_r(rng); -} - -// public interface to RNG - -uint32_t pcg32_random() { - return pcg32_random_r(&pcg32_global); -} - -uint32_t pcg32_boundedrand(uint32_t bound) { - return pcg32_boundedrand_r(&pcg32_global, bound); -} - -double pcg32_frandom() { - return ldexp(pcg32_random(), -32); -} - -void pcg32_srandom(uint64_t seq) { - pcg32_srandom_r(&pcg32_global, 42u, seq); -} diff --git a/clib/pecos-rng/src/rng_pcg.h b/clib/pecos-rng/src/rng_pcg.h deleted file mode 100644 index 5cfb759f5..000000000 --- a/clib/pecos-rng/src/rng_pcg.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * PCG Random Number Generation for C. - * - * Copyright 2014 Melissa O'Neill - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * For additional information about the PCG random number generation scheme, - * including its license and other licensing options, visit - * - * http://www.pcg-random.org - */ - -#pragma once - -#include - -#if __cplusplus -extern "C" { -#endif - -uint32_t pcg32_random(); - -uint32_t pcg32_boundedrand(uint32_t bound); - -double pcg32_frandom(); - -void pcg32_srandom(uint64_t seq); - -#if __cplusplus -} -#endif diff --git a/clib/pecos-rng/src/wrapper.cpp b/clib/pecos-rng/src/wrapper.cpp deleted file mode 100644 index 94a258808..000000000 --- a/clib/pecos-rng/src/wrapper.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include "rng_pcg.h" - -namespace nb = nanobind; -using namespace nb::literals; - -NB_MODULE(pecos_rng, m) { - m.def("pcg32_random", &pcg32_random,"generate random numbers"); - m.def("pcg32_frandom", &pcg32_frandom, "Generate random floating point number"); - m.def("pcg32_boundedrand", &pcg32_boundedrand, "Generate bounded random number"); - m.def("pcg32_srandom", &pcg32_srandom, "seeded random"); -} diff --git a/crates/pecos-clib-pcg/README.md b/crates/pecos-clib-pcg/README.md deleted file mode 100644 index 868c39dbf..000000000 --- a/crates/pecos-clib-pcg/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# pecos-clib-pcg - -Rust wrapper for the PCG C library used in PECOS. - -This crate provides safe Rust bindings to the PCG random number generator implementation in C. - -## Features - -- `pcg32_random()` - Generate a 32-bit random number -- `pcg32_boundedrand(bound)` - Generate a random number in range [0, bound) -- `pcg32_frandom()` - Generate a random floating-point number in [0, 1) -- `pcg32_srandom(seed)` - Set the random seed - -## Usage - -This crate is primarily used internally by PECOS and exposed through the main `pecos` crate's prelude. - -```rust -use pecos_clib_pcg::{random, boundedrand, frandom, srandom}; - -// Set seed for reproducibility -srandom(12345); - -// Generate random values -let r1 = random(); // 32-bit random number -let r2 = boundedrand(100); // Random number in [0, 100) -let r3 = frandom(); // Random float in [0, 1) -``` - -## Implementation - -This crate uses the `cc` build dependency to compile the C implementation of PCG32 (64-bit state, 32-bit output). -When building from the PECOS workspace, it uses the local C source files. When used as a dependency from crates.io, -it automatically downloads the C source files from the PECOS GitHub repository. - -The PCG implementation is based on the work by Melissa E. O'Neill. For more information, visit http://www.pcg-random.org diff --git a/crates/pecos-clib-pcg/build.rs b/crates/pecos-clib-pcg/build.rs deleted file mode 100644 index 7bf049bd1..000000000 --- a/crates/pecos-clib-pcg/build.rs +++ /dev/null @@ -1,63 +0,0 @@ -use cc::Build; -use std::env; -use std::fs; -use std::path::{Path, PathBuf}; - -// TODO: Should probably just vendor the C code into the Rust crate... -fn main() { - let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - - // Try local path first (for development) - let local_clib_path = manifest_dir - .parent() - .unwrap() - .parent() - .unwrap() - .join("clib") - .join("pecos-rng"); - - let src_path = if local_clib_path.exists() { - // Development: use local files - let src = local_clib_path.join("src"); - println!("cargo:rerun-if-changed={}", src.join("rng_pcg.c").display()); - println!("cargo:rerun-if-changed={}", src.join("rng_pcg.h").display()); - src - } else { - // Published crate: download from GitHub - let pcg_dir = out_dir.join("pcg"); - fs::create_dir_all(&pcg_dir).unwrap(); - - let commit = "95a6ddbdf85ad7bcf8b9133aa2552f3f1ae7da84"; - let base_url = format!( - "https://raw.githubusercontent.com/PECOS-packages/PECOS/{commit}/clib/pecos-rng/src" - ); - - // Download files if they don't exist - download_if_needed(&pcg_dir.join("rng_pcg.c"), &format!("{base_url}/rng_pcg.c")); - download_if_needed(&pcg_dir.join("rng_pcg.h"), &format!("{base_url}/rng_pcg.h")); - - pcg_dir - }; - - Build::new() - .file(src_path.join("rng_pcg.c")) - .include(&src_path) - .compile("pecos_pcg"); -} - -fn download_if_needed(path: &Path, url: &str) { - if !path.exists() { - println!("cargo:warning=Downloading {} to {}", url, path.display()); - - let response = ureq::get(url) - .call() - .unwrap_or_else(|e| panic!("Failed to download {url}: {e}")); - - let mut file = fs::File::create(path) - .unwrap_or_else(|e| panic!("Failed to create {}: {}", path.display(), e)); - - std::io::copy(&mut response.into_reader(), &mut file) - .unwrap_or_else(|e| panic!("Failed to write {}: {}", path.display(), e)); - } -} diff --git a/crates/pecos-clib-pcg/src/lib.rs b/crates/pecos-clib-pcg/src/lib.rs deleted file mode 100644 index f44696de9..000000000 --- a/crates/pecos-clib-pcg/src/lib.rs +++ /dev/null @@ -1,51 +0,0 @@ -// FFI bindings to the C PCG library -unsafe extern "C" { - fn pcg32_random() -> u32; - fn pcg32_boundedrand(bound: u32) -> u32; - fn pcg32_frandom() -> f64; - fn pcg32_srandom(seq: u64); -} - -// Rust wrapper functions with safe interfaces -#[must_use] -pub fn random() -> u32 { - unsafe { pcg32_random() } -} - -#[must_use] -pub fn boundedrand(bound: u32) -> u32 { - unsafe { pcg32_boundedrand(bound) } -} - -#[must_use] -pub fn frandom() -> f64 { - unsafe { pcg32_frandom() } -} - -pub fn srandom(seq: u64) { - unsafe { pcg32_srandom(seq) } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_pcg_functions() { - // Set seed - srandom(12345); - - // Test basic random - let r1 = random(); - assert!(r1 > 0); - - // Test bounded random - let bound = 100; - let r2 = boundedrand(bound); - assert!(r2 < bound); - - // Test float random - let r3 = frandom(); - assert!((0.0..1.0).contains(&r3)); - } -} diff --git a/crates/pecos-clib-pcg/tests/pcg_tests.rs b/crates/pecos-clib-pcg/tests/pcg_tests.rs deleted file mode 100644 index f687ef5b3..000000000 --- a/crates/pecos-clib-pcg/tests/pcg_tests.rs +++ /dev/null @@ -1,89 +0,0 @@ -use pecos_clib_pcg::{boundedrand, frandom, random, srandom}; - -#[test] -fn test_pcg_random_generates_values() { - // Test that random() generates different values - let val1 = random(); - let val2 = random(); - - // It's extremely unlikely that two consecutive calls return the same value - // (probability is 1 in 2^32) - assert_ne!( - val1, val2, - "Two consecutive random() calls should generate different values" - ); -} - -#[test] -fn test_pcg_bounded_random() { - // Test bounded random with various bounds - let bounds = [1, 2, 10, 100, 1000]; - - for bound in bounds { - for _ in 0..10 { - let val = boundedrand(bound); - assert!( - val < bound, - "boundedrand({bound}) returned {val}, which is >= {bound}" - ); - } - } -} - -#[test] -fn test_pcg_frandom_range() { - // Test that frandom returns values in [0.0, 1.0) - for _ in 0..100 { - let val = frandom(); - assert!(val >= 0.0, "frandom() returned {val}, which is < 0.0"); - assert!(val < 1.0, "frandom() returned {val}, which is >= 1.0"); - } -} - -#[test] -fn test_pcg_seeding() { - // Test that seeding produces deterministic sequences - // Note: PCG uses global state, so we test determinism directly - - // Test that the same seed produces the same first few values - srandom(12345); - let first_val_1 = random(); - let second_val_1 = random(); - - srandom(12345); - let first_val_2 = random(); - let second_val_2 = random(); - - assert_eq!( - first_val_1, first_val_2, - "First value after seeding should be deterministic" - ); - assert_eq!( - second_val_1, second_val_2, - "Second value after seeding should be deterministic" - ); - - // Test that different seeds produce different values - srandom(54321); - let different_first = random(); - - assert_ne!( - first_val_1, different_first, - "Different seeds should produce different values" - ); -} - -#[test] -fn test_pcg_deterministic_behavior() { - // Test that the RNG is deterministic after seeding - srandom(999); - let first_value = random(); - - srandom(999); - let second_value = random(); - - assert_eq!( - first_value, second_value, - "First value after seeding should be deterministic" - ); -} diff --git a/crates/pecos-clib-pcg/Cargo.toml b/crates/pecos-rng/Cargo.toml similarity index 54% rename from crates/pecos-clib-pcg/Cargo.toml rename to crates/pecos-rng/Cargo.toml index 259fd8bbc..3776d871b 100644 --- a/crates/pecos-clib-pcg/Cargo.toml +++ b/crates/pecos-rng/Cargo.toml @@ -1,21 +1,17 @@ [package] -name = "pecos-clib-pcg" +name = "pecos-rng" version.workspace = true edition.workspace = true +readme.workspace = true authors.workspace = true homepage.workspace = true repository.workspace = true license.workspace = true keywords.workspace = true categories.workspace = true -description = "PCG RNG C library wrapper for PECOS" -readme = "README.md" [dependencies] - -[build-dependencies] -cc.workspace = true -ureq = { version = "2.9", default-features = false, features = ["native-tls"] } +pyo3 = { workspace=true, features = ["extension-module", "abi3-py310", "generate-import-lib"] } [lints] workspace = true diff --git a/crates/pecos-rng/src/lib.rs b/crates/pecos-rng/src/lib.rs new file mode 100644 index 000000000..1a22dfb93 --- /dev/null +++ b/crates/pecos-rng/src/lib.rs @@ -0,0 +1,71 @@ +pub mod rng_pcg; + +// use rng_pcg::{PCGRandom}; +use pyo3::types::PyModule; +use pyo3::prelude::*; + +use crate::rng_pcg::PCGRandom; + + +#[pyclass] +pub struct RngPcg { + global_state: PCGRandom +} + +#[pymethods] +impl RngPcg { + #[new] + pub fn new() -> RngPcg { + RngPcg { global_state: PCGRandom::init_global_state() } + } + + pub fn random(&mut self) -> u32 { + PCGRandom::pcg32_random_r(&mut self.global_state) + } + + pub fn boundedrand(&mut self, bound: u32) -> u32 { + PCGRandom::pcg32_boundedrand_r(&mut self.global_state, bound) + } + + pub fn frandom(&mut self) -> f64 { + let random = self.random() as f64; + let exp: i32 = -32; + random * 2f64.powi(exp) + } + + pub fn srandom(&mut self, seq: u64) { + PCGRandom::pcg32_srandom_r(&mut self.global_state, 42_u64, seq); + } +} + +#[pymodule] +fn rng_pcg_py(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pcg_functions() { + let mut pcg = RngPcg::new(); + // Set seed + pcg.srandom(15); + + // Test basic random + let r1 = pcg.random(); + assert!(r1 > 0); + + // Test bounded random + let bound = 100; + let r2 = pcg.boundedrand(bound); + assert!(r2 < bound); + + // Test float random + let r3 = pcg.frandom(); + assert!((0.0..1.0).contains(&r3)); + } +} \ No newline at end of file diff --git a/crates/pecos-rng/src/rng_pcg.rs b/crates/pecos-rng/src/rng_pcg.rs new file mode 100644 index 000000000..e8aa31cd6 --- /dev/null +++ b/crates/pecos-rng/src/rng_pcg.rs @@ -0,0 +1,62 @@ +pub(crate) struct PCGRandom { + pub(crate) state: u64, + inc: u64, +} + +impl PCGRandom { + pub(crate) fn init_global_state() -> PCGRandom { + PCGRandom { + state: 0x853c49e6748fea9b, + inc: 0xda3e39cb94b95bdb, + } + } + + #[inline(always)] + pub(crate) fn pcg_rotr(value: u32, urot: u32) -> u32 { + let rot = urot as i32; + (value >> rot) | (value << ((-rot) & 31)) + } + + #[inline(always)] + pub(crate) fn pcg_setseq_64_step_r(rng: &mut PCGRandom) { + const PCG_DEFAULT_MULTIPLIER_64: u64 = 6364136223846793005; + rng.state = rng.state + .wrapping_mul(PCG_DEFAULT_MULTIPLIER_64) + .wrapping_add(rng.inc); + } + + #[inline(always)] + pub(crate) fn pcg_output_xsh(state: u64) -> u32 { + let value = ((state >> 18) ^ state) >> 27; + let urot = state >> 59; + PCGRandom::pcg_rotr(value as u32, urot as u32) + } + + #[inline(always)] + pub(crate) fn pcg32_random_r(rng: &mut PCGRandom) -> u32 { + let old_state: u64 = rng.state; + PCGRandom::pcg_setseq_64_step_r(rng); + return PCGRandom::pcg_output_xsh(old_state); + } + + #[inline(always)] + pub(crate) fn pcg32_boundedrand_r(rng: &mut PCGRandom, ubound: u32) -> u32 { + let bound: i32 = ubound as i32; + let threshold: u32 = (-bound % bound) as u32; + loop { + let random: u32 = PCGRandom::pcg32_random_r(rng); + if random >= threshold { + return random % bound as u32; + } + } + } + + #[inline(always)] + pub(crate) fn pcg32_srandom_r(rng: &mut PCGRandom, initstate: u64, initseq: u64) { + rng.state = 0_u64; + rng.inc = (initseq << 1_u64) | 1_u64; + PCGRandom::pcg_setseq_64_step_r(rng); + rng.state += initstate; + PCGRandom::pcg_setseq_64_step_r(rng); + } +} diff --git a/crates/pecos/Cargo.toml b/crates/pecos/Cargo.toml index 8aa18be77..13c2cfdb5 100644 --- a/crates/pecos/Cargo.toml +++ b/crates/pecos/Cargo.toml @@ -22,7 +22,7 @@ pecos-engines.workspace = true pecos-qasm.workspace = true pecos-phir.workspace = true pecos-qir.workspace = true -pecos-clib-pcg.workspace = true +pecos-rng.workspace = true log.workspace = true serde_json.workspace = true @@ -35,6 +35,7 @@ pecos-engines.workspace = true pecos-qasm.workspace = true pecos-qir.workspace = true pecos-phir.workspace = true +pecos-rng.workspace = true log.workspace = true serde_json.workspace = true diff --git a/crates/pecos/src/prelude.rs b/crates/pecos/src/prelude.rs index cd15fed83..47c944b2a 100644 --- a/crates/pecos/src/prelude.rs +++ b/crates/pecos/src/prelude.rs @@ -67,7 +67,6 @@ pub use pecos_qir::setup_qir_engine; pub use pecos_engines::run_sim; // Re-export PCG RNG functions -pub use pecos_clib_pcg::{ - boundedrand as pcg32_boundedrand, frandom as pcg32_frandom, random as pcg32_random, - srandom as pcg32_srandom, +pub use pecos_rng::{ + RngPcg }; diff --git a/python/pecos-rslib/rust/src/lib.rs b/python/pecos-rslib/rust/src/lib.rs index b8561b975..e03e82c45 100644 --- a/python/pecos-rslib/rust/src/lib.rs +++ b/python/pecos-rslib/rust/src/lib.rs @@ -19,7 +19,7 @@ mod byte_message_bindings; mod engine_bindings; mod noise_helpers; -mod pcg_bindings; +// mod pcg_bindings; pub mod phir_bridge; mod qasm_sim_bindings; mod sparse_sim; @@ -50,6 +50,6 @@ fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { // Register QASM simulation functions qasm_sim_bindings::register_qasm_sim_module(m)?; - pcg_bindings::create_pcg_module(m)?; + // pcg_bindings::create_pcg_module(m)?; Ok(()) } diff --git a/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py b/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py index 028acc8e7..45f6e8700 100644 --- a/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py +++ b/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py @@ -9,7 +9,7 @@ from __future__ import annotations try: - from pecos_pcg import pecos_rng + from pecos_rng import RngPcg except ImportError: from pecos_rslib._pecos_rslib import pcg as pecos_rng @@ -30,6 +30,7 @@ def __init__( self.current_bound = current_bound self.count = 0 self.seed = self.set_seed(seed) + self.pcg = RngPcg() def __str__(self) -> str: """Returns the str representation of the model.""" @@ -38,7 +39,7 @@ def __str__(self) -> str: def set_seed(self, seed: int) -> None: """Setting the seed for generating random numbers.""" self.seed = seed - pecos_rng.pcg32_srandom(seed) + self.pcg.srandom(seed) def set_bound(self, bound: int) -> None: """Setting the current bound for generating random numbers.""" @@ -47,9 +48,9 @@ def set_bound(self, bound: int) -> None: def rng_random(self) -> int: """Generating a random number and keeping track of how many we have generated.""" if self.current_bound == 0: - rng_num = pecos_rng.pcg32_random() + rng_num = self.pcg.random() else: - rng_num = pecos_rng.pcg32_boundedrand(self.current_bound) + rng_num = self.pcg.boundedrand(self.current_bound) self.count += 1 return rng_num @@ -62,7 +63,7 @@ def set_index(self, index: int) -> None: error_msg = "rngindex called after specified already generated" raise BufferError(error_msg) while self.count < index: - self.rng_random() + self.pcg.random() def extract_val(self, param: str, output: dict) -> int: """Responsible for extracting the value of interest depending on the type of the parameter being passed in.""" From d4d6bee5edcdba654c307605f6524ac18e432b00 Mon Sep 17 00:00:00 2001 From: Jonhas Colina Date: Wed, 6 Aug 2025 01:17:00 -0400 Subject: [PATCH 02/12] modified test cases to account for new rng lib --- crates/pecos-rng/src/lib.rs | 1 + crates/pecos-rng/src/rng_pcg.rs | 1 + crates/pecos-rng/tests/pcg_test.rs | 257 +++++++++++++++++++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 crates/pecos-rng/tests/pcg_test.rs diff --git a/crates/pecos-rng/src/lib.rs b/crates/pecos-rng/src/lib.rs index 1a22dfb93..685078a3e 100644 --- a/crates/pecos-rng/src/lib.rs +++ b/crates/pecos-rng/src/lib.rs @@ -8,6 +8,7 @@ use crate::rng_pcg::PCGRandom; #[pyclass] +#[derive(Clone, Copy)] pub struct RngPcg { global_state: PCGRandom } diff --git a/crates/pecos-rng/src/rng_pcg.rs b/crates/pecos-rng/src/rng_pcg.rs index e8aa31cd6..ffb3cbda8 100644 --- a/crates/pecos-rng/src/rng_pcg.rs +++ b/crates/pecos-rng/src/rng_pcg.rs @@ -1,3 +1,4 @@ +#[derive(Clone, Copy)] pub(crate) struct PCGRandom { pub(crate) state: u64, inc: u64, diff --git a/crates/pecos-rng/tests/pcg_test.rs b/crates/pecos-rng/tests/pcg_test.rs new file mode 100644 index 000000000..901622780 --- /dev/null +++ b/crates/pecos-rng/tests/pcg_test.rs @@ -0,0 +1,257 @@ +use pecos_rng::{RngPcg}; + +use std::sync::Arc; +use std::thread; + +#[test] +fn test_pcg_random_generates_values() { + // Test that random() generates different values + let mut rng_pcg = RngPcg::new(); + let val1 = rng_pcg.random(); + let val2 = rng_pcg.random(); + + // It's extremely unlikely that two consecutive calls return the same value + // (probability is 1 in 2^32) + assert_ne!( + val1, val2, + "Two consecutive random() calls should generate different values" + ); +} + +#[test] +fn test_pcg_bounded_random() { + // Test bounded random with various bounds + let bounds = [1, 2, 10, 100, 1000]; + + let mut pcg = RngPcg::new(); + for bound in bounds { + for _ in 0..10 { + let val = pcg.boundedrand(bound); + assert!( + val < bound, + "boundedrand({bound}) returned {val}, which is >= {bound}" + ); + } + } +} + +#[test] +fn test_pcg_frandom_range() { + // Test that frandom returns values in [0.0, 1.0) + let mut pcg = RngPcg::new(); + for _ in 0..100 { + let val = pcg.frandom(); + assert!(val >= 0.0, "frandom() returned {val}, which is < 0.0"); + assert!(val < 1.0, "frandom() returned {val}, which is >= 1.0"); + } +} + +#[test] +fn test_pcg_seeding() { + // Test that seeding produces deterministic sequences + // Note: PCG uses global state, so we test determinism directly + + // Test that the same seed produces the same first few values + let mut pcg = RngPcg::new(); + pcg.srandom(12345); + let first_val_1 = pcg.random(); + let second_val_1 = pcg.random(); + + pcg.srandom(12345); + let first_val_2 = pcg.random(); + let second_val_2 = pcg.random(); + + assert_eq!( + first_val_1, first_val_2, + "First value after seeding should be deterministic" + ); + assert_eq!( + second_val_1, second_val_2, + "Second value after seeding should be deterministic" + ); + + // Test that different seeds produce different values + pcg.srandom(54321); + let different_first = pcg.random(); + + assert_ne!( + first_val_1, different_first, + "Different seeds should produce different values" + ); +} + +#[test] +fn test_pcg_deterministic_behavior() { + // Test that the RNG is deterministic after seeding + let mut pcg = RngPcg::new(); + pcg.srandom(999); + let first_value = pcg.random(); + + pcg.srandom(999); + let second_value = pcg.random(); + + assert_eq!( + first_value, second_value, + "First value after seeding should be deterministic" + ); +} + +#[test] +fn test_pcg_shared_state_interference() { + // This test is more likely to fail when run in parallel with other tests + // because they all share the same global RNG state + + const ITERATIONS: usize = 100; + let mut results: Vec = Vec::new(); + let mut pcg = RngPcg::new(); + + for i in 0..ITERATIONS { + pcg.srandom(42); + // Add some delay to increase chance of interference + std::thread::yield_now(); + + let val = pcg.random(); + results.push(val); + + assert!( + !(i > 0 && results[i] != results[0]), + "Iteration {}: Expected deterministic value {} but got {} (shared state interference detected!)", + i, + results[0], + val + ); + } +} + +#[test] +fn test_pcg_rapid_reseeding() { + // Rapidly reseed and check values to increase chance of race conditions + let mut pcg = RngPcg::new(); + let expected_values: Vec = (0..10) + .map(|i| { + pcg.srandom(i); + pcg.random() + }) + .collect(); + + // Now verify multiple times + for round in 0..50 { + for (i, &expected) in expected_values.iter().enumerate() { + pcg.srandom(i as u64); + let actual = pcg.random(); + assert_eq!( + actual, expected, + "Round {round}, seed {i}: Expected {expected} but got {actual} (state corruption detected!)" + ); + } + } +} + +#[test] +fn test_pcg_concurrent_access() { + // This test verifies that threads maintain independent sequences even when running concurrently + + let num_threads = 10; + let iterations_per_thread = 100; + let barrier = Arc::new(std::sync::Barrier::new(num_threads)); + + let mut pcg = RngPcg::new(); + // First, generate expected sequences for each thread + let expected_sequences: Vec> = (0..num_threads) + .map(|thread_id| { + pcg.srandom(thread_id as u64); + (0..iterations_per_thread).map(|_| pcg.random()).collect() + }) + .collect(); + + let handles: Vec<_> = (0..num_threads) + .map(|thread_id| { + let barrier = Arc::clone(&barrier); + let expected_seq = expected_sequences[thread_id].clone(); + + thread::spawn(move || { + // Wait for all threads to be ready + barrier.wait(); + + // Each thread uses its own seed + pcg.srandom(thread_id as u64); + + let mut results = Vec::new(); + #[allow(clippy::needless_range_loop)] + for i in 0..iterations_per_thread { + let val = pcg.random(); + results.push(val); + + // Verify we're getting the expected value + assert_eq!( + val, expected_seq[i], + "Thread {thread_id} iteration {i}: expected {} but got {val}", + expected_seq[i] + ); + + // Yield to increase chance of interleaving + if i % 10 == 0 { + thread::yield_now(); + } + } + + results + }) + }) + .collect(); + + // Collect all results and verify + for (thread_id, handle) in handles.into_iter().enumerate() { + let results = handle.join().unwrap(); + assert_eq!( + results, expected_sequences[thread_id], + "Thread {thread_id} produced unexpected sequence" + ); + } +} + +#[test] +fn test_pcg_thread_independence() { + // Verify that threads with different seeds maintain independent sequences + + let mut pcg = RngPcg::new(); + // First, get expected sequences + pcg.srandom(100); + let expected_seq1: Vec = (0..5).map(|_| pcg.random()).collect(); + + pcg.srandom(200); + let expected_seq2: Vec = (0..5).map(|_| pcg.random()).collect(); + + // Run threads concurrently + let handle1 = thread::spawn(move || { + pcg.srandom(100); + let mut results = vec![]; + for _ in 0..5 { + results.push(pcg.random()); + thread::yield_now(); // Encourage interleaving + } + results + }); + + let handle2 = thread::spawn(move || { + pcg.srandom(200); + let mut results = vec![]; + for _ in 0..5 { + results.push(pcg.random()); + thread::yield_now(); // Encourage interleaving + } + results + }); + + let thread1_results = handle1.join().unwrap(); + let thread2_results = handle2.join().unwrap(); + + assert_eq!( + thread1_results, expected_seq1, + "Thread 1 maintains independent sequence" + ); + assert_eq!( + thread2_results, expected_seq2, + "Thread 2 maintains independent sequence" + ); +} \ No newline at end of file From c8b562a0f749af7fbb0223e3dc820c1e895138e1 Mon Sep 17 00:00:00 2001 From: Jonhas Colina Date: Wed, 6 Aug 2025 01:17:22 -0400 Subject: [PATCH 03/12] cargo fmt --- crates/pecos-rng/src/lib.rs | 12 ++++++------ crates/pecos-rng/src/rng_pcg.rs | 3 ++- crates/pecos-rng/tests/pcg_test.rs | 4 ++-- crates/pecos/src/prelude.rs | 4 +--- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/pecos-rng/src/lib.rs b/crates/pecos-rng/src/lib.rs index 685078a3e..dc3cf173d 100644 --- a/crates/pecos-rng/src/lib.rs +++ b/crates/pecos-rng/src/lib.rs @@ -1,23 +1,24 @@ pub mod rng_pcg; // use rng_pcg::{PCGRandom}; -use pyo3::types::PyModule; use pyo3::prelude::*; +use pyo3::types::PyModule; use crate::rng_pcg::PCGRandom; - #[pyclass] #[derive(Clone, Copy)] pub struct RngPcg { - global_state: PCGRandom + global_state: PCGRandom, } #[pymethods] impl RngPcg { #[new] pub fn new() -> RngPcg { - RngPcg { global_state: PCGRandom::init_global_state() } + RngPcg { + global_state: PCGRandom::init_global_state(), + } } pub fn random(&mut self) -> u32 { @@ -45,7 +46,6 @@ fn rng_pcg_py(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { Ok(()) } - #[cfg(test)] mod tests { use super::*; @@ -69,4 +69,4 @@ mod tests { let r3 = pcg.frandom(); assert!((0.0..1.0).contains(&r3)); } -} \ No newline at end of file +} diff --git a/crates/pecos-rng/src/rng_pcg.rs b/crates/pecos-rng/src/rng_pcg.rs index ffb3cbda8..0b39ea466 100644 --- a/crates/pecos-rng/src/rng_pcg.rs +++ b/crates/pecos-rng/src/rng_pcg.rs @@ -21,7 +21,8 @@ impl PCGRandom { #[inline(always)] pub(crate) fn pcg_setseq_64_step_r(rng: &mut PCGRandom) { const PCG_DEFAULT_MULTIPLIER_64: u64 = 6364136223846793005; - rng.state = rng.state + rng.state = rng + .state .wrapping_mul(PCG_DEFAULT_MULTIPLIER_64) .wrapping_add(rng.inc); } diff --git a/crates/pecos-rng/tests/pcg_test.rs b/crates/pecos-rng/tests/pcg_test.rs index 901622780..cea9a4aad 100644 --- a/crates/pecos-rng/tests/pcg_test.rs +++ b/crates/pecos-rng/tests/pcg_test.rs @@ -1,4 +1,4 @@ -use pecos_rng::{RngPcg}; +use pecos_rng::RngPcg; use std::sync::Arc; use std::thread; @@ -254,4 +254,4 @@ fn test_pcg_thread_independence() { thread2_results, expected_seq2, "Thread 2 maintains independent sequence" ); -} \ No newline at end of file +} diff --git a/crates/pecos/src/prelude.rs b/crates/pecos/src/prelude.rs index 47c944b2a..ab0715e91 100644 --- a/crates/pecos/src/prelude.rs +++ b/crates/pecos/src/prelude.rs @@ -67,6 +67,4 @@ pub use pecos_qir::setup_qir_engine; pub use pecos_engines::run_sim; // Re-export PCG RNG functions -pub use pecos_rng::{ - RngPcg -}; +pub use pecos_rng::RngPcg; From 15271d820728805961fc112a14bfb7e6abcf0c71 Mon Sep 17 00:00:00 2001 From: Jonhas Colina Date: Wed, 6 Aug 2025 01:21:34 -0400 Subject: [PATCH 04/12] changed import for rng crate --- python/quantum-pecos/src/pecos/engines/cvm/rng_model.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py b/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py index 45f6e8700..f29676d00 100644 --- a/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py +++ b/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py @@ -8,10 +8,7 @@ from __future__ import annotations -try: - from pecos_rng import RngPcg -except ImportError: - from pecos_rslib._pecos_rslib import pcg as pecos_rng +from pecos_rng import RngPcg from pecos.engines.cvm.binarray import BinArray From a6b99c44f8f025a4c24eb711a9483e9271c367fc Mon Sep 17 00:00:00 2001 From: Jonhas Colina Date: Wed, 6 Aug 2025 01:22:11 -0400 Subject: [PATCH 05/12] deleted old pcg binding file --- python/pecos-rslib/rust/src/pcg_bindings.rs | 49 --------------------- 1 file changed, 49 deletions(-) delete mode 100644 python/pecos-rslib/rust/src/pcg_bindings.rs diff --git a/python/pecos-rslib/rust/src/pcg_bindings.rs b/python/pecos-rslib/rust/src/pcg_bindings.rs deleted file mode 100644 index 04e977022..000000000 --- a/python/pecos-rslib/rust/src/pcg_bindings.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2025 The PECOS Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -// in compliance with the License.You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under the License -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -// or implied. See the License for the specific language governing permissions and limitations under -// the License. - -use pecos::prelude::*; -use pyo3::prelude::*; - -#[pyfunction] -#[pyo3(name = "pcg32_random")] -pub fn py_pcg32_random() -> u32 { - pcg32_random() -} - -#[pyfunction] -#[pyo3(name = "pcg32_boundedrand")] -pub fn py_pcg32_boundedrand(bound: u32) -> u32 { - pcg32_boundedrand(bound) -} - -#[pyfunction] -#[pyo3(name = "pcg32_frandom")] -pub fn py_pcg32_frandom() -> f64 { - pcg32_frandom() -} - -#[pyfunction] -#[pyo3(name = "pcg32_srandom")] -pub fn py_pcg32_srandom(seq: u64) { - pcg32_srandom(seq); -} - -/// Create a submodule for PCG functions -pub fn create_pcg_module(m: &Bound<'_, PyModule>) -> PyResult<()> { - let pcg_module = PyModule::new(m.py(), "pcg")?; - pcg_module.add_function(wrap_pyfunction!(py_pcg32_random, &pcg_module)?)?; - pcg_module.add_function(wrap_pyfunction!(py_pcg32_boundedrand, &pcg_module)?)?; - pcg_module.add_function(wrap_pyfunction!(py_pcg32_frandom, &pcg_module)?)?; - pcg_module.add_function(wrap_pyfunction!(py_pcg32_srandom, &pcg_module)?)?; - m.add_submodule(&pcg_module)?; - Ok(()) -} From 227f3dadd8bc171df08a69aee1aa5d2b8a0801f5 Mon Sep 17 00:00:00 2001 From: Jonhas Colina Date: Wed, 6 Aug 2025 01:24:11 -0400 Subject: [PATCH 06/12] pre-commit changes --- python/quantum-pecos/src/pecos/engines/cvm/rng_model.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py b/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py index f29676d00..1b53c36c9 100644 --- a/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py +++ b/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py @@ -44,10 +44,11 @@ def set_bound(self, bound: int) -> None: def rng_random(self) -> int: """Generating a random number and keeping track of how many we have generated.""" - if self.current_bound == 0: - rng_num = self.pcg.random() - else: - rng_num = self.pcg.boundedrand(self.current_bound) + rng_num = ( + self.pcg.random() + if self.current_bound == 0 + else self.pcg.boundedrand(self.current_bound) + ) self.count += 1 return rng_num From 1e247db8648baa61dc95f8501872991676cd3542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ryan-Anderson?= Date: Wed, 6 Aug 2025 15:48:09 -0600 Subject: [PATCH 07/12] Linting (#169) * Linting * Fixing Python Artifact workflow issue --- .github/workflows/python-release.yml | 46 ++++++++++++++++------------ crates/pecos-rng/Cargo.toml | 1 + crates/pecos-rng/src/lib.rs | 15 ++++++--- crates/pecos-rng/src/rng_pcg.rs | 26 ++++++++++------ ruff.toml | 1 - 5 files changed, 54 insertions(+), 35 deletions(-) diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index 37220b10d..d95f4388e 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -208,16 +208,19 @@ jobs: - name: Install pecos-rslib run: pip install ./pecos-rslib-wheel/*.whl - - name: Install pecos RNG build dependencies - run: | - pip install build nanobind - - - name: Build PECOS RNG - run: | - cd clib/pecos-rng && mkdir build && cd build/ && cmake .. - make && cd .. && pip install . - env: - NANOBIND_DIR: python -m nanobind --include_dir + # TODO: Re-enable RNG build once it's exposed via python/pecos-rslib/ + # The RNG functionality has been moved from clib/pecos-rng to crates/pecos-rng + # and will be exposed through the Rust library bindings + # - name: Install pecos RNG build dependencies + # run: | + # pip install build nanobind + # + # - name: Build PECOS RNG + # run: | + # cd clib/pecos-rng && mkdir build && cd build/ && cmake .. + # make && cd .. && pip install . + # env: + # NANOBIND_DIR: python -m nanobind --include_dir - name: Build quantum-pecos SDist run: | @@ -261,16 +264,19 @@ jobs: - name: Install pecos-rslib run: pip install ./pecos-rslib-wheel/*.whl - - name: Install pecos rng build dependencies - run: | - pip install build nanobind - - - name: Build PECOS RNG - run: | - cd clib/pecos-rng && mkdir build && cd build/ && cmake .. - make && cd .. && pip install . - env: - NANOBIND_DIR: python -m nanobind --include_dir + # TODO: Re-enable RNG build once it's exposed via python/pecos-rslib/ + # The RNG functionality has been moved from clib/pecos-rng to crates/pecos-rng + # and will be exposed through the Rust library bindings + # - name: Install pecos rng build dependencies + # run: | + # pip install build nanobind + # + # - name: Build PECOS RNG + # run: | + # cd clib/pecos-rng && mkdir build && cd build/ && cmake .. + # make && cd .. && pip install . + # env: + # NANOBIND_DIR: python -m nanobind --include_dir - name: Build quantum-pecos wheel run: | diff --git a/crates/pecos-rng/Cargo.toml b/crates/pecos-rng/Cargo.toml index 3776d871b..0e5cec07f 100644 --- a/crates/pecos-rng/Cargo.toml +++ b/crates/pecos-rng/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true license.workspace = true keywords.workspace = true categories.workspace = true +description = "Random number generators for PECOS quantum computing simulations" [dependencies] pyo3 = { workspace=true, features = ["extension-module", "abi3-py310", "generate-import-lib"] } diff --git a/crates/pecos-rng/src/lib.rs b/crates/pecos-rng/src/lib.rs index dc3cf173d..34455dc5b 100644 --- a/crates/pecos-rng/src/lib.rs +++ b/crates/pecos-rng/src/lib.rs @@ -12,13 +12,20 @@ pub struct RngPcg { global_state: PCGRandom, } +impl Default for RngPcg { + fn default() -> Self { + Self { + global_state: PCGRandom::init_global_state(), + } + } +} + #[pymethods] impl RngPcg { #[new] + #[must_use] pub fn new() -> RngPcg { - RngPcg { - global_state: PCGRandom::init_global_state(), - } + Self::default() } pub fn random(&mut self) -> u32 { @@ -30,7 +37,7 @@ impl RngPcg { } pub fn frandom(&mut self) -> f64 { - let random = self.random() as f64; + let random = f64::from(self.random()); let exp: i32 = -32; random * 2f64.powi(exp) } diff --git a/crates/pecos-rng/src/rng_pcg.rs b/crates/pecos-rng/src/rng_pcg.rs index 0b39ea466..d36680806 100644 --- a/crates/pecos-rng/src/rng_pcg.rs +++ b/crates/pecos-rng/src/rng_pcg.rs @@ -1,4 +1,7 @@ #[derive(Clone, Copy)] +#[allow(clippy::cast_possible_truncation)] +#[allow(clippy::cast_possible_wrap)] +#[allow(clippy::cast_sign_loss)] pub(crate) struct PCGRandom { pub(crate) state: u64, inc: u64, @@ -7,41 +10,44 @@ pub(crate) struct PCGRandom { impl PCGRandom { pub(crate) fn init_global_state() -> PCGRandom { PCGRandom { - state: 0x853c49e6748fea9b, - inc: 0xda3e39cb94b95bdb, + state: 0x853c_49e6_748f_ea9b, + inc: 0xda3e_39cb_94b9_5bdb, } } - #[inline(always)] + #[inline] + #[allow(clippy::cast_possible_wrap)] pub(crate) fn pcg_rotr(value: u32, urot: u32) -> u32 { let rot = urot as i32; (value >> rot) | (value << ((-rot) & 31)) } - #[inline(always)] + #[inline] pub(crate) fn pcg_setseq_64_step_r(rng: &mut PCGRandom) { - const PCG_DEFAULT_MULTIPLIER_64: u64 = 6364136223846793005; + const PCG_DEFAULT_MULTIPLIER_64: u64 = 6_364_136_223_846_793_005; rng.state = rng .state .wrapping_mul(PCG_DEFAULT_MULTIPLIER_64) .wrapping_add(rng.inc); } - #[inline(always)] + #[inline] + #[allow(clippy::cast_possible_truncation)] pub(crate) fn pcg_output_xsh(state: u64) -> u32 { let value = ((state >> 18) ^ state) >> 27; let urot = state >> 59; PCGRandom::pcg_rotr(value as u32, urot as u32) } - #[inline(always)] + #[inline] pub(crate) fn pcg32_random_r(rng: &mut PCGRandom) -> u32 { let old_state: u64 = rng.state; PCGRandom::pcg_setseq_64_step_r(rng); - return PCGRandom::pcg_output_xsh(old_state); + PCGRandom::pcg_output_xsh(old_state) } - #[inline(always)] + #[inline] + #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] pub(crate) fn pcg32_boundedrand_r(rng: &mut PCGRandom, ubound: u32) -> u32 { let bound: i32 = ubound as i32; let threshold: u32 = (-bound % bound) as u32; @@ -53,7 +59,7 @@ impl PCGRandom { } } - #[inline(always)] + #[inline] pub(crate) fn pcg32_srandom_r(rng: &mut PCGRandom, initstate: u64, initseq: u64) { rng.state = 0_u64; rng.inc = (initseq << 1_u64) | 1_u64; diff --git a/ruff.toml b/ruff.toml index ca8950d81..c3999a14f 100644 --- a/ruff.toml +++ b/ruff.toml @@ -112,7 +112,6 @@ ignore = [ "PLW", "B", ] -"clib/pecos-rng/pecos_pcg/__init__.py" = ["TID252"] From a7e08166e04db16bb3cce71a460023d986def94e Mon Sep 17 00:00:00 2001 From: Jonhas Colina Date: Wed, 6 Aug 2025 18:56:06 -0400 Subject: [PATCH 08/12] made changes for rng_model to properly import pecos-rng crate --- crates/pecos-rng/src/lib.rs | 124 +++++++++--------- crates/pecos-rng/src/rng_pcg.rs | 18 +-- crates/pecos/src/prelude.rs | 2 +- python/pecos-rslib/rust/src/lib.rs | 5 +- .../rust/src/pecos_rng_bindings.rs | 71 ++++++++++ .../src/pecos/engines/cvm/rng_model.py | 7 +- 6 files changed, 150 insertions(+), 77 deletions(-) create mode 100644 python/pecos-rslib/rust/src/pecos_rng_bindings.rs diff --git a/crates/pecos-rng/src/lib.rs b/crates/pecos-rng/src/lib.rs index 34455dc5b..c915a1dc7 100644 --- a/crates/pecos-rng/src/lib.rs +++ b/crates/pecos-rng/src/lib.rs @@ -1,79 +1,79 @@ pub mod rng_pcg; // use rng_pcg::{PCGRandom}; -use pyo3::prelude::*; -use pyo3::types::PyModule; +// use pyo3::prelude::*; +// use pyo3::types::PyModule; -use crate::rng_pcg::PCGRandom; +// use crate::rng_pcg::PCGRandom; -#[pyclass] -#[derive(Clone, Copy)] -pub struct RngPcg { - global_state: PCGRandom, -} +// #[pyclass] +// #[derive(Clone, Copy)] +// pub struct RngPcg { +// global_state: PCGRandom, +// } -impl Default for RngPcg { - fn default() -> Self { - Self { - global_state: PCGRandom::init_global_state(), - } - } -} +// impl Default for RngPcg { +// fn default() -> Self { +// Self { +// global_state: PCGRandom::init_global_state(), +// } +// } +// } -#[pymethods] -impl RngPcg { - #[new] - #[must_use] - pub fn new() -> RngPcg { - Self::default() - } +// #[pymethods] +// impl RngPcg { +// #[new] +// #[must_use] +// pub fn new() -> RngPcg { +// Self::default() +// } - pub fn random(&mut self) -> u32 { - PCGRandom::pcg32_random_r(&mut self.global_state) - } +// pub fn random(&mut self) -> u32 { +// PCGRandom::pcg32_random_r(&mut self.global_state) +// } - pub fn boundedrand(&mut self, bound: u32) -> u32 { - PCGRandom::pcg32_boundedrand_r(&mut self.global_state, bound) - } +// pub fn boundedrand(&mut self, bound: u32) -> u32 { +// PCGRandom::pcg32_boundedrand_r(&mut self.global_state, bound) +// } - pub fn frandom(&mut self) -> f64 { - let random = f64::from(self.random()); - let exp: i32 = -32; - random * 2f64.powi(exp) - } +// pub fn frandom(&mut self) -> f64 { +// let random = f64::from(self.random()); +// let exp: i32 = -32; +// random * 2f64.powi(exp) +// } - pub fn srandom(&mut self, seq: u64) { - PCGRandom::pcg32_srandom_r(&mut self.global_state, 42_u64, seq); - } -} +// pub fn srandom(&mut self, seq: u64) { +// PCGRandom::pcg32_srandom_r(&mut self.global_state, 42_u64, seq); +// } +// } -#[pymodule] -fn rng_pcg_py(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; - Ok(()) -} +// #[pymodule] +// fn rng_pcg_py(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { +// m.add_class::()?; +// Ok(()) +// } -#[cfg(test)] -mod tests { - use super::*; +// #[cfg(test)] +// mod tests { +// use super::*; - #[test] - fn test_pcg_functions() { - let mut pcg = RngPcg::new(); - // Set seed - pcg.srandom(15); +// #[test] +// fn test_pcg_functions() { +// let mut pcg = RngPcg::new(); +// // Set seed +// pcg.srandom(15); - // Test basic random - let r1 = pcg.random(); - assert!(r1 > 0); +// // Test basic random +// let r1 = pcg.random(); +// assert!(r1 > 0); - // Test bounded random - let bound = 100; - let r2 = pcg.boundedrand(bound); - assert!(r2 < bound); +// // Test bounded random +// let bound = 100; +// let r2 = pcg.boundedrand(bound); +// assert!(r2 < bound); - // Test float random - let r3 = pcg.frandom(); - assert!((0.0..1.0).contains(&r3)); - } -} +// // Test float random +// let r3 = pcg.frandom(); +// assert!((0.0..1.0).contains(&r3)); +// } +// } diff --git a/crates/pecos-rng/src/rng_pcg.rs b/crates/pecos-rng/src/rng_pcg.rs index d36680806..e0ada633e 100644 --- a/crates/pecos-rng/src/rng_pcg.rs +++ b/crates/pecos-rng/src/rng_pcg.rs @@ -2,13 +2,13 @@ #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_sign_loss)] -pub(crate) struct PCGRandom { - pub(crate) state: u64, +pub struct PCGRandom { + pub state: u64, inc: u64, } impl PCGRandom { - pub(crate) fn init_global_state() -> PCGRandom { + pub fn init_global_state() -> PCGRandom { PCGRandom { state: 0x853c_49e6_748f_ea9b, inc: 0xda3e_39cb_94b9_5bdb, @@ -17,13 +17,13 @@ impl PCGRandom { #[inline] #[allow(clippy::cast_possible_wrap)] - pub(crate) fn pcg_rotr(value: u32, urot: u32) -> u32 { + fn pcg_rotr(value: u32, urot: u32) -> u32 { let rot = urot as i32; (value >> rot) | (value << ((-rot) & 31)) } #[inline] - pub(crate) fn pcg_setseq_64_step_r(rng: &mut PCGRandom) { + fn pcg_setseq_64_step_r(rng: &mut PCGRandom) { const PCG_DEFAULT_MULTIPLIER_64: u64 = 6_364_136_223_846_793_005; rng.state = rng .state @@ -33,14 +33,14 @@ impl PCGRandom { #[inline] #[allow(clippy::cast_possible_truncation)] - pub(crate) fn pcg_output_xsh(state: u64) -> u32 { + fn pcg_output_xsh(state: u64) -> u32 { let value = ((state >> 18) ^ state) >> 27; let urot = state >> 59; PCGRandom::pcg_rotr(value as u32, urot as u32) } #[inline] - pub(crate) fn pcg32_random_r(rng: &mut PCGRandom) -> u32 { + pub fn pcg32_random_r(rng: &mut PCGRandom) -> u32 { let old_state: u64 = rng.state; PCGRandom::pcg_setseq_64_step_r(rng); PCGRandom::pcg_output_xsh(old_state) @@ -48,7 +48,7 @@ impl PCGRandom { #[inline] #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] - pub(crate) fn pcg32_boundedrand_r(rng: &mut PCGRandom, ubound: u32) -> u32 { + pub fn pcg32_boundedrand_r(rng: &mut PCGRandom, ubound: u32) -> u32 { let bound: i32 = ubound as i32; let threshold: u32 = (-bound % bound) as u32; loop { @@ -60,7 +60,7 @@ impl PCGRandom { } #[inline] - pub(crate) fn pcg32_srandom_r(rng: &mut PCGRandom, initstate: u64, initseq: u64) { + pub fn pcg32_srandom_r(rng: &mut PCGRandom, initstate: u64, initseq: u64) { rng.state = 0_u64; rng.inc = (initseq << 1_u64) | 1_u64; PCGRandom::pcg_setseq_64_step_r(rng); diff --git a/crates/pecos/src/prelude.rs b/crates/pecos/src/prelude.rs index ab0715e91..3cb677a40 100644 --- a/crates/pecos/src/prelude.rs +++ b/crates/pecos/src/prelude.rs @@ -67,4 +67,4 @@ pub use pecos_qir::setup_qir_engine; pub use pecos_engines::run_sim; // Re-export PCG RNG functions -pub use pecos_rng::RngPcg; +pub use pecos_rng::rng_pcg; diff --git a/python/pecos-rslib/rust/src/lib.rs b/python/pecos-rslib/rust/src/lib.rs index e03e82c45..3c4d096a1 100644 --- a/python/pecos-rslib/rust/src/lib.rs +++ b/python/pecos-rslib/rust/src/lib.rs @@ -27,13 +27,14 @@ mod sparse_stab_bindings; mod sparse_stab_engine_bindings; mod state_vec_bindings; mod state_vec_engine_bindings; +mod pecos_rng_bindings; use byte_message_bindings::{PyByteMessage, PyByteMessageBuilder}; use sparse_stab_bindings::SparseSim; use sparse_stab_engine_bindings::PySparseStabEngine; use state_vec_bindings::RsStateVec; use state_vec_engine_bindings::PyStateVecEngine; - +use pecos_rng_bindings::RngPcg; use pyo3::prelude::*; /// A Python module implemented in Rust. @@ -46,10 +47,10 @@ fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; // Register QASM simulation functions qasm_sim_bindings::register_qasm_sim_module(m)?; - // pcg_bindings::create_pcg_module(m)?; Ok(()) } diff --git a/python/pecos-rslib/rust/src/pecos_rng_bindings.rs b/python/pecos-rslib/rust/src/pecos_rng_bindings.rs new file mode 100644 index 000000000..aaf46e889 --- /dev/null +++ b/python/pecos-rslib/rust/src/pecos_rng_bindings.rs @@ -0,0 +1,71 @@ +// use rng_pcg::{PCGRandom}; +use pyo3::prelude::*; +use pecos::prelude::rng_pcg::PCGRandom; + +// use core::prelude::rng_pcg::PCGRandom; + +#[pyclass] +#[derive(Clone, Copy)] +pub struct RngPcg { + global_state: PCGRandom, +} + +impl Default for RngPcg { + fn default() -> Self { + Self { + global_state: PCGRandom::init_global_state(), + } + } +} + +#[pymethods] +impl RngPcg { + #[new] + #[must_use] + pub fn new() -> RngPcg { + Self::default() + } + + pub fn random(&mut self) -> u32 { + PCGRandom::pcg32_random_r(&mut self.global_state) + } + + pub fn boundedrand(&mut self, bound: u32) -> u32 { + PCGRandom::pcg32_boundedrand_r(&mut self.global_state, bound) + } + + pub fn frandom(&mut self) -> f64 { + let random = f64::from(self.random()); + let exp: i32 = -32; + random * 2f64.powi(exp) + } + + pub fn srandom(&mut self, seq: u64) { + PCGRandom::pcg32_srandom_r(&mut self.global_state, 42_u64, seq); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pcg_functions() { + let mut pcg = RngPcg::new(); + // Set seed + pcg.srandom(15); + + // Test basic random + let r1 = pcg.random(); + assert!(r1 > 0); + + // Test bounded random + let bound = 100; + let r2 = pcg.boundedrand(bound); + assert!(r2 < bound); + + // Test float random + let r3 = pcg.frandom(); + assert!((0.0..1.0).contains(&r3)); + } +} diff --git a/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py b/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py index 1b53c36c9..420bf05e7 100644 --- a/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py +++ b/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py @@ -8,7 +8,8 @@ from __future__ import annotations -from pecos_rng import RngPcg +# from pecos_rslib import RngPcg +from pecos_rslib._pecos_rslib import RngPcg from pecos.engines.cvm.binarray import BinArray @@ -26,8 +27,8 @@ def __init__( self.shot_id = shot_id self.current_bound = current_bound self.count = 0 - self.seed = self.set_seed(seed) self.pcg = RngPcg() + self.seed = self.set_seed(seed) def __str__(self) -> str: """Returns the str representation of the model.""" @@ -61,7 +62,7 @@ def set_index(self, index: int) -> None: error_msg = "rngindex called after specified already generated" raise BufferError(error_msg) while self.count < index: - self.pcg.random() + self.rng_random() def extract_val(self, param: str, output: dict) -> int: """Responsible for extracting the value of interest depending on the type of the parameter being passed in.""" From e04c71aadd73bdefc5fee9e5aaaaf52f86ffe496 Mon Sep 17 00:00:00 2001 From: Jonhas Colina Date: Wed, 6 Aug 2025 19:21:55 -0400 Subject: [PATCH 09/12] fixed test case for pecos-rng --- crates/pecos-rng/src/lib.rs | 80 +-------------- crates/pecos-rng/src/rng_pcg.rs | 7 ++ crates/pecos-rng/tests/pcg_test.rs | 99 ++++++++++--------- .../rust/src/pecos_rng_bindings.rs | 4 +- .../src/pecos/engines/cvm/rng_model.py | 1 - 5 files changed, 62 insertions(+), 129 deletions(-) diff --git a/crates/pecos-rng/src/lib.rs b/crates/pecos-rng/src/lib.rs index c915a1dc7..c593c52de 100644 --- a/crates/pecos-rng/src/lib.rs +++ b/crates/pecos-rng/src/lib.rs @@ -1,79 +1 @@ -pub mod rng_pcg; - -// use rng_pcg::{PCGRandom}; -// use pyo3::prelude::*; -// use pyo3::types::PyModule; - -// use crate::rng_pcg::PCGRandom; - -// #[pyclass] -// #[derive(Clone, Copy)] -// pub struct RngPcg { -// global_state: PCGRandom, -// } - -// impl Default for RngPcg { -// fn default() -> Self { -// Self { -// global_state: PCGRandom::init_global_state(), -// } -// } -// } - -// #[pymethods] -// impl RngPcg { -// #[new] -// #[must_use] -// pub fn new() -> RngPcg { -// Self::default() -// } - -// pub fn random(&mut self) -> u32 { -// PCGRandom::pcg32_random_r(&mut self.global_state) -// } - -// pub fn boundedrand(&mut self, bound: u32) -> u32 { -// PCGRandom::pcg32_boundedrand_r(&mut self.global_state, bound) -// } - -// pub fn frandom(&mut self) -> f64 { -// let random = f64::from(self.random()); -// let exp: i32 = -32; -// random * 2f64.powi(exp) -// } - -// pub fn srandom(&mut self, seq: u64) { -// PCGRandom::pcg32_srandom_r(&mut self.global_state, 42_u64, seq); -// } -// } - -// #[pymodule] -// fn rng_pcg_py(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { -// m.add_class::()?; -// Ok(()) -// } - -// #[cfg(test)] -// mod tests { -// use super::*; - -// #[test] -// fn test_pcg_functions() { -// let mut pcg = RngPcg::new(); -// // Set seed -// pcg.srandom(15); - -// // Test basic random -// let r1 = pcg.random(); -// assert!(r1 > 0); - -// // Test bounded random -// let bound = 100; -// let r2 = pcg.boundedrand(bound); -// assert!(r2 < bound); - -// // Test float random -// let r3 = pcg.frandom(); -// assert!((0.0..1.0).contains(&r3)); -// } -// } +pub mod rng_pcg; \ No newline at end of file diff --git a/crates/pecos-rng/src/rng_pcg.rs b/crates/pecos-rng/src/rng_pcg.rs index e0ada633e..74989c7ef 100644 --- a/crates/pecos-rng/src/rng_pcg.rs +++ b/crates/pecos-rng/src/rng_pcg.rs @@ -59,6 +59,13 @@ impl PCGRandom { } } + #[inline] + pub fn frandom(rng: &mut PCGRandom) -> f64 { + let random = f64::from(PCGRandom::pcg32_random_r(rng)); + let exp: i32 = -32; + random * 2f64.powi(exp) + } + #[inline] pub fn pcg32_srandom_r(rng: &mut PCGRandom, initstate: u64, initseq: u64) { rng.state = 0_u64; diff --git a/crates/pecos-rng/tests/pcg_test.rs b/crates/pecos-rng/tests/pcg_test.rs index cea9a4aad..e04493849 100644 --- a/crates/pecos-rng/tests/pcg_test.rs +++ b/crates/pecos-rng/tests/pcg_test.rs @@ -1,4 +1,4 @@ -use pecos_rng::RngPcg; +use pecos_rng::rng_pcg::PCGRandom; use std::sync::Arc; use std::thread; @@ -6,9 +6,9 @@ use std::thread; #[test] fn test_pcg_random_generates_values() { // Test that random() generates different values - let mut rng_pcg = RngPcg::new(); - let val1 = rng_pcg.random(); - let val2 = rng_pcg.random(); + let mut rng_pcg = PCGRandom::init_global_state(); + let val1 = PCGRandom::pcg32_random_r(&mut rng_pcg); + let val2 = PCGRandom::pcg32_random_r(&mut rng_pcg); // It's extremely unlikely that two consecutive calls return the same value // (probability is 1 in 2^32) @@ -23,10 +23,10 @@ fn test_pcg_bounded_random() { // Test bounded random with various bounds let bounds = [1, 2, 10, 100, 1000]; - let mut pcg = RngPcg::new(); + let mut rng_pcg = PCGRandom::init_global_state(); for bound in bounds { for _ in 0..10 { - let val = pcg.boundedrand(bound); + let val = PCGRandom::pcg32_boundedrand_r(&mut rng_pcg, bound); assert!( val < bound, "boundedrand({bound}) returned {val}, which is >= {bound}" @@ -38,9 +38,9 @@ fn test_pcg_bounded_random() { #[test] fn test_pcg_frandom_range() { // Test that frandom returns values in [0.0, 1.0) - let mut pcg = RngPcg::new(); + let mut pcg = PCGRandom::init_global_state(); for _ in 0..100 { - let val = pcg.frandom(); + let val = PCGRandom::frandom(&mut pcg); assert!(val >= 0.0, "frandom() returned {val}, which is < 0.0"); assert!(val < 1.0, "frandom() returned {val}, which is >= 1.0"); } @@ -52,14 +52,15 @@ fn test_pcg_seeding() { // Note: PCG uses global state, so we test determinism directly // Test that the same seed produces the same first few values - let mut pcg = RngPcg::new(); - pcg.srandom(12345); - let first_val_1 = pcg.random(); - let second_val_1 = pcg.random(); + let mut pcg = PCGRandom::init_global_state(); + let init_state = 42_u64; + PCGRandom::pcg32_srandom_r(&mut pcg, init_state, 12345); + let first_val_1 = PCGRandom::pcg32_random_r(&mut pcg); + let second_val_1 = PCGRandom::pcg32_random_r(&mut pcg); - pcg.srandom(12345); - let first_val_2 = pcg.random(); - let second_val_2 = pcg.random(); + PCGRandom::pcg32_srandom_r(&mut pcg, init_state, 12345); + let first_val_2 = PCGRandom::pcg32_random_r(&mut pcg); + let second_val_2 = PCGRandom::pcg32_random_r(&mut pcg); assert_eq!( first_val_1, first_val_2, @@ -71,8 +72,8 @@ fn test_pcg_seeding() { ); // Test that different seeds produce different values - pcg.srandom(54321); - let different_first = pcg.random(); + PCGRandom::pcg32_srandom_r(&mut pcg, init_state, 54321); + let different_first = PCGRandom::pcg32_random_r(&mut pcg); assert_ne!( first_val_1, different_first, @@ -83,12 +84,13 @@ fn test_pcg_seeding() { #[test] fn test_pcg_deterministic_behavior() { // Test that the RNG is deterministic after seeding - let mut pcg = RngPcg::new(); - pcg.srandom(999); - let first_value = pcg.random(); + let mut pcg = PCGRandom::init_global_state(); + let init_state = 42_u64; + PCGRandom::pcg32_srandom_r(&mut pcg, init_state, 999); + let first_value = PCGRandom::pcg32_random_r(&mut pcg); - pcg.srandom(999); - let second_value = pcg.random(); + PCGRandom::pcg32_srandom_r(&mut pcg, init_state, 999); + let second_value = PCGRandom::pcg32_random_r(&mut pcg); assert_eq!( first_value, second_value, @@ -96,6 +98,7 @@ fn test_pcg_deterministic_behavior() { ); } + #[test] fn test_pcg_shared_state_interference() { // This test is more likely to fail when run in parallel with other tests @@ -103,14 +106,14 @@ fn test_pcg_shared_state_interference() { const ITERATIONS: usize = 100; let mut results: Vec = Vec::new(); - let mut pcg = RngPcg::new(); - + let mut pcg = PCGRandom::init_global_state(); + let init_state = 42_u64; for i in 0..ITERATIONS { - pcg.srandom(42); + PCGRandom::pcg32_srandom_r(&mut pcg, init_state, 42); // Add some delay to increase chance of interference std::thread::yield_now(); - let val = pcg.random(); + let val = PCGRandom::pcg32_random_r(&mut pcg); results.push(val); assert!( @@ -126,19 +129,20 @@ fn test_pcg_shared_state_interference() { #[test] fn test_pcg_rapid_reseeding() { // Rapidly reseed and check values to increase chance of race conditions - let mut pcg = RngPcg::new(); + let mut pcg = PCGRandom::init_global_state(); + let initstate = 42_u64; let expected_values: Vec = (0..10) .map(|i| { - pcg.srandom(i); - pcg.random() + PCGRandom::pcg32_srandom_r(&mut pcg, initstate, i); + PCGRandom::pcg32_random_r(&mut pcg) }) .collect(); // Now verify multiple times for round in 0..50 { for (i, &expected) in expected_values.iter().enumerate() { - pcg.srandom(i as u64); - let actual = pcg.random(); + PCGRandom::pcg32_srandom_r(&mut pcg, initstate, i as u64 ); + let actual = PCGRandom::pcg32_random_r(&mut pcg); assert_eq!( actual, expected, "Round {round}, seed {i}: Expected {expected} but got {actual} (state corruption detected!)" @@ -154,13 +158,14 @@ fn test_pcg_concurrent_access() { let num_threads = 10; let iterations_per_thread = 100; let barrier = Arc::new(std::sync::Barrier::new(num_threads)); + let initstate = 42_u64; - let mut pcg = RngPcg::new(); + let mut pcg = PCGRandom::init_global_state(); // First, generate expected sequences for each thread let expected_sequences: Vec> = (0..num_threads) - .map(|thread_id| { - pcg.srandom(thread_id as u64); - (0..iterations_per_thread).map(|_| pcg.random()).collect() + .map(|thread_id: usize| { + PCGRandom::pcg32_srandom_r(&mut pcg, initstate, thread_id as u64); + (0..iterations_per_thread).map(|_| PCGRandom::pcg32_random_r(&mut pcg)).collect() }) .collect(); @@ -174,12 +179,12 @@ fn test_pcg_concurrent_access() { barrier.wait(); // Each thread uses its own seed - pcg.srandom(thread_id as u64); + PCGRandom::pcg32_srandom_r(&mut pcg, initstate, thread_id as u64); let mut results = Vec::new(); #[allow(clippy::needless_range_loop)] for i in 0..iterations_per_thread { - let val = pcg.random(); + let val = PCGRandom::pcg32_random_r(&mut pcg); results.push(val); // Verify we're getting the expected value @@ -214,30 +219,32 @@ fn test_pcg_concurrent_access() { fn test_pcg_thread_independence() { // Verify that threads with different seeds maintain independent sequences - let mut pcg = RngPcg::new(); + let mut pcg = PCGRandom::init_global_state(); + let initstate = 42_u64; + // First, get expected sequences - pcg.srandom(100); - let expected_seq1: Vec = (0..5).map(|_| pcg.random()).collect(); + PCGRandom::pcg32_srandom_r(&mut pcg, initstate, 100); + let expected_seq1: Vec = (0..5).map(|_| PCGRandom::pcg32_random_r(&mut pcg)).collect(); - pcg.srandom(200); - let expected_seq2: Vec = (0..5).map(|_| pcg.random()).collect(); + PCGRandom::pcg32_srandom_r(&mut pcg, initstate, 200); + let expected_seq2: Vec = (0..5).map(|_| PCGRandom::pcg32_random_r(&mut pcg)).collect(); // Run threads concurrently let handle1 = thread::spawn(move || { - pcg.srandom(100); + PCGRandom::pcg32_srandom_r(&mut pcg, initstate, 100); let mut results = vec![]; for _ in 0..5 { - results.push(pcg.random()); + results.push(PCGRandom::pcg32_random_r(&mut pcg)); thread::yield_now(); // Encourage interleaving } results }); let handle2 = thread::spawn(move || { - pcg.srandom(200); + PCGRandom::pcg32_srandom_r(&mut pcg, initstate, 200); let mut results = vec![]; for _ in 0..5 { - results.push(pcg.random()); + results.push(PCGRandom::pcg32_random_r(&mut pcg)); thread::yield_now(); // Encourage interleaving } results diff --git a/python/pecos-rslib/rust/src/pecos_rng_bindings.rs b/python/pecos-rslib/rust/src/pecos_rng_bindings.rs index aaf46e889..83cd1fe58 100644 --- a/python/pecos-rslib/rust/src/pecos_rng_bindings.rs +++ b/python/pecos-rslib/rust/src/pecos_rng_bindings.rs @@ -35,9 +35,7 @@ impl RngPcg { } pub fn frandom(&mut self) -> f64 { - let random = f64::from(self.random()); - let exp: i32 = -32; - random * 2f64.powi(exp) + PCGRandom::frandom(&mut self.global_state) } pub fn srandom(&mut self, seq: u64) { diff --git a/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py b/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py index 420bf05e7..9d259868a 100644 --- a/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py +++ b/python/quantum-pecos/src/pecos/engines/cvm/rng_model.py @@ -8,7 +8,6 @@ from __future__ import annotations -# from pecos_rslib import RngPcg from pecos_rslib._pecos_rslib import RngPcg from pecos.engines.cvm.binarray import BinArray From 532980bb66d4d9a3d6e20cf67283083504975e08 Mon Sep 17 00:00:00 2001 From: Jonhas Colina Date: Wed, 6 Aug 2025 19:24:35 -0400 Subject: [PATCH 10/12] cargo fmt changes --- .github/workflows/python-release.yml | 28 ------------------- crates/pecos-rng/src/lib.rs | 2 +- crates/pecos-rng/tests/pcg_test.rs | 15 ++++++---- python/pecos-rslib/rust/src/lib.rs | 6 ++-- .../rust/src/pecos_rng_bindings.rs | 2 +- 5 files changed, 15 insertions(+), 38 deletions(-) diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index d95f4388e..fe1518584 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -208,20 +208,6 @@ jobs: - name: Install pecos-rslib run: pip install ./pecos-rslib-wheel/*.whl - # TODO: Re-enable RNG build once it's exposed via python/pecos-rslib/ - # The RNG functionality has been moved from clib/pecos-rng to crates/pecos-rng - # and will be exposed through the Rust library bindings - # - name: Install pecos RNG build dependencies - # run: | - # pip install build nanobind - # - # - name: Build PECOS RNG - # run: | - # cd clib/pecos-rng && mkdir build && cd build/ && cmake .. - # make && cd .. && pip install . - # env: - # NANOBIND_DIR: python -m nanobind --include_dir - - name: Build quantum-pecos SDist run: | cd python/quantum-pecos @@ -264,20 +250,6 @@ jobs: - name: Install pecos-rslib run: pip install ./pecos-rslib-wheel/*.whl - # TODO: Re-enable RNG build once it's exposed via python/pecos-rslib/ - # The RNG functionality has been moved from clib/pecos-rng to crates/pecos-rng - # and will be exposed through the Rust library bindings - # - name: Install pecos rng build dependencies - # run: | - # pip install build nanobind - # - # - name: Build PECOS RNG - # run: | - # cd clib/pecos-rng && mkdir build && cd build/ && cmake .. - # make && cd .. && pip install . - # env: - # NANOBIND_DIR: python -m nanobind --include_dir - - name: Build quantum-pecos wheel run: | cd python/quantum-pecos diff --git a/crates/pecos-rng/src/lib.rs b/crates/pecos-rng/src/lib.rs index c593c52de..81524dafe 100644 --- a/crates/pecos-rng/src/lib.rs +++ b/crates/pecos-rng/src/lib.rs @@ -1 +1 @@ -pub mod rng_pcg; \ No newline at end of file +pub mod rng_pcg; diff --git a/crates/pecos-rng/tests/pcg_test.rs b/crates/pecos-rng/tests/pcg_test.rs index e04493849..060446a2b 100644 --- a/crates/pecos-rng/tests/pcg_test.rs +++ b/crates/pecos-rng/tests/pcg_test.rs @@ -98,7 +98,6 @@ fn test_pcg_deterministic_behavior() { ); } - #[test] fn test_pcg_shared_state_interference() { // This test is more likely to fail when run in parallel with other tests @@ -141,7 +140,7 @@ fn test_pcg_rapid_reseeding() { // Now verify multiple times for round in 0..50 { for (i, &expected) in expected_values.iter().enumerate() { - PCGRandom::pcg32_srandom_r(&mut pcg, initstate, i as u64 ); + PCGRandom::pcg32_srandom_r(&mut pcg, initstate, i as u64); let actual = PCGRandom::pcg32_random_r(&mut pcg); assert_eq!( actual, expected, @@ -165,7 +164,9 @@ fn test_pcg_concurrent_access() { let expected_sequences: Vec> = (0..num_threads) .map(|thread_id: usize| { PCGRandom::pcg32_srandom_r(&mut pcg, initstate, thread_id as u64); - (0..iterations_per_thread).map(|_| PCGRandom::pcg32_random_r(&mut pcg)).collect() + (0..iterations_per_thread) + .map(|_| PCGRandom::pcg32_random_r(&mut pcg)) + .collect() }) .collect(); @@ -224,10 +225,14 @@ fn test_pcg_thread_independence() { // First, get expected sequences PCGRandom::pcg32_srandom_r(&mut pcg, initstate, 100); - let expected_seq1: Vec = (0..5).map(|_| PCGRandom::pcg32_random_r(&mut pcg)).collect(); + let expected_seq1: Vec = (0..5) + .map(|_| PCGRandom::pcg32_random_r(&mut pcg)) + .collect(); PCGRandom::pcg32_srandom_r(&mut pcg, initstate, 200); - let expected_seq2: Vec = (0..5).map(|_| PCGRandom::pcg32_random_r(&mut pcg)).collect(); + let expected_seq2: Vec = (0..5) + .map(|_| PCGRandom::pcg32_random_r(&mut pcg)) + .collect(); // Run threads concurrently let handle1 = thread::spawn(move || { diff --git a/python/pecos-rslib/rust/src/lib.rs b/python/pecos-rslib/rust/src/lib.rs index 3c4d096a1..61fc6b912 100644 --- a/python/pecos-rslib/rust/src/lib.rs +++ b/python/pecos-rslib/rust/src/lib.rs @@ -20,6 +20,7 @@ mod byte_message_bindings; mod engine_bindings; mod noise_helpers; // mod pcg_bindings; +mod pecos_rng_bindings; pub mod phir_bridge; mod qasm_sim_bindings; mod sparse_sim; @@ -27,15 +28,14 @@ mod sparse_stab_bindings; mod sparse_stab_engine_bindings; mod state_vec_bindings; mod state_vec_engine_bindings; -mod pecos_rng_bindings; use byte_message_bindings::{PyByteMessage, PyByteMessageBuilder}; +use pecos_rng_bindings::RngPcg; +use pyo3::prelude::*; use sparse_stab_bindings::SparseSim; use sparse_stab_engine_bindings::PySparseStabEngine; use state_vec_bindings::RsStateVec; use state_vec_engine_bindings::PyStateVecEngine; -use pecos_rng_bindings::RngPcg; -use pyo3::prelude::*; /// A Python module implemented in Rust. #[pymodule] diff --git a/python/pecos-rslib/rust/src/pecos_rng_bindings.rs b/python/pecos-rslib/rust/src/pecos_rng_bindings.rs index 83cd1fe58..505ea4858 100644 --- a/python/pecos-rslib/rust/src/pecos_rng_bindings.rs +++ b/python/pecos-rslib/rust/src/pecos_rng_bindings.rs @@ -1,6 +1,6 @@ // use rng_pcg::{PCGRandom}; -use pyo3::prelude::*; use pecos::prelude::rng_pcg::PCGRandom; +use pyo3::prelude::*; // use core::prelude::rng_pcg::PCGRandom; From 3a2b0ee5e22cfe58eb94e5f53776749fd8115530 Mon Sep 17 00:00:00 2001 From: Jonhas Colina Date: Wed, 6 Aug 2025 19:25:48 -0400 Subject: [PATCH 11/12] clippy changes --- crates/pecos-rng/src/rng_pcg.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/pecos-rng/src/rng_pcg.rs b/crates/pecos-rng/src/rng_pcg.rs index 74989c7ef..dfeb7de8d 100644 --- a/crates/pecos-rng/src/rng_pcg.rs +++ b/crates/pecos-rng/src/rng_pcg.rs @@ -8,6 +8,7 @@ pub struct PCGRandom { } impl PCGRandom { + #[must_use] pub fn init_global_state() -> PCGRandom { PCGRandom { state: 0x853c_49e6_748f_ea9b, From adf9905a23e4184fb198970b9a70503c89995f34 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Wed, 6 Aug 2025 20:18:30 -0600 Subject: [PATCH 12/12] Fix artifact issue and remove unneeded dependency --- .github/workflows/python-release.yml | 6 ++++++ Cargo.lock | 3 --- crates/pecos-rng/Cargo.toml | 1 - 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index fe1518584..0bf89393e 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -208,6 +208,9 @@ jobs: - name: Install pecos-rslib run: pip install ./pecos-rslib-wheel/*.whl + - name: Install build dependencies + run: pip install build + - name: Build quantum-pecos SDist run: | cd python/quantum-pecos @@ -250,6 +253,9 @@ jobs: - name: Install pecos-rslib run: pip install ./pecos-rslib-wheel/*.whl + - name: Install build dependencies + run: pip install build + - name: Build quantum-pecos wheel run: | cd python/quantum-pecos diff --git a/Cargo.lock b/Cargo.lock index 2f3cc7037..1ec200db9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1916,9 +1916,6 @@ dependencies = [ [[package]] name = "pecos-rng" version = "0.1.1" -dependencies = [ - "pyo3", -] [[package]] name = "pecos-rslib" diff --git a/crates/pecos-rng/Cargo.toml b/crates/pecos-rng/Cargo.toml index 0e5cec07f..96f279bf2 100644 --- a/crates/pecos-rng/Cargo.toml +++ b/crates/pecos-rng/Cargo.toml @@ -12,7 +12,6 @@ categories.workspace = true description = "Random number generators for PECOS quantum computing simulations" [dependencies] -pyo3 = { workspace=true, features = ["extension-module", "abi3-py310", "generate-import-lib"] } [lints] workspace = true