Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
311 changes: 258 additions & 53 deletions Cargo.lock

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,24 @@ pecos-decoders = { version = "0.1.1", path = "crates/pecos-decoders" }
# ldpc decoder wrapper (https://github.com/quantumgizmos/ldpc)
pecos-ldpc-decoders = { version = "0.1.1", path = "crates/pecos-ldpc-decoders" }

# PyMatching decoder wrapper (https://github.com/oscarhiggott/PyMatching)
pecos-pymatching = { version = "0.1.1", path = "crates/pecos-pymatching" }

# Tesseract decoder wrapper (https://github.com/quantumlib/tesseract-decoder)
pecos-tesseract = { version = "0.1.1", path = "crates/pecos-tesseract" }

# Chromobius decoder wrapper (https://github.com/quantumlib/chromobius)
pecos-chromobius = { version = "0.1.1", path = "crates/pecos-chromobius" }

# Fusion Blossom decoder wrapper (pure Rust MWPM)
pecos-fusion-blossom = { version = "0.1.1", path = "crates/pecos-fusion-blossom" }

# Fusion Blossom library (pure Rust MWPM decoder)
fusion-blossom = "0.2"

# petgraph for graph algorithms (used by pymatching)
petgraph = "0.8"

# QuEST simulator wrapper (https://github.com/quest-kit/QuEST)
pecos-quest = { version = "0.1.1", path = "crates/pecos-quest" }

Expand Down
2 changes: 1 addition & 1 deletion crates/pecos-build/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ mod tests {
assert_eq!(qulacs_deps.len(), 3); // qulacs, eigen, boost

let ldpc_deps = manifest.get_crate_dependencies("pecos-ldpc-decoders");
assert!(ldpc_deps.len() >= 5);
assert_eq!(ldpc_deps.len(), 3); // ldpc, stim, boost
}

#[test]
Expand Down
31 changes: 31 additions & 0 deletions crates/pecos-chromobius/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "pecos-chromobius"
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 = "Chromobius decoder wrapper for PECOS"

[dependencies]
pecos-decoder-core.workspace = true
ndarray.workspace = true
thiserror.workspace = true
cxx.workspace = true

[build-dependencies]
pecos-build.workspace = true
cxx-build.workspace = true
cc.workspace = true
env_logger.workspace = true
log.workspace = true

[lib]
name = "pecos_chromobius"

[lints]
workspace = true
13 changes: 13 additions & 0 deletions crates/pecos-chromobius/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Build script for pecos-chromobius

mod build_chromobius;
mod build_stim;
mod chromobius_patch;

fn main() {
// Initialize logger for build script
env_logger::init();

// Build Chromobius (download handled inside build_chromobius)
build_chromobius::build().expect("Chromobius build failed");
}
237 changes: 237 additions & 0 deletions crates/pecos-chromobius/build_chromobius.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
//! Build script for Chromobius decoder integration

use log::info;
use pecos_build::{Manifest, Result, ensure_dep_ready, report_cache_config};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};

// Use the shared modules from the parent
use crate::build_stim;
use crate::chromobius_patch;

/// Get the build profile from Cargo's environment
fn get_build_profile() -> String {
if let Ok(out_dir) = env::var("OUT_DIR") {
let parts: Vec<&str> = out_dir.split(std::path::MAIN_SEPARATOR).collect();
if let Some(target_idx) = parts.iter().position(|&p| p == "target")
&& let Some(profile_name) = parts.get(target_idx + 1)
{
return match *profile_name {
"native" => "native",
"release" => "release",
"debug" => "debug",
_ => {
if env::var("PROFILE").as_deref() == Ok("release") {
"release"
} else {
"debug"
}
}
}
.to_string();
}
}

match env::var("PROFILE").as_deref() {
Ok("release") => "release".to_string(),
_ => "debug".to_string(),
}
}

/// Main build function for Chromobius
pub fn build() -> Result<()> {
println!("cargo:rerun-if-changed=build_chromobius.rs");
println!("cargo:rerun-if-changed=src/bridge.rs");
println!("cargo:rerun-if-changed=src/bridge.cpp");
println!("cargo:rerun-if-changed=include/chromobius_bridge.h");
println!("cargo:rerun-if-env-changed=FORCE_REBUILD");

let out_dir = PathBuf::from(env::var("OUT_DIR")?);

// Always emit link directives - Cargo will cache these
println!("cargo:rustc-link-search=native={}", out_dir.display());
println!("cargo:rustc-link-lib=static=chromobius-bridge");

// Get dependencies (downloads to ~/.pecos/cache/, extracts to ~/.pecos/deps/)
let manifest = Manifest::find_and_load_validated()?;
let chromobius_dir = ensure_dep_ready("chromobius", &manifest)?;
let stim_dir = ensure_dep_ready("stim", &manifest)?;
let pymatching_dir = ensure_dep_ready("pymatching", &manifest)?;

// Apply compatibility patches for newer Stim version
chromobius_patch::patch_chromobius_for_newer_stim(&chromobius_dir)?;

// Generate amalgamated stim.h if needed
build_stim::generate_amalgamated_header(&stim_dir)?;

// Build using cxx
build_cxx_bridge(&chromobius_dir, &stim_dir, &pymatching_dir)?;

Ok(())
}

fn build_cxx_bridge(chromobius_dir: &Path, stim_dir: &Path, pymatching_dir: &Path) -> Result<()> {
let chromobius_src_dir = chromobius_dir.join("src");
let stim_src_dir = stim_dir.join("src");
let pymatching_src_dir = pymatching_dir.join("src");

// Find essential source files
let chromobius_files = collect_chromobius_sources(&chromobius_src_dir)?;
let stim_files = build_stim::collect_stim_sources(&stim_src_dir);
let pymatching_files = collect_pymatching_sources(&pymatching_src_dir)?;

// Build the cxx bridge first to generate headers
let mut build = cxx_build::bridge("src/bridge.rs");

let target = env::var("TARGET").unwrap_or_default();

// On macOS, explicitly use system clang to ensure SDK paths are correct.
if target.contains("darwin") && env::var("CXX").is_err() && env::var("CC").is_err() {
build.compiler("/usr/bin/clang++");
}

// Add our bridge implementation
build.file("src/bridge.cpp");

// Add Chromobius core files
for file in chromobius_files {
build.file(file);
}

// Add PyMatching files
for file in pymatching_files {
build.file(file);
}

// Configure build
build
.std("c++20")
.include(&chromobius_src_dir)
.include(&stim_src_dir)
.include(stim_dir) // For amalgamated stim.h
.include(&pymatching_src_dir)
.include("include")
.include("src")
.define("CHROMOBIUS_BRIDGE_EXPORTS", None);

// Report ccache/sccache configuration
report_cache_config();

// Use build profile for optimization settings
let profile = get_build_profile();
match profile.as_str() {
"native" => {
build.flag_if_supported("-O3");
if env::var("CARGO_CFG_TARGET_ARCH").ok() == env::var("HOST_ARCH").ok() {
build.flag_if_supported("-march=native");
}
}
"release" => {
build.flag_if_supported("-O3");
}
_ => {
build.flag_if_supported("-O0");
build.flag_if_supported("-g");
}
}

// Add Stim files to the main build
for file in &stim_files {
build.file(file);
}

// Platform-specific configurations
if cfg!(not(target_env = "msvc")) {
build
.flag("-fvisibility=hidden")
.flag("-fvisibility-inlines-hidden")
.flag("-w")
.flag_if_supported("-fopenmp")
.flag("-fPIC");

if target.contains("darwin") {
build.flag("-stdlib=libc++");
build.flag("-L/usr/lib");
build.flag("-Wl,-search_paths_first");
}
} else {
build
.flag("/W0")
.flag("/MD")
.flag("/EHsc") // Enable C++ exception handling
.flag_if_supported("/permissive-")
.flag_if_supported("/Zc:__cplusplus");

// Force include standard headers that external libraries assume are available
// MSVC is stricter than GCC/Clang about transitive includes
build.flag("/FI").flag("array"); // For std::array
build.flag("/FI").flag("numeric"); // For std::iota (used by PyMatching)
}

build.compile("chromobius-bridge");

// On macOS, link against the system C++ library
if target.contains("darwin") {
println!("cargo:rustc-link-search=native=/usr/lib");
println!("cargo:rustc-link-lib=c++");
println!("cargo:rustc-link-arg=-Wl,-search_paths_first");
}

Ok(())
}

fn collect_chromobius_sources(chromobius_src_dir: &Path) -> Result<Vec<PathBuf>> {
let mut files = Vec::new();

// Collect all non-test, non-perf, non-pybind .cc files
collect_cc_files_filtered(chromobius_src_dir, &mut files)?;

info!("Found {} Chromobius source files", files.len());
Ok(files)
}

fn collect_pymatching_sources(pymatching_src_dir: &Path) -> Result<Vec<PathBuf>> {
let mut files = Vec::new();

// PyMatching sparse_blossom implementation files
let sparse_blossom_dir = pymatching_src_dir.join("pymatching/sparse_blossom");
if sparse_blossom_dir.exists() {
collect_cc_files_filtered(&sparse_blossom_dir, &mut files)?;
}

info!("Found {} PyMatching source files", files.len());
Ok(files)
}

fn collect_cc_files_filtered(dir: &Path, files: &mut Vec<PathBuf>) -> Result<()> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();

if path.is_dir() {
// Skip test directories
if let Some(name) = path.file_name().and_then(|n| n.to_str())
&& (name == "test" || name == "tests")
{
continue;
}
collect_cc_files_filtered(&path, files)?;
} else if path.extension().and_then(|s| s.to_str()) == Some("cc") {
let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
// Skip test, perf, pybind, and main files
if filename.contains(".test.")
|| filename.contains(".perf.")
|| filename.contains(".pybind.")
|| filename == "main.cc"
{
continue;
}
if !files.contains(&path) {
files.push(path);
}
}
}

Ok(())
}
Loading
Loading