diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..1bcef5b8d2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize all text files to LF in the repo and working copy. +* text=auto eol=lf diff --git a/.github/scripts/check_license_headers.py b/.github/scripts/check_license_headers.py index 936c3227dd..30f623f4de 100644 --- a/.github/scripts/check_license_headers.py +++ b/.github/scripts/check_license_headers.py @@ -16,6 +16,10 @@ from pathlib import Path from typing import List, Optional, Tuple +# Ensure emoji and other Unicode characters can be printed on Windows (cp1252) +if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8") + # Expected license headers for different file types CPP_LICENSE_PATTERNS = [ # Full MIT license header - standard C-style comment with line breaks diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index b151ccedad..323cb9308d 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -62,29 +62,29 @@ jobs: matrix: include: - os-name: Linux - runner: [self-hosted, 1ES.Pool=1es-gh-hb120-pool-wus3, 1ES.ImageOverride=Ubuntu24.04Nightly, 'JobId=qdk_chemistry-linux-py3.10-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] - build-parallel: 120 + runner: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=Ubuntu24.04Nightly, 'JobId=qdk_chemistry-linux-py3.10-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] + build-parallel: 32 uarch: x86-64-v3 python-version: '3.10' cpp-test-threads: 2 python-test-threads: 2 - os-name: Linux - runner: [self-hosted, 1ES.Pool=1es-gh-hb120-pool-wus3, 1ES.ImageOverride=Ubuntu24.04Nightly, 'JobId=qdk_chemistry-linux-py3.13-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] - build-parallel: 120 + runner: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=Ubuntu24.04Nightly, 'JobId=qdk_chemistry-linux-py3.13-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] + build-parallel: 32 uarch: x86-64-v3 python-version: '3.13' cpp-test-threads: 2 python-test-threads: 2 - os-name: Linux - runner: [self-hosted, 1ES.Pool=1es-gh-hb120-pool-wus3, 1ES.ImageOverride=Ubuntu24.04Nightly, 'JobId=qdk_chemistry-linux-py3.14-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] - build-parallel: 120 + runner: [self-hosted, 1ES.Pool=1es-gh-Dads_v5-pool-wus2, 1ES.ImageOverride=Ubuntu24.04Nightly, 'JobId=qdk_chemistry-linux-py3.14-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}'] + build-parallel: 32 uarch: x86-64-v3 python-version: '3.14' cpp-test-threads: 2 python-test-threads: 2 - os-name: macOS runner: macos-26-xlarge - build-parallel: 3 + build-parallel: 5 uarch: native python-version: '3.13' cpp-test-threads: 16 @@ -251,13 +251,21 @@ jobs: - name: Configure C++ RelWithDebInfo/Coverage build run: | + if [ "${{ matrix.os-name }}" == "macOS" ]; then + TOOLCHAIN_FILE="${{ github.workspace }}/.pipelines/toolchains/macos.cmake" + else + TOOLCHAIN_FILE="${{ github.workspace }}/.pipelines/toolchains/linux.cmake" + fi cmake -S cpp -B "$BUILD_DIR" \ -GNinja \ + -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_INSTALL_PREFIX="$CMAKE_INSTALL_PREFIX" \ -DQDK_UARCH="$QDK_UARCH" \ + -DQDK_ENABLE_OPENMP=ON \ -DQDK_CHEMISTRY_ENABLE_COVERAGE=ON \ - -DBUILD_TESTING=ON + -DBUILD_TESTING=ON \ + -DMACIS_ENABLE_TESTS=ON - name: Build C++ RelWithDebInfo/Coverage library run: cmake --build "$BUILD_DIR" diff --git a/.gitignore b/.gitignore index 71be853863..4e8546adae 100644 --- a/.gitignore +++ b/.gitignore @@ -12,16 +12,20 @@ docs/*.txt # Distribution / packaging .Python build/ +build-*/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ +install/ +install-*/ lib/ lib64/ parts/ sdist/ var/ +vcpkg_installed/ wheels/ share/python-wheels/ *.egg-info/ diff --git a/.pipelines/toolchains/linux.cmake b/.pipelines/toolchains/linux.cmake new file mode 100644 index 0000000000..c008bae529 --- /dev/null +++ b/.pipelines/toolchains/linux.cmake @@ -0,0 +1,10 @@ +# Example toolchain: GCC/Clang flag setup for QDK/Chemistry on Linux. +# +# Usage: +# cmake -S cpp -B build -DCMAKE_TOOLCHAIN_FILE=/.pipelines/toolchains/linux.cmake + +foreach(_lang C CXX) + set(CMAKE_${_lang}_FLAGS_DEBUG "-g -O0 -Wall -Wextra" CACHE STRING "" FORCE) + set(CMAKE_${_lang}_FLAGS_RELWITHDEBINFO "-g -O3 -DNDEBUG" CACHE STRING "" FORCE) + set(CMAKE_${_lang}_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "" FORCE) +endforeach() diff --git a/.pipelines/toolchains/macos.cmake b/.pipelines/toolchains/macos.cmake new file mode 100644 index 0000000000..343023a4a6 --- /dev/null +++ b/.pipelines/toolchains/macos.cmake @@ -0,0 +1,8 @@ +# Example toolchain: AppleClang flag setup for QDK/Chemistry on macOS. +# +# Usage: +# cmake -S cpp -B build -DCMAKE_TOOLCHAIN_FILE=/.pipelines/toolchains/macos.cmake +# +# AppleClang uses the same GCC/Clang-style flags as the Linux toolchain. + +include("${CMAKE_CURRENT_LIST_DIR}/linux.cmake") diff --git a/.pipelines/toolchains/windows.cmake b/.pipelines/toolchains/windows.cmake new file mode 100644 index 0000000000..c1370a9d74 --- /dev/null +++ b/.pipelines/toolchains/windows.cmake @@ -0,0 +1,14 @@ +# Example toolchain: Windows (cl.exe and clang-cl) flag setup for QDK/Chemistry. +# +# clang-cl targets the MSVC ABI and accepts MSVC-style flags, so both native +# cl.exe and clang-cl use the same per-config flag setup. +# +# Chainload after the vcpkg toolchain via VCPKG_CHAINLOAD_TOOLCHAIN_FILE: +# -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake +# -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=/.pipelines/toolchains/windows.cmake + +foreach(_lang C CXX) + set(CMAKE_${_lang}_FLAGS_DEBUG "/Zi /Od /RTC1" CACHE STRING "" FORCE) + set(CMAKE_${_lang}_FLAGS_RELWITHDEBINFO "/Zi /O2 /DNDEBUG" CACHE STRING "" FORCE) + set(CMAKE_${_lang}_FLAGS_RELEASE "/O2 /DNDEBUG" CACHE STRING "" FORCE) +endforeach() diff --git a/.pipelines/windows/windows-build-clang-cl-cmake.ps1 b/.pipelines/windows/windows-build-clang-cl-cmake.ps1 new file mode 100644 index 0000000000..73f95faeab --- /dev/null +++ b/.pipelines/windows/windows-build-clang-cl-cmake.ps1 @@ -0,0 +1,460 @@ +# windows-build-clang-cl-cmake.ps1 +# Local script to build and test the QDK Chemistry Python package on Windows using clang-cl and CMake. +# Run from the repo root in an elevated PowerShell (admin) if VS Build Tools need installing. +# +# Usage: +# .\windows-build-clang-cl-cmake.ps1 +# +# This will run a full build. It will check for prerequisites (VS Build Tools with C++ and Clang components, vcpkg, uv) +# and dependencies, and install them if missing. Then it will configure, build, and test the C++ library, install it, +# and finally build and test the Python package. By default, it uses static linking for dependencies (no DLLs). +# +# Optional switches: +# -DynamicDeps # Use dynamic linking for dependencies (DLLs) instead of static. +# # This requires copying DLLs to the Python package folder. +# -SkipPrereqs # Skip prerequisite installation (VS Build tools, vcpkg, etc) +# -SkipCpp # Skip C++ build, only do pip install step (assumes C++ library is already built and installed) +# -SkipConfigure # Skip CMake configure, incremental build only +# -SkipPython # Skip Python build, only do C++ +# -SkipTests # Skip test runs +# -BuildType # CMake build type: Release (default), RelWithDebInfo, or Debug. +# # Debug build skips the Python build (not usable for the Python extension). + +param( + [switch]$DynamicDeps, + [switch]$SkipPrereqs, + [switch]$SkipCpp, + [switch]$SkipConfigure, + [switch]$SkipPython, + [switch]$SkipTests, + [ValidateSet("Release", "RelWithDebInfo", "Debug")] + [string]$BuildType = "Release" +) + +$ErrorActionPreference = "Stop" +$RepoRoot = Get-Location +if (-not (Test-Path "$RepoRoot\cpp\CMakeLists.txt")) { + Write-Error "This script must be run from the repository root." + exit 1 +} +# Debug build is not usable for the Python extension +if ($BuildType -eq "Debug") { $SkipPython = $true } +$buildTypeLower = $BuildType.ToLower() +$BuildDir = "$RepoRoot\cpp\build-clang-cl" + $(if ($BuildType -ne "Release") { "-$buildTypeLower" } else { "" }) +$InstallDir = "$RepoRoot\install-clang-cl" + $(if ($BuildType -ne "Release") { "-$buildTypeLower" } else { "" }) +$VcpkgInstalledDir = "$RepoRoot\vcpkg_installed" +# vcpkg triplets: https://learn.microsoft.com/en-us/vcpkg/users/platforms/windows +# Using dynamic (DLL) dependencies requires copying the corresponding DLL files to qdk-chemistry's Python package +# installation folder. Else, Windows won't find them at runtime and the Python package will fail to import. +# This is because Windows does not have a system-wide DLL search path configuration like Linux's ldconfig. +if ($DynamicDeps) { + $VcpkgTriplet = "x64-windows" +} else { + $VcpkgTriplet = "x64-windows-static-md" +} +$QDK_UARCH = "x86-64-v3" +$NCPUS = [Math]::Max(1, [System.Environment]::ProcessorCount - 2) + +$linkMode = if ($DynamicDeps) { "dynamic" } else { "static" } +Write-Host "============================================" -ForegroundColor Cyan +Write-Host " QDK Chemistry - Windows Build (clang-cl) " -ForegroundColor Cyan +Write-Host "============================================" -ForegroundColor Cyan +Write-Host "Repo root: $RepoRoot" +Write-Host "Build type: $BuildType" +Write-Host "Triplet: $VcpkgTriplet ($linkMode)" +Write-Host "" + +# -------------------------------------------------------------------------- +# Helper: ensure a command exists +# -------------------------------------------------------------------------- +function Assert-Command($Name) { + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + Write-Error "$Name not found in PATH. Please install it first." + exit 1 + } +} + +# ========================================================================== +# STEP 0 - Prerequisites +# ========================================================================== +if (-not $SkipPrereqs) { + Write-Host "" + Write-Host "=== Step 0: Checking / installing prerequisites ===" -ForegroundColor Yellow + + # --- 0a. VS Build Tools with clang-cl --- + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $clangCl = $null + + # Search existing VS installations for clang-cl (-products * includes BuildTools) + if (Test-Path $vswhere) { + $vsPath = & $vswhere -latest -products * -property installationPath 2>$null + if ($vsPath) { + $candidates = @( + "$vsPath\VC\Tools\Llvm\x64\bin\clang-cl.exe", + "$vsPath\VC\Tools\Llvm\bin\clang-cl.exe" + ) + foreach ($c in $candidates) { + if (Test-Path $c) { $clangCl = $c; break } + } + } + } + + # Fall back to standalone LLVM installations (winget / installer / chocolatey) + if (-not $clangCl) { + $standaloneCandidates = @( + "${env:ProgramFiles}\LLVM\bin\clang-cl.exe", + "${env:ProgramFiles(x86)}\LLVM\bin\clang-cl.exe", + "${env:LocalAppData}\Programs\LLVM\bin\clang-cl.exe" + ) + foreach ($c in $standaloneCandidates) { + if (Test-Path $c) { $clangCl = $c; break } + } + } + + if (-not $clangCl) { + Write-Host "clang-cl not found. Installing VS Build Tools with C++ and Clang components..." -ForegroundColor Magenta + Write-Host "This requires an elevated (admin) PowerShell and will take several minutes." + Write-Host "" + + $installerUrl = "https://aka.ms/vs/17/release/vs_BuildTools.exe" + $installerPath = "$env:TEMP\vs_BuildTools.exe" + + if (-not (Test-Path $installerPath)) { + Write-Host "Downloading VS Build Tools installer..." + Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath -UseBasicParsing + } + + Write-Host "Running VS Build Tools installer (this may take 10-20 minutes)..." + $installArgs = @( + "--quiet", "--wait", "--norestart", + "--add", "Microsoft.VisualStudio.Workload.VCTools", + "--add", "Microsoft.VisualStudio.Component.VC.Llvm.Clang", + "--add", "Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset", + "--add", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "--add", "Microsoft.VisualStudio.Component.Windows11SDK.26100", + "--includeRecommended" + ) + $proc = Start-Process -FilePath $installerPath -ArgumentList $installArgs -Wait -PassThru + if ($proc.ExitCode -ne 0 -and $proc.ExitCode -ne 3010) { + Write-Error "VS Build Tools installation failed with exit code $($proc.ExitCode)" + exit 1 + } + Write-Host "VS Build Tools installed successfully." -ForegroundColor Green + + # Re-search for clang-cl + $vsPath = & $vswhere -latest -products * -property installationPath 2>$null + if ($vsPath) { + $candidates = @( + "$vsPath\VC\Tools\Llvm\x64\bin\clang-cl.exe", + "$vsPath\VC\Tools\Llvm\bin\clang-cl.exe" + ) + foreach ($c in $candidates) { + if (Test-Path $c) { $clangCl = $c; break } + } + } + if (-not $clangCl) { + $standaloneCandidates = @( + "${env:ProgramFiles}\LLVM\bin\clang-cl.exe", + "${env:ProgramFiles(x86)}\LLVM\bin\clang-cl.exe", + "${env:LocalAppData}\Programs\LLVM\bin\clang-cl.exe" + ) + foreach ($c in $standaloneCandidates) { + if (Test-Path $c) { $clangCl = $c; break } + } + } + + if (-not $clangCl) { + Write-Error "clang-cl still not found after installing VS Build Tools." + exit 1 + } + } + + $clangDir = Split-Path $clangCl + Write-Host "Using clang-cl: $clangCl" -ForegroundColor Green + & $clangCl --version + + # Add clang-cl to PATH for this session + $env:PATH = "$clangDir;$env:PATH" + + # --- 0b. Set up MSVC environment (vcvarsall) --- + Write-Host "" + Write-Host "Setting up MSVC developer environment..." + $vsPath = & $vswhere -latest -products * -property installationPath + if (-not $vsPath) { + Write-Error "No Visual Studio installation found." + exit 1 + } + $vcvarsall = "$vsPath\VC\Auxiliary\Build\vcvarsall.bat" + if (-not (Test-Path $vcvarsall)) { + Write-Error "vcvarsall.bat not found at $vcvarsall" + exit 1 + } + + # Capture environment from vcvarsall + $envBefore = @{} + Get-ChildItem env: | ForEach-Object { $envBefore[$_.Name] = $_.Value } + + $tempFile = [System.IO.Path]::GetTempFileName() + cmd /c "`"$vcvarsall`" x64 && set > `"$tempFile`"" + Get-Content $tempFile | ForEach-Object { + if ($_ -match "^([^=]+)=(.*)$") { + $name = $matches[1] + $value = $matches[2] + if ($envBefore[$name] -ne $value) { + [System.Environment]::SetEnvironmentVariable($name, $value, "Process") + } + } + } + Remove-Item $tempFile + Write-Host "MSVC developer environment configured." -ForegroundColor Green + + # Re-add clang-cl to PATH (vcvarsall may have reset it) + $env:PATH = "$clangDir;$env:PATH" + + # --- 0c. vcpkg --- + # Check VS-bundled vcpkg first, then VCPKG_INSTALLATION_ROOT, then bootstrap + $vcpkgRoot = $null + $vsVcpkg = "$vsPath\VC\vcpkg\vcpkg.exe" + if (Test-Path $vsVcpkg) { + $vcpkgRoot = Split-Path $vsVcpkg + } elseif ($env:VCPKG_INSTALLATION_ROOT -and (Test-Path "$($env:VCPKG_INSTALLATION_ROOT)\vcpkg.exe")) { + $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT + } else { + $vcpkgRoot = "$RepoRoot\vcpkg-tool" + if (-not (Test-Path "$vcpkgRoot\vcpkg.exe")) { + Write-Host "" + Write-Host "Bootstrapping vcpkg..." + git clone https://github.com/microsoft/vcpkg.git "$vcpkgRoot" + & "$vcpkgRoot\bootstrap-vcpkg.bat" -disableMetrics + } + } + Write-Host "Using vcpkg: $vcpkgRoot\vcpkg.exe" -ForegroundColor Green + + # --- 0d. Install vcpkg packages --- + Write-Host "" + Write-Host "Installing vcpkg dependencies with triplet '$VcpkgTriplet' (this may take a while on first run)..." + & "$vcpkgRoot\vcpkg.exe" install ` + --triplet $VcpkgTriplet ` + --x-manifest-root="$RepoRoot" ` + --x-install-root="$VcpkgInstalledDir" ` + --overlay-ports="$RepoRoot\vcpkg-overlay\ports" + if ($LASTEXITCODE -ne 0) { + Write-Error "vcpkg install failed" + exit 1 + } + Write-Host "vcpkg dependencies installed." -ForegroundColor Green + + # --- 0e. Set CMake/vcpkg environment variables --- + $toolchainFile = "$vcpkgRoot\scripts\buildsystems\vcpkg.cmake" + $env:CMAKE_TOOLCHAIN_FILE = $toolchainFile + $env:VCPKG_TARGET_TRIPLET = $VcpkgTriplet + $env:VCPKG_INSTALLED_DIR = $VcpkgInstalledDir + $env:CMAKE_PREFIX_PATH = "$VcpkgInstalledDir\$VcpkgTriplet" + # Dynamic triplets produce runtime DLLs that must be on PATH for the build tools + if ($DynamicDeps) { + $env:PATH = "$VcpkgInstalledDir\$VcpkgTriplet\bin;$VcpkgInstalledDir\$VcpkgTriplet\debug\bin;$env:PATH" + } + + # --- 0f. uv (Python package manager) --- + if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { + Write-Host "uv not found. Installing..." -ForegroundColor Magenta + Invoke-RestMethod https://astral.sh/uv/install.ps1 | Invoke-Expression + if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { + Write-Error "uv installation failed. Install manually: https://docs.astral.sh/uv/getting-started/installation/" + exit 1 + } + } + Write-Host "Using uv: $(Get-Command uv | Select-Object -ExpandProperty Source) ($(uv --version))" -ForegroundColor Green + +} else { + Write-Host "=== Skipping prerequisites (reusing previous environment) ===" -ForegroundColor DarkGray + + # Even when skipping, we need the environment set up + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $vsPath = & $vswhere -latest -products * -property installationPath 2>$null + $candidates = @( + "$vsPath\VC\Tools\Llvm\x64\bin\clang-cl.exe", + "$vsPath\VC\Tools\Llvm\bin\clang-cl.exe", + "${env:ProgramFiles}\LLVM\bin\clang-cl.exe", + "${env:ProgramFiles(x86)}\LLVM\bin\clang-cl.exe", + "${env:LocalAppData}\Programs\LLVM\bin\clang-cl.exe" + ) + foreach ($c in $candidates) { + if (Test-Path $c) { + $clangDir = Split-Path $c + $env:PATH = "$clangDir;$env:PATH" + break + } + } + + # Set up MSVC env + $vcvarsall = "$vsPath\VC\Auxiliary\Build\vcvarsall.bat" + if (Test-Path $vcvarsall) { + $tempFile = [System.IO.Path]::GetTempFileName() + cmd /c "`"$vcvarsall`" x64 && set > `"$tempFile`"" + Get-Content $tempFile | ForEach-Object { + if ($_ -match "^([^=]+)=(.*)$") { + [System.Environment]::SetEnvironmentVariable($matches[1], $matches[2], "Process") + } + } + Remove-Item $tempFile + # Re-add clang-cl to PATH + $env:PATH = "$clangDir;$env:PATH" + } + Assert-Command "clang-cl" + Assert-Command "cmake" + Assert-Command "ninja" + + # vcpkg env - check VS-bundled vcpkg first + $vcpkgRoot = $null + $vsVcpkg = "$vsPath\VC\vcpkg\vcpkg.exe" + if (Test-Path $vsVcpkg) { + $vcpkgRoot = Split-Path $vsVcpkg + } elseif ($env:VCPKG_INSTALLATION_ROOT -and (Test-Path "$($env:VCPKG_INSTALLATION_ROOT)\vcpkg.exe")) { + $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT + } else { + $vcpkgRoot = "$RepoRoot\vcpkg-tool" + } + $env:CMAKE_TOOLCHAIN_FILE = "$vcpkgRoot\scripts\buildsystems\vcpkg.cmake" + $env:VCPKG_TARGET_TRIPLET = $VcpkgTriplet + $env:VCPKG_INSTALLED_DIR = $VcpkgInstalledDir + $env:CMAKE_PREFIX_PATH = "$VcpkgInstalledDir\$VcpkgTriplet" + # Dynamic triplets produce runtime DLLs that must be on PATH for the build tools + if ($DynamicDeps) { + $env:PATH = "$VcpkgInstalledDir\$VcpkgTriplet\bin;$VcpkgInstalledDir\$VcpkgTriplet\debug\bin;$env:PATH" + } +} + +# Verify tools +Write-Host "" +Write-Host "=== Environment summary ===" -ForegroundColor Yellow +Write-Host " clang-cl: $(Get-Command clang-cl | Select-Object -ExpandProperty Source)" +Write-Host " cmake: $(Get-Command cmake | Select-Object -ExpandProperty Source)" +Write-Host " ninja: $(Get-Command ninja | Select-Object -ExpandProperty Source)" +Write-Host " python: $(Get-Command python | Select-Object -ExpandProperty Source) ($(python --version 2>&1))" +Write-Host " uv: $(Get-Command uv | Select-Object -ExpandProperty Source) ($(uv --version 2>&1))" +Write-Host " TOOLCHAIN_FILE: $env:CMAKE_TOOLCHAIN_FILE" +Write-Host " VCPKG_TRIPLET: $env:VCPKG_TARGET_TRIPLET" +Write-Host " VCPKG_INSTALLED: $env:VCPKG_INSTALLED_DIR" +Write-Host "" + +# ========================================================================== +# STEP 1 - C++ Build +# ========================================================================== +if (-not $SkipCpp) { + if (-not $SkipConfigure) { + Write-Host "=== Step 1: Configure C++ build ===" -ForegroundColor Yellow + cmake -S cpp -B "$BuildDir" ` + -GNinja ` + -DQDK_UARCH="$QDK_UARCH" ` + -DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF ` + -DQDK_CHEMISTRY_ENABLE_MPI=OFF ` + -DMACIS_ENABLE_TESTS=ON ` + -DBUILD_SHARED_LIBS=OFF ` + -DBUILD_TESTING=ON ` + -DCMAKE_BUILD_TYPE="$BuildType" ` + -DCMAKE_C_COMPILER=clang-cl ` + -DCMAKE_CXX_COMPILER=clang-cl ` + -DCMAKE_INSTALL_PREFIX="$InstallDir" ` + -DCMAKE_TOOLCHAIN_FILE="$env:CMAKE_TOOLCHAIN_FILE" ` + -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="$RepoRoot\.pipelines\toolchains\windows.cmake" ` + -DVCPKG_TARGET_TRIPLET="$env:VCPKG_TARGET_TRIPLET" ` + -DVCPKG_INSTALLED_DIR="$env:VCPKG_INSTALLED_DIR" ` + -DFETCHCONTENT_QUIET=OFF + if ($LASTEXITCODE -ne 0) { Write-Error "CMake configure failed"; exit 1 } + } else { + Write-Host "=== Step 1: Skipping configure (incremental build) ===" -ForegroundColor DarkGray + if (-not (Test-Path "$BuildDir\build.ninja")) { + Write-Error "No existing build found at $BuildDir. Run without -SkipConfigure first." + exit 1 + } + } + + Write-Host "" + Write-Host "=== Step 2: Build C++ library ===" -ForegroundColor Yellow + cmake --build "$BuildDir" --parallel $NCPUS 2>&1 *> "$BuildDir\build.log" + if ($LASTEXITCODE -ne 0) { Write-Error "CMake build failed"; exit 1 } + Write-Host "C++ build succeeded." -ForegroundColor Green + + if (-not $SkipTests) { + Write-Host "" + Write-Host "=== Step 3: Run C++ tests ===" -ForegroundColor Yellow + Push-Location "$BuildDir" + $env:OMP_NUM_THREADS = 2 + ctest --output-on-failure --verbose --timeout 400 --output-junit ctest_results.xml 2>&1 *> ctest_clang_cl.log + $ctestExit = $LASTEXITCODE + Pop-Location + if ($ctestExit -ne 0) { + Write-Warning "Some C++ tests failed (exit code: $ctestExit)" + } else { + Write-Host "All C++ tests passed." -ForegroundColor Green + } + } + + Write-Host "" + Write-Host "=== Step 4: Install C++ library ===" -ForegroundColor Yellow + cmake --install "$BuildDir" --prefix "$InstallDir" + if ($LASTEXITCODE -ne 0) { Write-Error "CMake install failed"; exit 1 } + Write-Host "C++ library installed to $InstallDir" -ForegroundColor Green + +} else { + Write-Host "=== Skipping C++ build ===" -ForegroundColor DarkGray +} + +# ========================================================================== +# STEP 2 - Python Build +# ========================================================================== +if (-not $SkipPython) { + Write-Host "" + Write-Host "=== Step 5: Install Python package ===" -ForegroundColor Yellow + Push-Location "$RepoRoot\python" + + $env:CMAKE_BUILD_PARALLEL_LEVEL = "$NCPUS" + if (-not (Test-Path .\venv)) { + uv venv .\venv + } + .\venv\Scripts\activate.ps1 + # Do not install: + # - plugins: pyscf does not build on Windows + # - jupyter: requires plugins + # Resolve the full path to clang-cl so that the scikit-build-core subprocess + # (which does not inherit our modified PATH) can locate the compiler. + $clangClExe = (Get-Command clang-cl -ErrorAction Stop).Source + uv pip install -v .[test] ` + -C cmake.args=-GNinja ` + -C cmake.define.CMAKE_PREFIX_PATH="$env:CMAKE_PREFIX_PATH;$InstallDir" ` + -C cmake.define.CMAKE_C_COMPILER="$clangClExe" ` + -C cmake.define.CMAKE_CXX_COMPILER="$clangClExe" ` + -C cmake.define.CMAKE_TOOLCHAIN_FILE="$env:CMAKE_TOOLCHAIN_FILE" ` + -C cmake.define.VCPKG_CHAINLOAD_TOOLCHAIN_FILE="$RepoRoot\.pipelines\toolchains\windows.cmake" ` + -C cmake.define.VCPKG_TARGET_TRIPLET="$env:VCPKG_TARGET_TRIPLET" ` + -C cmake.define.VCPKG_INSTALLED_DIR="$env:VCPKG_INSTALLED_DIR" + if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Error "Python package install failed"; exit 1 } + + python -c "import qdk_chemistry; print('qdk_chemistry version:', qdk_chemistry.__version__)" + if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Error "Python import check failed"; exit 1 } + Write-Host "Python package installed successfully." -ForegroundColor Green + + if (-not $SkipTests) { + Write-Host "" + Write-Host "=== Step 6: Run Python tests ===" -ForegroundColor Yellow + $env:OMP_NUM_THREADS = 2 + $env:QDK_CHEMISTRY_RUN_SLOW_TESTS = 1 + pytest -v --tb=short 2>&1 *> pytest_clang_cl.log + $pytestExit = $LASTEXITCODE + if ($pytestExit -ne 0) { + Write-Warning "Some Python tests failed (exit code: $pytestExit)" + } else { + Write-Host "All Python tests passed." -ForegroundColor Green + } + } + + Pop-Location +} else { + Write-Host "=== Skipping Python build ===" -ForegroundColor DarkGray +} + +Write-Host "" +Write-Host "============================================" -ForegroundColor Cyan +Write-Host " Build script finished! " -ForegroundColor Cyan +Write-Host "============================================" -ForegroundColor Cyan diff --git a/.pipelines/windows/windows-build-msvc-cmake.ps1 b/.pipelines/windows/windows-build-msvc-cmake.ps1 new file mode 100644 index 0000000000..c2a5b816d9 --- /dev/null +++ b/.pipelines/windows/windows-build-msvc-cmake.ps1 @@ -0,0 +1,379 @@ +# windows-build-msvc-cmake.ps1 +# Local script to build and test the QDK Chemistry Python package on Windows using native MSVC cl.exe and CMake. +# Run from the repo root in an elevated PowerShell (admin) if VS Build Tools need installing. +# +# Usage: +# .\windows-build-msvc-cmake.ps1 +# +# This will run a full build. It will check for prerequisites (VS Build Tools with C++ components, vcpkg, uv) +# and dependencies, and install them if missing. Then it will configure, build, and test the C++ library, install it, +# and finally build and test the Python package. By default, it uses static linking for dependencies (no DLLs). +# +# Optional switches: +# -DynamicDeps # Use dynamic linking for dependencies (DLLs) instead of static. +# # This requires copying DLLs to the Python package folder. +# -SkipPrereqs # Skip prerequisite installation (VS Build tools, vcpkg, etc) +# -SkipCpp # Skip C++ build, only do pip install step (assumes C++ library is already built and installed) +# -SkipConfigure # Skip CMake configure, incremental build only +# -SkipPython # Skip Python build, only do C++ +# -SkipTests # Skip test runs +# -BuildType # CMake build type: Release (default), RelWithDebInfo, or Debug. +# # Debug build skips the Python build (not usable for the Python extension). + +param( + [switch]$DynamicDeps, + [switch]$SkipPrereqs, + [switch]$SkipCpp, + [switch]$SkipConfigure, + [switch]$SkipPython, + [switch]$SkipTests, + [ValidateSet("Release", "RelWithDebInfo", "Debug")] + [string]$BuildType = "Release" +) + +$ErrorActionPreference = "Stop" +$RepoRoot = Get-Location +if (-not (Test-Path "$RepoRoot\cpp\CMakeLists.txt")) { + Write-Error "This script must be run from the repository root." + exit 1 +} +$buildTypeLower = $BuildType.ToLower() +$BuildDir = "$RepoRoot\cpp\build-msvc" + $(if ($BuildType -ne "Release") { "-$buildTypeLower" } else { "" }) +$InstallDir = "$RepoRoot\install-msvc" + $(if ($BuildType -ne "Release") { "-$buildTypeLower" } else { "" }) +# Debug build is not usable for the Python extension +if ($BuildType -eq "Debug") { $SkipPython = $true } +$VcpkgInstalledDir = "$RepoRoot\vcpkg_installed" +# vcpkg triplets: https://learn.microsoft.com/en-us/vcpkg/users/platforms/windows +# Using dynamic (DLL) dependencies requires copying the corresponding DLL files to qdk-chemistry's Python package +# installation folder. Else, Windows won't find them at runtime and the Python package will fail to import. +# This is because Windows does not have a system-wide DLL search path configuration like Linux's ldconfig. +if ($DynamicDeps) { + $VcpkgTriplet = "x64-windows" +} else { + $VcpkgTriplet = "x64-windows-static-md" +} +$QDK_UARCH = "x86-64-v3" +$NCPUS = [Math]::Max(1, [System.Environment]::ProcessorCount - 2) + +$linkMode = if ($DynamicDeps) { "dynamic" } else { "static" } +Write-Host "============================================" -ForegroundColor Cyan +Write-Host " QDK Chemistry - Windows Build (MSVC cl) " -ForegroundColor Cyan +Write-Host "============================================" -ForegroundColor Cyan +Write-Host "Repo root: $RepoRoot" +Write-Host "Build type: $BuildType" +Write-Host "Triplet: $VcpkgTriplet ($linkMode)" +Write-Host "" + +# -------------------------------------------------------------------------- +# Helper: ensure a command exists +# -------------------------------------------------------------------------- +function Assert-Command($Name) { + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + Write-Error "$Name not found in PATH. Please install it first." + exit 1 + } +} + +# ========================================================================== +# STEP 0 - Prerequisites +# ========================================================================== +if (-not $SkipPrereqs) { + Write-Host "" + Write-Host "=== Step 0: Checking / installing prerequisites ===" -ForegroundColor Yellow + + # --- 0a. VS Build Tools with MSVC cl --- + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + + if (-not (Test-Path $vswhere)) { + Write-Host "VS Build Tools not found. Installing..." -ForegroundColor Magenta + Write-Host "This requires an elevated (admin) PowerShell and will take several minutes." + Write-Host "" + + $installerUrl = "https://aka.ms/vs/17/release/vs_BuildTools.exe" + $installerPath = "$env:TEMP\vs_BuildTools.exe" + + if (-not (Test-Path $installerPath)) { + Write-Host "Downloading VS Build Tools installer..." + Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath -UseBasicParsing + } + + Write-Host "Running VS Build Tools installer (this may take 10-20 minutes)..." + $installArgs = @( + "--quiet", "--wait", "--norestart", + "--add", "Microsoft.VisualStudio.Workload.VCTools", + "--add", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "--add", "Microsoft.VisualStudio.Component.Windows11SDK.26100", + "--includeRecommended" + ) + $proc = Start-Process -FilePath $installerPath -ArgumentList $installArgs -Wait -PassThru + if ($proc.ExitCode -ne 0 -and $proc.ExitCode -ne 3010) { + Write-Error "VS Build Tools installation failed with exit code $($proc.ExitCode)" + exit 1 + } + Write-Host "VS Build Tools installed successfully." -ForegroundColor Green + } + + # --- 0b. Set up MSVC environment (vcvarsall) --- + Write-Host "" + Write-Host "Setting up MSVC developer environment..." + $vsPath = & $vswhere -latest -products * -property installationPath + if (-not $vsPath) { + Write-Error "No Visual Studio installation found." + exit 1 + } + $vcvarsall = "$vsPath\VC\Auxiliary\Build\vcvarsall.bat" + if (-not (Test-Path $vcvarsall)) { + Write-Error "vcvarsall.bat not found at $vcvarsall" + exit 1 + } + + # Capture environment from vcvarsall + $envBefore = @{} + Get-ChildItem env: | ForEach-Object { $envBefore[$_.Name] = $_.Value } + + $tempFile = [System.IO.Path]::GetTempFileName() + cmd /c "`"$vcvarsall`" x64 && set > `"$tempFile`"" + Get-Content $tempFile | ForEach-Object { + if ($_ -match "^([^=]+)=(.*)$") { + $name = $matches[1] + $value = $matches[2] + if ($envBefore[$name] -ne $value) { + [System.Environment]::SetEnvironmentVariable($name, $value, "Process") + } + } + } + Remove-Item $tempFile + Write-Host "MSVC developer environment configured." -ForegroundColor Green + + # Verify cl.exe is available + Assert-Command "cl" + Write-Host "Using cl: $(Get-Command cl | Select-Object -ExpandProperty Source)" -ForegroundColor Green + + # --- 0c. vcpkg --- + # Check VS-bundled vcpkg first, then VCPKG_INSTALLATION_ROOT, then bootstrap + $vcpkgRoot = $null + $vsVcpkg = "$vsPath\VC\vcpkg\vcpkg.exe" + if (Test-Path $vsVcpkg) { + $vcpkgRoot = Split-Path $vsVcpkg + } elseif ($env:VCPKG_INSTALLATION_ROOT -and (Test-Path "$($env:VCPKG_INSTALLATION_ROOT)\vcpkg.exe")) { + $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT + } else { + $vcpkgRoot = "$RepoRoot\vcpkg-tool" + if (-not (Test-Path "$vcpkgRoot\vcpkg.exe")) { + Write-Host "" + Write-Host "Bootstrapping vcpkg..." + git clone https://github.com/microsoft/vcpkg.git "$vcpkgRoot" + & "$vcpkgRoot\bootstrap-vcpkg.bat" -disableMetrics + } + } + Write-Host "Using vcpkg: $vcpkgRoot\vcpkg.exe" -ForegroundColor Green + + # --- 0d. Install vcpkg packages --- + Write-Host "" + Write-Host "Installing vcpkg dependencies with triplet '$VcpkgTriplet' (this may take a while on first run)..." + & "$vcpkgRoot\vcpkg.exe" install ` + --triplet $VcpkgTriplet ` + --x-manifest-root="$RepoRoot" ` + --x-install-root="$VcpkgInstalledDir" ` + --overlay-ports="$RepoRoot\vcpkg-overlay\ports" + if ($LASTEXITCODE -ne 0) { + Write-Error "vcpkg install failed" + exit 1 + } + Write-Host "vcpkg dependencies installed." -ForegroundColor Green + + # --- 0e. Set CMake/vcpkg environment variables --- + $toolchainFile = "$vcpkgRoot\scripts\buildsystems\vcpkg.cmake" + $env:CMAKE_TOOLCHAIN_FILE = $toolchainFile + $env:VCPKG_TARGET_TRIPLET = $VcpkgTriplet + $env:VCPKG_INSTALLED_DIR = $VcpkgInstalledDir + $env:CMAKE_PREFIX_PATH = "$VcpkgInstalledDir\$VcpkgTriplet" + # Dynamic triplets produce runtime DLLs that must be on PATH for the build tools + if ($DynamicDeps) { + $env:PATH = "$VcpkgInstalledDir\$VcpkgTriplet\bin;$VcpkgInstalledDir\$VcpkgTriplet\debug\bin;$env:PATH" + } + + # --- 0f. uv (Python package manager) --- + if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { + Write-Host "uv not found. Installing..." -ForegroundColor Magenta + Invoke-RestMethod https://astral.sh/uv/install.ps1 | Invoke-Expression + if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { + Write-Error "uv installation failed. Install manually: https://docs.astral.sh/uv/getting-started/installation/" + exit 1 + } + } + Write-Host "Using uv: $(Get-Command uv | Select-Object -ExpandProperty Source) ($(uv --version))" -ForegroundColor Green + +} else { + Write-Host "=== Skipping prerequisites (reusing previous environment) ===" -ForegroundColor DarkGray + + # Even when skipping, we need the environment set up + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $vsPath = & $vswhere -latest -products * -property installationPath 2>$null + + # Set up MSVC env + $vcvarsall = "$vsPath\VC\Auxiliary\Build\vcvarsall.bat" + if (Test-Path $vcvarsall) { + $tempFile = [System.IO.Path]::GetTempFileName() + cmd /c "`"$vcvarsall`" x64 && set > `"$tempFile`"" + Get-Content $tempFile | ForEach-Object { + if ($_ -match "^([^=]+)=(.*)$") { + [System.Environment]::SetEnvironmentVariable($matches[1], $matches[2], "Process") + } + } + Remove-Item $tempFile + } + Assert-Command "cl" + Assert-Command "cmake" + Assert-Command "ninja" + + # vcpkg env - check VS-bundled vcpkg first + $vcpkgRoot = $null + $vsVcpkg = "$vsPath\VC\vcpkg\vcpkg.exe" + if (Test-Path $vsVcpkg) { + $vcpkgRoot = Split-Path $vsVcpkg + } elseif ($env:VCPKG_INSTALLATION_ROOT -and (Test-Path "$($env:VCPKG_INSTALLATION_ROOT)\vcpkg.exe")) { + $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT + } else { + $vcpkgRoot = "$RepoRoot\vcpkg-tool" + } + $env:CMAKE_TOOLCHAIN_FILE = "$vcpkgRoot\scripts\buildsystems\vcpkg.cmake" + $env:VCPKG_TARGET_TRIPLET = $VcpkgTriplet + $env:VCPKG_INSTALLED_DIR = $VcpkgInstalledDir + $env:CMAKE_PREFIX_PATH = "$VcpkgInstalledDir\$VcpkgTriplet" + # Dynamic triplets produce runtime DLLs that must be on PATH for the build tools + if ($DynamicDeps) { + $env:PATH = "$VcpkgInstalledDir\$VcpkgTriplet\bin;$VcpkgInstalledDir\$VcpkgTriplet\debug\bin;$env:PATH" + } +} + +# Verify tools +Write-Host "" +Write-Host "=== Environment summary ===" -ForegroundColor Yellow +Write-Host " cl: $(Get-Command cl | Select-Object -ExpandProperty Source)" +Write-Host " cmake: $(Get-Command cmake | Select-Object -ExpandProperty Source)" +Write-Host " ninja: $(Get-Command ninja | Select-Object -ExpandProperty Source)" +Write-Host " python: $(Get-Command python | Select-Object -ExpandProperty Source) ($(python --version 2>&1))" +Write-Host " uv: $(Get-Command uv | Select-Object -ExpandProperty Source) ($(uv --version 2>&1))" +Write-Host " TOOLCHAIN_FILE: $env:CMAKE_TOOLCHAIN_FILE" +Write-Host " VCPKG_TRIPLET: $env:VCPKG_TARGET_TRIPLET" +Write-Host " VCPKG_INSTALLED: $env:VCPKG_INSTALLED_DIR" +Write-Host "" + +# ========================================================================== +# STEP 1 - C++ Build +# ========================================================================== +if (-not $SkipCpp) { + if (-not $SkipConfigure) { + Write-Host "=== Step 1: Configure C++ build ===" -ForegroundColor Yellow + cmake -S cpp -B "$BuildDir" ` + -GNinja ` + -DQDK_UARCH="$QDK_UARCH" ` + -DQDK_CHEMISTRY_ENABLE_COVERAGE=OFF ` + -DQDK_CHEMISTRY_ENABLE_MPI=OFF ` + -DMACIS_ENABLE_TESTS=ON ` + -DBUILD_SHARED_LIBS=OFF ` + -DBUILD_TESTING=ON ` + -DCMAKE_BUILD_TYPE="$BuildType" ` + -DCMAKE_C_COMPILER=cl ` + -DCMAKE_CXX_COMPILER=cl ` + -DCMAKE_INSTALL_PREFIX="$InstallDir" ` + -DCMAKE_TOOLCHAIN_FILE="$env:CMAKE_TOOLCHAIN_FILE" ` + -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="$RepoRoot\.pipelines\toolchains\windows.cmake" ` + -DVCPKG_TARGET_TRIPLET="$env:VCPKG_TARGET_TRIPLET" ` + -DVCPKG_INSTALLED_DIR="$env:VCPKG_INSTALLED_DIR" ` + -DFETCHCONTENT_QUIET=OFF + if ($LASTEXITCODE -ne 0) { Write-Error "CMake configure failed"; exit 1 } + } else { + Write-Host "=== Step 1: Skipping configure (incremental build) ===" -ForegroundColor DarkGray + if (-not (Test-Path "$BuildDir\build.ninja")) { + Write-Error "No existing build found at $BuildDir. Run without -SkipConfigure first." + exit 1 + } + } + + Write-Host "" + Write-Host "=== Step 2: Build C++ library ===" -ForegroundColor Yellow + cmake --build "$BuildDir" --parallel $NCPUS 2>&1 *> "$BuildDir\build.log" + if ($LASTEXITCODE -ne 0) { Write-Error "CMake build failed"; exit 1 } + Write-Host "C++ build succeeded." -ForegroundColor Green + + if (-not $SkipTests) { + Write-Host "" + Write-Host "=== Step 3: Run C++ tests ===" -ForegroundColor Yellow + Push-Location "$BuildDir" + $env:OMP_NUM_THREADS = 2 + ctest --output-on-failure --verbose --timeout 400 --output-junit ctest_results.xml 2>&1 *> ctest_msvc.log + $ctestExit = $LASTEXITCODE + Pop-Location + if ($ctestExit -ne 0) { + Write-Warning "Some C++ tests failed (exit code: $ctestExit)" + } else { + Write-Host "All C++ tests passed." -ForegroundColor Green + } + } + + Write-Host "" + Write-Host "=== Step 4: Install C++ library ===" -ForegroundColor Yellow + cmake --install "$BuildDir" --prefix "$InstallDir" + if ($LASTEXITCODE -ne 0) { Write-Error "CMake install failed"; exit 1 } + Write-Host "C++ library installed to $InstallDir" -ForegroundColor Green + +} else { + Write-Host "=== Skipping C++ build ===" -ForegroundColor DarkGray +} + +# ========================================================================== +# STEP 2 - Python Build +# ========================================================================== +if (-not $SkipPython) { + Write-Host "" + Write-Host "=== Step 5: Install Python package ===" -ForegroundColor Yellow + Push-Location "$RepoRoot\python" + + $env:CMAKE_BUILD_PARALLEL_LEVEL = "$NCPUS" + if (-not (Test-Path .\venv)) { + uv venv .\venv + } + .\venv\Scripts\activate.ps1 + # Do not install: + # - plugins: pyscf does not build on Windows + # - jupyter: requires plugins + uv pip install -v .[test] ` + -C cmake.args=-GNinja ` + -C cmake.define.CMAKE_PREFIX_PATH="$env:CMAKE_PREFIX_PATH;$InstallDir" ` + -C cmake.define.CMAKE_C_COMPILER=cl ` + -C cmake.define.CMAKE_CXX_COMPILER=cl ` + -C cmake.define.CMAKE_TOOLCHAIN_FILE="$env:CMAKE_TOOLCHAIN_FILE" ` + -C cmake.define.VCPKG_CHAINLOAD_TOOLCHAIN_FILE="$RepoRoot\.pipelines\toolchains\windows.cmake" ` + -C cmake.define.VCPKG_TARGET_TRIPLET="$env:VCPKG_TARGET_TRIPLET" ` + -C cmake.define.VCPKG_INSTALLED_DIR="$env:VCPKG_INSTALLED_DIR" + if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Error "Python package install failed"; exit 1 } + + python -c "import qdk_chemistry; print('qdk_chemistry version:', qdk_chemistry.__version__)" + if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Error "Python import check failed"; exit 1 } + Write-Host "Python package installed successfully." -ForegroundColor Green + + if (-not $SkipTests) { + Write-Host "" + Write-Host "=== Step 6: Run Python tests ===" -ForegroundColor Yellow + $env:OMP_NUM_THREADS = 2 + $env:QDK_CHEMISTRY_RUN_SLOW_TESTS = 1 + pytest -v --tb=short 2>&1 *> pytest_msvc.log + $pytestExit = $LASTEXITCODE + if ($pytestExit -ne 0) { + Write-Warning "Some Python tests failed (exit code: $pytestExit)" + } else { + Write-Host "All Python tests passed." -ForegroundColor Green + } + } + + Pop-Location +} else { + Write-Host "=== Skipping Python build ===" -ForegroundColor DarkGray +} + +Write-Host "" +Write-Host "============================================" -ForegroundColor Cyan +Write-Host " Build script finished! " -ForegroundColor Cyan +Write-Host "============================================" -ForegroundColor Cyan diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d5e45cefa..1d061348ec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,8 +24,10 @@ repos: files: *linted_files - id: trailing-whitespace files: *linted_files + exclude: \.patch$ - id: end-of-file-fixer files: *linted_files + exclude: \.patch$ - id: debug-statements files: *linted_files - id: check-case-conflict diff --git a/INSTALL.md b/INSTALL.md index bdfdc7d5d6..41dc988ee6 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -219,10 +219,12 @@ Build from source if you need to modify the C++ core, work with unreleased featu **Linux**: A Debian-based distribution is recommended for the broadest package availability. Other distributions may require building some dependencies (e.g. Eigen3, nlohmann-json) from source. -**Windows**: Native Windows builds are not supported. Use the [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install) instead. +**Windows**: Native Windows builds (MSVC / clang-cl with vcpkg) are **experimental** and in active development. Helper scripts are available under [`.pipelines/windows/`](.pipelines/windows/). For a fully supported build, use the [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install) instead. **macOS**: The latest version of [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) must be installed. +> **NOTE:** QDK/Chemistry requires a **64-bit** platform. + ### Step 2: Install system dependencies All from-source builds require the dependencies listed in the [Dependencies](#dependencies-for-source-builds) section above. Install those before proceeding. diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index d588883f48..bb2598921b 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -68,10 +68,6 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() -# Add compiler flags -set(CMAKE_CXX_FLAGS_DEBUG_INIT "-g -O0 -Wall -Wextra") -set(CMAKE_CXX_FLAGS_RELEASE_INIT "-O3 -DNDEBUG") - # Options option(QDK_CHEMISTRY_ENABLE_COVERAGE "Enable coverage build" OFF) option(QDK_CHEMISTRY_ENABLE_GPU "Enable GPU acceleration in QDK" OFF) @@ -82,13 +78,17 @@ option(QDK_ALLOW_DEPENDENCY_FETCH "Allow fetching of dependencies" ON) option(QDK_CHEMISTRY_ENABLE_MPI "Enable MPI Bindings for QDK Chemistry" OFF) option(QDK_EMBED_RESOURCE_LOCATION "Point to embedded (permanent) resource location" ON) -# Enable OpenMP by default, except on Apple platforms with AppleClang +# Enable OpenMP by default, except on Apple platforms with AppleClang and on +# Windows (OpenMP + MSVC/clang-cl is not currently supported). cmake_dependent_option(QDK_ENABLE_OPENMP "Enable OpenMP support" ON - "NOT (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL \"AppleClang\")" OFF) + "NOT (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL \"AppleClang\");NOT WIN32" OFF) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) +# Make the patches directory available to subdirectories (e.g. external/macis) +set(QDK_CMAKE_PATCHES_DIR "${PROJECT_SOURCE_DIR}/cmake/patches" CACHE INTERNAL "") + # Custom CMake Find* modules for linear algebra dependencies include( FetchContent ) FetchContent_Declare( linalg-cmake-modules @@ -119,6 +119,7 @@ if(NOT macis_FOUND) set(MACIS_ENABLE_MPI OFF CACHE BOOL "MACIS enable MPI" FORCE) set(MACIS_ENABLE_PYTHON OFF CACHE BOOL "MACIS Enable Python" FORCE) set(MACIS_ENABLE_EXAMPLES OFF CACHE BOOL "MACIS Build Examples" FORCE) + set(MACIS_ENABLE_TESTS ON CACHE BOOL "MACIS Build Tests") set(MACIS_ENABLE_OPENMP ${QDK_ENABLE_OPENMP} CACHE BOOL "MACIS Enable OpenMP" FORCE) if(DEFINED QDK_UARCH_USED) set(MACIS_UARCH ${QDK_UARCH_USED} CACHE STRING "MACIS Microarchitecture" FORCE) @@ -175,6 +176,10 @@ endif() target_include_directories(chemistry PUBLIC ${HDF5_INCLUDE_DIRS}) target_compile_definitions(chemistry PUBLIC ${HDF5_DEFINITIONS}) +if(MSVC) + # MSVC CRT doesn't define M_PI etc. without this. + target_compile_definitions(chemistry PUBLIC _USE_MATH_DEFINES) +endif() target_compile_features(chemistry PUBLIC cxx_std_20) set_property(TARGET chemistry PROPERTY POSITION_INDEPENDENT_CODE ON) if(DEFINED QDK_UARCH_FLAGS) @@ -191,8 +196,12 @@ set_target_properties(chemistry PROPERTIES OUTPUT_NAME "qdk_chemistry") if(QDK_CHEMISTRY_ENABLE_COVERAGE) if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") message(STATUS "Enabling coverage build") - target_compile_options(chemistry PRIVATE --coverage -fprofile-arcs -ftest-coverage) - target_link_libraries(chemistry PRIVATE --coverage) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang" AND NOT MSVC) + target_compile_options(chemistry PRIVATE --coverage -fprofile-arcs -ftest-coverage) + target_link_libraries(chemistry PRIVATE --coverage) + else() + message(WARNING "Coverage build is only supported with GCC or Clang compilers") + endif() else() message(FATAL_ERROR "Coverage build is only supported in CMAKE_BUILD_TYPE=Debug or RelWithDebInfo mode") endif() diff --git a/cpp/cmake/patches/apply-patch.cmake b/cpp/cmake/patches/apply-patch.cmake new file mode 100644 index 0000000000..c51ebd9f90 --- /dev/null +++ b/cpp/cmake/patches/apply-patch.cmake @@ -0,0 +1,31 @@ +# apply-patch.cmake +# Idempotent wrapper around `git apply`. Skips if the patch is already applied. +# +# Usage: cmake -DPATCH_FILE= -P apply-patch.cmake +# (run from the source tree to be patched) + +if(NOT DEFINED PATCH_FILE) + message(FATAL_ERROR "PATCH_FILE must be defined (-DPATCH_FILE=)") +endif() + +find_program(GIT_EXECUTABLE git REQUIRED) + +execute_process( + COMMAND "${GIT_EXECUTABLE}" apply --reverse --check --ignore-whitespace "${PATCH_FILE}" + RESULT_VARIABLE _already_applied + OUTPUT_QUIET ERROR_QUIET +) +if(_already_applied EQUAL 0) + message(STATUS "Patch already applied, skipping: ${PATCH_FILE}") + return() +endif() + +execute_process( + COMMAND "${GIT_EXECUTABLE}" apply --ignore-whitespace "${PATCH_FILE}" + RESULT_VARIABLE _result + ERROR_VARIABLE _error +) +if(NOT _result EQUAL 0) + message(FATAL_ERROR "Failed to apply patch ${PATCH_FILE}:\n${_error}") +endif() +message(STATUS "Applied patch: ${PATCH_FILE}") diff --git a/cpp/cmake/patches/blaspp-msvc.patch b/cpp/cmake/patches/blaspp-msvc.patch new file mode 100644 index 0000000000..c4223f6a3b --- /dev/null +++ b/cpp/cmake/patches/blaspp-msvc.patch @@ -0,0 +1,80 @@ +# 3. CMakeLists.txt: guard -Wall behind a clang-cl check. +# Under clang-cl, -Wall maps to /Wall which is equivalent to -Weverything, +# flooding the build log with ~700k pedantic / C++98-compat warnings. +diff --git a/CMakeLists.txt b/CMakeLists.txt +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -474,9 +474,12 @@ + + if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.15) + # Conditionally add -Wall. See CMake tutorial. +- set( gcc_like_cxx "$" ) +- target_compile_options( +- blaspp PRIVATE "$<${gcc_like_cxx}:$>" ) ++ # Skip clang-cl: it maps -Wall to /Wall (= -Weverything on Clang). ++ if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")) ++ set( gcc_like_cxx "$" ) ++ target_compile_options( ++ blaspp PRIVATE "$<${gcc_like_cxx}:$>" ) ++ endif() + endif() + + #------------------------------------------------------------------------------- +diff --git a/cmake/BLASFinder.cmake b/cmake/BLASFinder.cmake +index 2994f85..fa494c9 100644 +--- a/cmake/BLASFinder.cmake ++++ b/cmake/BLASFinder.cmake +@@ -26,6 +26,7 @@ if (BLAS_LIBRARIES) + # Already checked this BLAS_LIBRARIES; load cached results. + message( STATUS "Using cached BLAS_LIBRARIES settings" ) + set( BLAS_FOUND "${blas_found_cached}" ) ++ set( blaspp_defs_ "${blaspp_defs_cached}" ) + set( run_ false ) + else() + # Need to check BLAS_LIBRARIES. +@@ -42,6 +43,7 @@ elseif ( "${blas}" STREQUAL "${blas_cached}" + message( STATUS "Using cached blas settings" ) + set( BLAS_LIBRARIES "${blas_libraries_cached}" ) + set( BLAS_FOUND "${blas_found_cached}" ) ++ set( blaspp_defs_ "${blaspp_defs_cached}" ) + set( run_ false ) + else() + # Search blas, blas_int, etc. +@@ -549,6 +551,7 @@ set( blas_fortran_cached "${blas_fortran}" CACHE INTERNAL "" ) + set( blas_int_cached "${blas_int}" CACHE INTERNAL "" ) + set( blas_threaded_cached "${blas_threaded}" CACHE INTERNAL "" ) + ++set( blaspp_defs_cached "${blaspp_defs_}" CACHE INTERNAL "" ) + endif() # run_ + #=============================================================================== + +diff --git a/examples/util.hh b/examples/util.hh +index 5e0a509..8812022 100644 +--- a/examples/util.hh ++++ b/examples/util.hh +@@ -16,7 +16,11 @@ void print_func_( const char* func ) + } + + #ifdef __GNUC__ +- #define print_func() print_func_( __PRETTY_FUNCTION__ ) ++ #ifdef _MSC_VER ++ #define print_func() print_func_( __FUNCSIG__ ) ++ #else ++ #define print_func() print_func_( __PRETTY_FUNCTION__ ) ++ #endif + #else + #define print_func() print_func_( __func__ ) + #endif +diff --git a/include/blas/symv.hh b/include/blas/symv.hh +index 0881fae..46d98b2 100644 +--- a/include/blas/symv.hh ++++ b/include/blas/symv.hh +@@ -73,7 +73,7 @@ void symv( + blas::scalar_type beta, + TY *y, int64_t incy ) + { +-printf( "%s: %s\n", __func__, __PRETTY_FUNCTION__ ); ++printf( "%s: %s\n", __func__, __FUNCSIG__ ); + typedef blas::scalar_type scalar_t; + + #define A(i_, j_) A[ (i_) + (j_)*lda ] diff --git a/cpp/cmake/patches/ecpint-msvc-vla.cmake b/cpp/cmake/patches/ecpint-msvc-vla.cmake new file mode 100644 index 0000000000..f02f52cf75 --- /dev/null +++ b/cpp/cmake/patches/ecpint-msvc-vla.cmake @@ -0,0 +1,126 @@ +# ecpint-msvc-vla.cmake +# Patch script to replace C99 variable-length arrays (VLAs) with std::vector +# in libecpint sources. MSVC does not support VLAs. +# Applied via PATCH_COMMAND in FetchContent. +# +# Usage: cmake -P ecpint-msvc-vla.cmake (run from ecpint source root) + +# Helper: read file, apply replacements, write back (only if changed) +function(patch_file FILE_PATH) + file(READ "${FILE_PATH}" _content) + set(_original "${_content}") + + # Apply all remaining ARGN as pairs of (old new) + set(_args ${ARGN}) + list(LENGTH _args _len) + math(EXPR _pairs "${_len} / 2") + set(_i 0) + while(_i LESS _pairs) + math(EXPR _old_idx "${_i} * 2") + math(EXPR _new_idx "${_i} * 2 + 1") + list(GET _args ${_old_idx} _old) + list(GET _args ${_new_idx} _new) + string(REPLACE "${_old}" "${_new}" _content "${_content}") + math(EXPR _i "${_i} + 1") + endwhile() + + if(NOT "${_content}" STREQUAL "${_original}") + file(WRITE "${FILE_PATH}" "${_content}") + message(STATUS " Patched: ${FILE_PATH}") + endif() +endfunction() + +message(STATUS "Patching ecpint for MSVC VLA compatibility...") + +# --- src/lib/mathutil.cpp --- +# double Plm[lmax+1][lmax+1] → std::vector> (preserves [i][j] syntax) +patch_file("src/lib/mathutil.cpp" + "double Plm[lmax+1][lmax+1]" + "std::vector> Plm(lmax+1, std::vector(lmax+1, 0.0))" +) + +# --- src/generate.cpp --- +# double w1_contr[w_size*(lam+LA+1)] → std::vector +# double w2_contr[w_size*(lam+LB+1)] → std::vector +patch_file("src/generate.cpp" + "double w1_contr[w_size*(lam+LA+1)]" + "std::vector w1_contr(w_size*(lam+LA+1))" + "double w2_contr[w_size*(lam+LB+1)]" + "std::vector w2_contr(w_size*(lam+LB+1))" +) + +# --- src/lib/qgen.cpp --- +# Same VLA pattern as generate.cpp +patch_file("src/lib/qgen.cpp" + "double w1_contr[w_size*(lam+LA+1)]" + "std::vector w1_contr(w_size*(lam+LA+1))" + "double w2_contr[w_size*(lam+LB+1)]" + "std::vector w2_contr(w_size*(lam+LB+1))" +) + +# --- src/lib/bessel.cpp --- +# double F[order + 1] → std::vector +patch_file("src/lib/bessel.cpp" + "double F[order + 1]" + "std::vector F(order + 1)" +) + +# --- src/lib/ecpint.cpp --- +# double screens[U.getL() + 1] → std::vector +# Also fix call site: estimate_type2(..., screens) → estimate_type2(..., screens.data()) +# because the function expects double*, and std::vector doesn't implicitly decay. +patch_file("src/lib/ecpint.cpp" + "double screens[U.getL() + 1]" + "std::vector screens(U.getL() + 1)" + "estimate_type2(U, shellA, shellB, data, screens)" + "estimate_type2(U, shellA, shellB, data, screens.data())" +) + +# --- src/lib/radial_gen.cpp --- +# double Ftab[gridSize] → std::vector +# Fix call site: integrate(..., Ftab, ...) → integrate(..., Ftab.data(), ...) +patch_file("src/lib/radial_gen.cpp" + "double Ftab[gridSize]" + "std::vector Ftab(gridSize)" + "transformedGrid.integrate(intgd, Ftab, 1e-12, 0, primGrid.getN() - 1)" + "transformedGrid.integrate(intgd, Ftab.data(), 1e-12, 0, primGrid.getN() - 1)" +) + +# --- src/generated/radial/radial_gen.cpp --- +# double Ftab[gridSize] → std::vector +# Fix call site: integrate(..., Ftab, ...) → integrate(..., Ftab.data(), ...) +patch_file("src/generated/radial/radial_gen.cpp" + "double Ftab[gridSize]" + "std::vector Ftab(gridSize)" + "smallGrid.integrate(intgd, Ftab, 1e-12)" + "smallGrid.integrate(intgd, Ftab.data(), 1e-12)" +) + +# --- src/lib/radial_quad.cpp --- +# Multiple VLAs with gridSize, plus call sites that pass them to functions expecting double*. +patch_file("src/lib/radial_quad.cpp" + "double params[gridSize]" + "std::vector params(gridSize)" + "double Utab[gridSize]" + "std::vector Utab(gridSize)" + "double Utab2[gridSize]" + "std::vector Utab2(gridSize)" + "double Xvals[gridSize]" + "std::vector Xvals(gridSize)" + "double params2[gridSize]" + "std::vector params2(gridSize)" + "grid.integrate(intgd, params, tolerance, start, end)" + "grid.integrate(intgd, params.data(), tolerance, start, end)" + "buildU(U, U.getL(), N, newGrid, Utab)" + "buildU(U, U.getL(), N, newGrid, Utab.data())" + "buildU(U, l, N, smallGrid, Utab)" + "buildU(U, l, N, smallGrid, Utab.data())" + "smallGrid.integrate(intgd, params, tolerance, start, end)" + "smallGrid.integrate(intgd, params.data(), tolerance, start, end)" + "buildU(U, l, N, newGrid, Utab2)" + "buildU(U, l, N, newGrid, Utab2.data())" + "newGrid.integrate(intgd, params2, tolerance, start, end)" + "newGrid.integrate(intgd, params2.data(), tolerance, start, end)" +) + +message(STATUS "ecpint VLA patching complete.") diff --git a/cpp/cmake/patches/lapackpp-msvc-allocator.patch b/cpp/cmake/patches/lapackpp-msvc-allocator.patch new file mode 100644 index 0000000000..87182aa67a --- /dev/null +++ b/cpp/cmake/patches/lapackpp-msvc-allocator.patch @@ -0,0 +1,47 @@ +# MSVC Debug CRT compatibility patch for lapackpp v2025.05.28. +# Apply with: git apply lapackpp-msvc-allocator.patch (run from lapackpp source root) +# +# 1. src/NoConstructAllocator.hh: add template rebind constructor. +# MSVC's _ITERATOR_DEBUG_LEVEL=2 (enabled in Debug builds) causes the std::vector +# destructor to rebind the allocator from NoConstructAllocator to +# NoConstructAllocator for iterator validation tracking. +# The rebind uses a converting constructor that the class lacks, producing C2440 +# errors across all lapackpp source files that use lapack::vector<>. +# +# 2. CMakeLists.txt: guard -Wall behind a clang-cl check. +# Under clang-cl, -Wall maps to /Wall which is equivalent to -Weverything, +# flooding the build log with ~700k pedantic / C++98-compat warnings. +diff --git a/src/NoConstructAllocator.hh b/src/NoConstructAllocator.hh +--- a/src/NoConstructAllocator.hh ++++ b/src/NoConstructAllocator.hh +@@ -25,7 +25,11 @@ struct NoConstructAllocator + using value_type = T; + + NoConstructAllocator() = default; + ++ // MSVC Debug CRT rebinds allocator to allocator<_ContainerProxy> for iterator tracking. ++ template ++ NoConstructAllocator(const NoConstructAllocator&) noexcept {} ++ + // Construction given an allocated pointer is a null-op. + // + // @tparam Args Parameter pack which handles all possible calling +diff --git a/CMakeLists.txt b/CMakeLists.txt +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -698,9 +698,12 @@ + + if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.15) + # Conditionally add -Wall. See CMake tutorial. +- set( gcc_like_cxx "$" ) +- target_compile_options( +- lapackpp PRIVATE "$<${gcc_like_cxx}:$>" ) ++ # Skip clang-cl: it maps -Wall to /Wall (= -Weverything on Clang). ++ if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")) ++ set( gcc_like_cxx "$" ) ++ target_compile_options( ++ lapackpp PRIVATE "$<${gcc_like_cxx}:$>" ) ++ endif() + endif() + + #------------------------------------------------------------------------------- diff --git a/cpp/cmake/patches/libint2-msvc-sse-macros.cmake b/cpp/cmake/patches/libint2-msvc-sse-macros.cmake new file mode 100644 index 0000000000..77e1c972df --- /dev/null +++ b/cpp/cmake/patches/libint2-msvc-sse-macros.cmake @@ -0,0 +1,99 @@ +# libint2-msvc-sse-macros.cmake +# MSVC x64 supports SSE2 but doesn't define __SSE__/__SSE2__, so libint2's +# vector_x86.h skips the SSE2 section that defines VectorSSEDouble. Patch +# vector_x86.h to define these macros. Applied via PATCH_COMMAND in FetchContent. +# Usage: cmake -P libint2-msvc-sse-macros.cmake (run from libint2 source root) + +set(_file "libint-2.9.0/include/libint2/util/vector_x86.h") +if(NOT EXISTS "${_file}") + message(WARNING "libint2-msvc-sse-macros: ${_file} not found, skipping patch") + return() +endif() + +file(READ "${_file}" _content) +set(_original "${_content}") + +# Insert MSVC SSE/SSE2 macro definitions after the #include block. +# MSVC x64 always has SSE2 but doesn't define these macros. +string(REPLACE + "#if defined(_MSC_VER) +#include +#elif defined(__SSE2__) || defined(__SSE__) || defined(__AVX__) +#include +#endif" + "#if defined(_MSC_VER) +#include +// MSVC x64 always supports SSE/SSE2 but does not define these macros. +// Define them so that the SSE/SSE2 code sections are compiled. +#if defined(_M_X64) || defined(_M_AMD64) +# if !defined(__SSE__) +# define __SSE__ 1 +# endif +# if !defined(__SSE2__) +# define __SSE2__ 1 +# endif +#endif +#elif defined(__SSE2__) || defined(__SSE__) || defined(__AVX__) +#include +#endif" + _content "${_content}") + +if(NOT "${_content}" STREQUAL "${_original}") + file(WRITE "${_file}" "${_content}") + message(STATUS " Patched: ${_file} (added MSVC SSE/SSE2 macro definitions)") +else() + message(STATUS " Already patched or pattern not found: ${_file}") +endif() + +message(STATUS "libint2 MSVC SSE macro patching complete.") + +# Patch array_adaptor.h: ext_stack_allocator is missing the template rebind +# constructor required by MSVC Debug CRT (_ITERATOR_DEBUG_LEVEL=2). +# The vector destructor rebinds allocator to allocator<_ContainerProxy,N>, +# which requires a converting constructor that the class lacks (C2440 error). +set(_alloc_file "libint-2.9.0/include/libint2/util/array_adaptor.h") +if(NOT EXISTS "${_alloc_file}") + message(WARNING "libint2-msvc-sse-macros: ${_alloc_file} not found, skipping") + return() +endif() + +file(READ "${_alloc_file}" _alloc_content) +set(_alloc_original "${_alloc_content}") + +string(REPLACE + " template + struct rebind {" + " // MSVC Debug CRT rebinds allocator to allocator<_ContainerProxy> for iterator tracking. + template + ext_stack_allocator(const ext_stack_allocator&) noexcept + : stack_(nullptr), free_(nullptr) {} + + template + struct rebind {" + _alloc_content "${_alloc_content}") + +# Also fix allocate() and pointer_on_stack() to gracefully handle stack_==nullptr +# (which happens when the allocator was constructed via the rebind constructor above). +string(REPLACE + " T* allocate(std::size_t n) { + assert(stack_ != nullptr && \"array_view_allocator not initialized\"); + if (stack_ + N - free_ >=" + " T* allocate(std::size_t n) { + if (stack_ != nullptr && stack_ + N - free_ >=" + _alloc_content "${_alloc_content}") + +string(REPLACE + " bool pointer_on_stack(T* ptr) const { + return stack_ <= ptr && ptr < stack_ + N;" + " bool pointer_on_stack(T* ptr) const { + return stack_ != nullptr && stack_ <= ptr && ptr < stack_ + N;" + _alloc_content "${_alloc_content}") + +if(NOT "${_alloc_content}" STREQUAL "${_alloc_original}") + file(WRITE "${_alloc_file}" "${_alloc_content}") + message(STATUS " Patched: ${_alloc_file} (added MSVC rebind constructor to ext_stack_allocator)") +else() + message(STATUS " Already patched or pattern not found: ${_alloc_file}") +endif() + +message(STATUS "libint2 MSVC allocator patching complete.") diff --git a/cpp/cmake/qdk-uarch.cmake b/cpp/cmake/qdk-uarch.cmake index 1c2116ba8f..aee0818ac3 100644 --- a/cpp/cmake/qdk-uarch.cmake +++ b/cpp/cmake/qdk-uarch.cmake @@ -1,3 +1,4 @@ +# --- Step 1: Resolve QDK_UARCH --- # Check environment variable first, then CMake variable if(NOT DEFINED QDK_UARCH AND DEFINED ENV{QDK_UARCH}) set(QDK_UARCH $ENV{QDK_UARCH}) @@ -5,17 +6,13 @@ endif() if(DEFINED QDK_UARCH) message(STATUS "Using user-defined uarch: ${QDK_UARCH}") - # If compiler ID is not GNU or Clang, we cannot use -march flag, so we will not set QDK_UARCH_FLAGS - if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")) - message(WARNING "Compiler ${CMAKE_CXX_COMPILER_ID} does not support -march flag. QDK_UARCH_FLAGS will not be set.") - set(QDK_UARCH_USED "NONE" CACHE STRING "User-defined microarchitecture for optimization") - else() - set(QDK_UARCH_USED ${QDK_UARCH} CACHE STRING "User-defined microarchitecture for optimization") - set(QDK_UARCH_FLAGS "-march=${QDK_UARCH}" CACHE STRING "Compiler flags for user-defined microarchitecture") - endif() else() - # Set architecture-specific defaults based on the target platform - if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") + # Auto-detect based on the target platform + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + # Native MSVC cl has no generic baseline /arch: flag; leave QDK_UARCH unset. + message(STATUS "Native MSVC: use compiler default ISA.") + return() + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") set(QDK_UARCH "x86-64" CACHE STRING "Target microarchitecture") message(STATUS "Auto-detected x86_64 architecture, using: ${QDK_UARCH}") elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64") @@ -25,17 +22,21 @@ else() message(WARNING "Unknown architecture ${CMAKE_SYSTEM_PROCESSOR}. QDK_UARCH not set. This may degrade performance") return() endif() +endif() - # Set the used arch and flags - set(QDK_UARCH_USED ${QDK_UARCH} CACHE STRING "User-defined microarchitecture for optimization") - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") - set(QDK_UARCH_FLAGS "-march=${QDK_UARCH}" CACHE STRING "Compiler flags for user-defined microarchitecture") - else() - message(WARNING "Compiler syntax for ISA flags is unknown, defaulting to the system default generic ISA") - endif() +# --- Step 2: Set QDK_UARCH_FLAGS based on compiler and uarch --- +set(QDK_UARCH_USED ${QDK_UARCH} CACHE STRING "Target microarchitecture for optimization") +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") + set(QDK_UARCH_FLAGS "-march=${QDK_UARCH}" CACHE STRING "Compiler flags for target microarchitecture") +elseif(MSVC) + # Users should set QDK_UARCH to a valid MSVC /arch: argument (e.g. AVX2, AVX512) + set(QDK_UARCH_FLAGS "/arch:${QDK_UARCH}" CACHE STRING "Compiler flags for target microarchitecture") +else() + message(WARNING "Compiler ${CMAKE_CXX_COMPILER_ID}: unknown flag syntax for ISA selection. QDK_UARCH_FLAGS will not be set.") + set(QDK_UARCH_USED "NONE" CACHE STRING "Target microarchitecture for optimization") endif() -# Test that the produced flags are sane +# --- Step 3: Validate the flags --- if(QDK_UARCH_FLAGS) message(STATUS "Testing QDK_UARCH_FLAGS: ${QDK_UARCH_FLAGS}") include(CheckCXXCompilerFlag) @@ -43,6 +44,6 @@ if(QDK_UARCH_FLAGS) if(NOT COMPILER_SUPPORTS_QDK_UARCH_FLAGS) message(WARNING "The compiler does not support the specified QDK_UARCH_FLAGS: ${QDK_UARCH_FLAGS}. Unsetting these flags.") unset(QDK_UARCH_FLAGS CACHE) - set(QDK_UARCH_USED "NONE" CACHE STRING "User-defined microarchitecture for optimization") + set(QDK_UARCH_USED "NONE" CACHE STRING "Target microarchitecture for optimization") endif() endif() diff --git a/cpp/cmake/third_party.cmake b/cpp/cmake/third_party.cmake index 21e170a00c..e643799181 100644 --- a/cpp/cmake/third_party.cmake +++ b/cpp/cmake/third_party.cmake @@ -2,7 +2,10 @@ include(DependencyManager) # Extract QDK_UARCH FLAGS -set(DEPENDENCY_BUILD_FLAGS BUILD_ARGS "${QDK_UARCH_FLAGS} -fPIC") +set(DEPENDENCY_BUILD_FLAGS BUILD_ARGS "${QDK_UARCH_FLAGS}") +if(NOT MSVC) + set(DEPENDENCY_BUILD_FLAGS "${DEPENDENCY_BUILD_FLAGS} -fPIC") +endif() # Save current warning settings get_property(_old_warn_deprecated CACHE CMAKE_WARN_DEPRECATED PROPERTY VALUE) @@ -41,27 +44,69 @@ set(_libint2_source_subdir "SOURCE_SUBDIR;libint-2.9.0") if(APPLE) set(_libint2_source_subdir "") endif() +# MSVC x64 doesn't define __SSE__/__SSE2__; patch vector_x86.h to define them. +set(_libint2_patch_args "") +if(MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(_libint2_patch_args FETCHCONTENT_ARGS + PATCH_COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_LIST_DIR}/patches/libint2-msvc-sse-macros.cmake" + ) +endif() handle_dependency(libint2 URL https://github.com/evaleev/libint/releases/download/v2.9.0/libint-2.9.0-mpqc4.tgz BUILD_TARGET Libint2::cxx INSTALL_TARGET Libint2::cxx ${_libint2_source_subdir} ${DEPENDENCY_BUILD_FLAGS} + ${_libint2_patch_args} REQUIRED ) +if(MSVC AND TARGET libint2_cxx) + # libint2 needs /Zc:__cplusplus (C++11 detection) and /Zc:preprocessor (Boost.Preprocessor). + # Skip IMPORTED targets (target_compile_options rejects them). + get_target_property(_libint2_cxx_imported libint2_cxx IMPORTED) + if(NOT _libint2_cxx_imported) + # clang-cl rejects /Zc:preprocessor; omit it to avoid -Wunused-command-line-argument. + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC") + target_compile_options(libint2_cxx INTERFACE /Zc:__cplusplus) + else() + target_compile_options(libint2_cxx INTERFACE /Zc:__cplusplus /Zc:preprocessor) + endif() + endif() +endif() +# eritest-libint2 links only to libint2-static (C library), so it misses the +# INTERFACE flags from libint2_cxx but still needs C++11 detection. +if(MSVC AND TARGET eritest-libint2) + get_target_property(_eritest_imported eritest-libint2 IMPORTED) + if(NOT _eritest_imported) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC") + target_compile_options(eritest-libint2 PRIVATE /Zc:__cplusplus) + else() + target_compile_options(eritest-libint2 PRIVATE /Zc:__cplusplus /Zc:preprocessor) + endif() + endif() +endif() # ecpint for ECP-related integral evaluation set(LIBECPINT_BUILD_TESTS OFF CACHE BOOL "Enable ECPINT Tests" FORCE) set(LIBECPINT_USE_PUGIXML OFF CACHE BOOL "Use pugixml for ECPINT" FORCE) +# MSVC doesn't support the C99 VLAs ecpint uses; patch replaces them with std::vector. +set(_ecpint_patch_args "") +if(MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(_ecpint_patch_args FETCHCONTENT_ARGS + PATCH_COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_LIST_DIR}/patches/ecpint-msvc-vla.cmake" + ) +endif() handle_dependency(ecpint GIT_REPOSITORY https://github.com/robashaw/libecpint GIT_TAG v1.0.7 BUILD_TARGET ECPINT::ecpint INSTALL_TARGET ECPINT::ecpint ${DEPENDENCY_BUILD_FLAGS} + ${_ecpint_patch_args} REQUIRED ) + # gauxc for XC evaluation set(EXCHCXX_ENABLE_LIBXC OFF CACHE BOOL "Enable LibXC Support" FORCE) set(GAUXC_ENABLE_HDF5 OFF CACHE BOOL "Enable gauxc HDF5 Support" FORCE) @@ -73,7 +118,7 @@ set(GAUXC_ENABLE_OPENMP ${QDK_ENABLE_OPENMP} CACHE BOOL "Enable gauxc OpenMP Sup handle_dependency(gauxc GIT_REPOSITORY https://github.com/wavefunction91/gauxc.git - GIT_TAG 62fea07c9306dbd83dd18b6957358827ac9b3da0 + GIT_TAG f05cd68e1fd549cc45a318e6d039f49d044d3e1d BUILD_TARGET gauxc::gauxc INSTALL_TARGET gauxc::gauxc ${DEPENDENCY_BUILD_FLAGS} diff --git a/cpp/include/qdk/chemistry/algorithms/dynamical_correlation_calculator.hpp b/cpp/include/qdk/chemistry/algorithms/dynamical_correlation_calculator.hpp index c53e16889a..e694a7d0c2 100644 --- a/cpp/include/qdk/chemistry/algorithms/dynamical_correlation_calculator.hpp +++ b/cpp/include/qdk/chemistry/algorithms/dynamical_correlation_calculator.hpp @@ -76,7 +76,7 @@ class DynamicalCorrelationCalculator * * @return The algorithm's name */ - virtual std::string name() const = 0; + virtual std::string name() const override = 0; /** * @brief Access the algorithm's type name @@ -99,7 +99,7 @@ class DynamicalCorrelationCalculator * wavefunction, and optionally a bra wavefunction */ virtual DynamicalCorrelationResult _run_impl( - std::shared_ptr ansatz) const = 0; + std::shared_ptr ansatz) const override = 0; }; /** diff --git a/cpp/include/qdk/chemistry/algorithms/stability.hpp b/cpp/include/qdk/chemistry/algorithms/stability.hpp index 88b29b2939..9663409cfc 100644 --- a/cpp/include/qdk/chemistry/algorithms/stability.hpp +++ b/cpp/include/qdk/chemistry/algorithms/stability.hpp @@ -114,7 +114,7 @@ class StabilityChecker * @return A pair containing stability status and detailed results */ virtual std::pair> _run_impl( - std::shared_ptr wavefunction) const = 0; + std::shared_ptr wavefunction) const override = 0; }; /** diff --git a/cpp/include/qdk/chemistry/data/structure.hpp b/cpp/include/qdk/chemistry/data/structure.hpp index 61267f37c1..bb83b1a49e 100644 --- a/cpp/include/qdk/chemistry/data/structure.hpp +++ b/cpp/include/qdk/chemistry/data/structure.hpp @@ -117,12 +117,12 @@ class Structure : public DataClass, /** * @brief Copy assignment operator */ - Structure& operator=(const Structure& other) = default; + Structure& operator=(const Structure&) = delete; /** * @brief Move assignment operator */ - Structure& operator=(Structure&& other) noexcept = default; + Structure& operator=(Structure&&) = delete; /** * @brief Destructor diff --git a/cpp/src/qdk/chemistry/algorithms/active_space.cpp b/cpp/src/qdk/chemistry/algorithms/active_space.cpp index bb03e13d2a..03b561ff49 100644 --- a/cpp/src/qdk/chemistry/algorithms/active_space.cpp +++ b/cpp/src/qdk/chemistry/algorithms/active_space.cpp @@ -63,14 +63,14 @@ _sort_entropies_and_indices(std::shared_ptr wavefunction, "Wavefunction does not have single orbital entropies."); } const auto entropies = wavefunction->get_single_orbital_entropies(); - if (entropies.size() != active_space_indices.size()) { + if (static_cast(entropies.size()) != active_space_indices.size()) { throw std::runtime_error( "Entropy size does not match number of active space orbitals."); } // Sort entropies and orbital indices std::vector> entropy_index_pairs(entropies.size()); - for (size_t i = 0; i < entropies.size(); ++i) { + for (size_t i = 0; i < static_cast(entropies.size()); ++i) { entropy_index_pairs[i] = {entropies(i), active_space_indices[i]}; } std::sort(entropy_index_pairs.begin(), entropy_index_pairs.end(), @@ -96,7 +96,7 @@ _sort_entropies_and_indices(std::shared_ptr wavefunction, if (normalize_entropies) { QDK_LOGGER().info("Normalized orbital entropies:"); QDK_LOGGER().info(" Orbital index Normalized Entropy Raw Entropy"); - for (size_t i = 0; i < sorted_entropies.size(); ++i) { + for (size_t i = 0; i < static_cast(sorted_entropies.size()); ++i) { QDK_LOGGER().info(" {:>6} {:>12.6f} {:>10.6f}", selected_active_space_indices[i], sorted_entropies[i], sorted_entropies[i] * max_entropy); @@ -104,7 +104,7 @@ _sort_entropies_and_indices(std::shared_ptr wavefunction, } else { QDK_LOGGER().info("Orbital entropies:"); QDK_LOGGER().info(" Orbital index Raw Entropy"); - for (size_t i = 0; i < sorted_entropies.size(); ++i) { + for (size_t i = 0; i < static_cast(sorted_entropies.size()); ++i) { QDK_LOGGER().info(" {:>6} {:>12.6f}", selected_active_space_indices[i], sorted_entropies[i]); } diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/autocas_active_space.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/autocas_active_space.cpp index f43c5e2c87..d40bfcd422 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/autocas_active_space.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/autocas_active_space.cpp @@ -44,17 +44,18 @@ std::shared_ptr AutocasActiveSpaceSelector::_run_impl( } // create discrete bins + const size_t num_bins_ull = static_cast(num_bins); const double bin_width = 1.0 / num_bins; QDK_LOGGER().debug("Bin width: {:.6f}", bin_width); - std::vector bins(num_bins); - for (size_t i = 0; i < num_bins; ++i) { + std::vector bins(num_bins_ull); + for (size_t i = 0; i < num_bins_ull; ++i) { bins[i] = i * bin_width; } // Count orbitals above each bin - std::vector orbitals_above_bin(num_bins); + std::vector orbitals_above_bin(num_bins_ull); size_t entropy_idx = sorted_entropies.size(); - for (size_t i = 0; i < num_bins; ++i) { + for (size_t i = 0; i < num_bins_ull; ++i) { double bin_threshold = bins[i]; // Move backwards while entropies are <= threshold while (entropy_idx > 0 && @@ -66,7 +67,7 @@ std::shared_ptr AutocasActiveSpaceSelector::_run_impl( } QDK_LOGGER().debug("Orbitals above each bin threshold:"); QDK_LOGGER().debug(" Bin Bin Threshold #Orbitals Above"); - for (size_t i = 0; i < num_bins; ++i) { + for (size_t i = 0; i < num_bins_ull; ++i) { QDK_LOGGER().debug(" {:>5} {:>12.6f} {:>15}", i, bins[i], orbitals_above_bin[i]); } @@ -76,12 +77,13 @@ std::shared_ptr AutocasActiveSpaceSelector::_run_impl( // look for unique values in orbitals_above_bin // check that number of the values is larger equal than min_plateau_size // these form plateau + const size_t min_plateau_size_ull = static_cast(min_plateau_size); size_t current_bin = 0; for (size_t i = 1; i < orbitals_above_bin.size(); ++i) { if (orbitals_above_bin[i] != orbitals_above_bin[i - 1]) { // end of plateau const size_t plateau_size = i - current_bin; - if (plateau_size >= min_plateau_size) { + if (plateau_size >= min_plateau_size_ull) { plateaus.emplace_back(current_bin, i - 1); } current_bin = i; @@ -89,7 +91,7 @@ std::shared_ptr AutocasActiveSpaceSelector::_run_impl( } // check last plateau const size_t plateau_size = bins.size() - current_bin; - if (plateau_size >= min_plateau_size) { + if (plateau_size >= min_plateau_size_ull) { plateaus.emplace_back(current_bin, bins.size() - 1); } // Logging diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/entropy_active_space.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/entropy_active_space.cpp index e1ee0c390a..feff28f924 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/entropy_active_space.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/entropy_active_space.cpp @@ -44,7 +44,7 @@ std::shared_ptr AutocasEosActiveSpaceSelector::_run_impl( // find first index where diff is larger than threshold std::vector active_space_sizes; - for (size_t i = 0; i < diff.size(); ++i) { + for (size_t i = 0; i < static_cast(diff.size()); ++i) { if (sorted_entropies(i) > entropy_threshold && diff(i) > plateau_threshold) { active_space_sizes.push_back(i + 1); diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/valence_active_space.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/valence_active_space.cpp index ff9c661668..dbb92ed9f2 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/valence_active_space.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/active_space/valence_active_space.cpp @@ -78,7 +78,7 @@ std::shared_ptr ValenceActiveSpaceSelector::_run_impl( } auto [nalpha, nbeta] = wavefunction->get_total_num_electrons(); - if (num_active_electrons > (nalpha + nbeta)) { + if (static_cast(num_active_electrons) > (nalpha + nbeta)) { throw std::runtime_error( "Number of active electrons exceeds total number of electrons."); } diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/cholesky_hamiltonian.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/cholesky_hamiltonian.cpp index dbacecd14a..44f2e7ebf2 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/cholesky_hamiltonian.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/cholesky_hamiltonian.cpp @@ -490,7 +490,7 @@ Eigen::MatrixXd transform_cholesky_to_mo( if (n_ao == 0 || n_mo == 0) { throw std::invalid_argument("C matrix has zero dimensions"); } - if (ao_cholesky_vectors.rows() != n_ao * n_ao) { + if (static_cast(ao_cholesky_vectors.rows()) != n_ao * n_ao) { throw std::invalid_argument( "ao_cholesky_vectors dimensions do not match n_ao"); } @@ -524,7 +524,7 @@ Eigen::MatrixXd build_J_from_cholesky( if (density.cols() != density.rows()) { throw std::invalid_argument("Density matrix must be square"); } - if (ao_cholesky_vectors.rows() != n_ao * n_ao) { + if (static_cast(ao_cholesky_vectors.rows()) != n_ao * n_ao) { throw std::invalid_argument( "ao_cholesky_vectors dimensions do not match density matrix"); } @@ -555,7 +555,7 @@ Eigen::MatrixXd build_K_from_cholesky( size_t rank = ao_cholesky_vectors.cols(); // Validate dimensions - if (ao_cholesky_vectors.rows() != n_ao * n_ao) { + if (static_cast(ao_cholesky_vectors.rows()) != n_ao * n_ao) { throw std::invalid_argument( "ao_cholesky_vectors dimensions do not match density matrix"); } diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/localization/pipek_mezey.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/localization/pipek_mezey.cpp index 16a84bd067..3b492e1ca7 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/localization/pipek_mezey.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/localization/pipek_mezey.cpp @@ -208,7 +208,7 @@ Eigen::MatrixXd PipekMezeyLocalization::localize( const auto tol = this->settings_.get("tolerance"); const auto AB_tol = this->settings_.get("small_rotation_tolerance"); size_t i_sweep; - for (i_sweep = 0; i_sweep < max_sweeps; ++i_sweep) { + for (i_sweep = 0; i_sweep < static_cast(max_sweeps); ++i_sweep) { // Compute Xi Xi.setZero(); for (auto p = 0; p < num_orbitals; ++p) diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/localization/vvhv.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/localization/vvhv.cpp index a4eab05fbe..9c147e9cd2 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/localization/vvhv.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/localization/vvhv.cpp @@ -64,10 +64,11 @@ class VVHVLocalization : public IterativeOrbitalLocalizationScheme { std::shared_ptr inner_localizer) : IterativeOrbitalLocalizationScheme(settings), basis_set_(basis_set), - overlap_ori_(ao_overlap), minimal_basis_name_(minimal_basis_name), - basis_ori_fp_(utils::microsoft::convert_basis_set_from_qdk(*basis_set)), - inner_localizer_(inner_localizer) { + inner_localizer_(inner_localizer), + overlap_ori_(ao_overlap), + basis_ori_fp_( + utils::microsoft::convert_basis_set_from_qdk(*basis_set)) { QDK_LOG_TRACE_ENTERING(); // Initialize all data structures and pre-compute integrals @@ -799,7 +800,7 @@ Eigen::MatrixXd VVHVLocalization::localize_hard_virtuals( // First collect all atomic orbitals on atom A for both basis sets std::vector bf_list_ori; std::vector bf_list_min; - for (auto l = 0; l <= max_l_ori; ++l) { + for (auto l = 0; l <= static_cast(max_l_ori); ++l) { auto& bf_l_ori = al_to_bf_ori[atom_a][l]; auto& bf_l_min = al_to_bf_min[atom_a][l]; bf_list_ori.insert(bf_list_ori.end(), bf_l_ori.begin(), bf_l_ori.end()); @@ -837,7 +838,7 @@ Eigen::MatrixXd VVHVLocalization::localize_hard_virtuals( Eigen::MatrixXd::Zero(num_atomic_orbitals_ori, nhv_a); int proto_hv_idx = 0; - for (auto l = 0; l <= max_l_ori; ++l) { + for (auto l = 0; l <= static_cast(max_l_ori); ++l) { auto& bf_al_ori = al_to_bf_ori[atom_a][l]; if (bf_al_ori.size() == 0) continue; // no atomic orbitals with this angular momentum on this atom diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/macis_base.hpp b/cpp/src/qdk/chemistry/algorithms/microsoft/macis_base.hpp index 1c57dbb073..cfdac79768 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/macis_base.hpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/macis_base.hpp @@ -333,7 +333,7 @@ class Macis : public qdk::chemistry::algorithms::MultiConfigurationCalculator { */ virtual ~Macis() noexcept override = default; - virtual std::string name() const = 0; + virtual std::string name() const override = 0; protected: /** diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/scf.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/scf.cpp index 952c570fce..464b0bf53c 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/scf.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/scf.cpp @@ -93,6 +93,8 @@ std::pair> ScfSolver::_run_impl( } qdk_raw_basis_set = data::BasisSet::from_basis_name(basis_set_name, structure); + } else { + throw std::logic_error("Unhandled basis_or_guess alternative."); } // Extract geometry from structure object diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/include/qdk/chemistry/scf/eri/eri_multiplexer.h b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/include/qdk/chemistry/scf/eri/eri_multiplexer.h index 0366e9d251..3b9be9c7da 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/include/qdk/chemistry/scf/eri/eri_multiplexer.h +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/include/qdk/chemistry/scf/eri/eri_multiplexer.h @@ -64,9 +64,9 @@ class ERIMultiplexer : public ERI { const SCFConfig& cfg, double omega); /** - * @brief Default constructor (private, used by factory methods) + * @brief Default constructor (deleted — base class ERI has no default ctor) */ - ERIMultiplexer() noexcept = default; + ERIMultiplexer() noexcept = delete; public: /** diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/core/basis_set.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/core/basis_set.cpp index 0f7c2b3f1b..4b27efa3fa 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/core/basis_set.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/core/basis_set.cpp @@ -132,8 +132,8 @@ BasisSet::BasisSet(std::shared_ptr mol, int n_ecp_electrons, BasisMode mode, bool pure, bool sort) : mol(mol), mode(mode), - pure(pure), shells(input_shells), + pure(pure), ecp_shells(input_ecp_shells), element_ecp_electrons(element_ecp_electrons), n_ecp_electrons(n_ecp_electrons) { @@ -188,7 +188,7 @@ BasisSet::BasisSet(std::shared_ptr mol, BasisSet::BasisSet(std::shared_ptr mol, const std::vector& input_shells, BasisMode mode, bool pure, bool sort) - : mol(mol), mode(mode), pure(pure), shells(input_shells) { + : mol(mol), mode(mode), shells(input_shells), pure(pure) { #ifdef QDK_CHEMISTRY_ENABLE_MPI if (mpi::get_world_size() > 1) { MPI_Barrier(MPI_COMM_WORLD); @@ -245,7 +245,7 @@ BasisSet::BasisSet(std::shared_ptr mol, const std::string& path, "basis" / (normalized_path + ".json"); name = normalized_path; } else { - name = bs_path.stem(); + name = bs_path.stem().string(); } if (!std::filesystem::exists(bs_path)) { auto compressed_path = QDKChemistryConfig::get_resources_dir() / @@ -256,9 +256,23 @@ BasisSet::BasisSet(std::shared_ptr mol, const std::string& path, if (!std::filesystem::exists(odir)) { std::filesystem::create_directories(odir); } + // On Windows, GNU tar (Git for Windows/MSYS) interprets paths with a + // colon as remote host:path and fails with "Cannot connect to C:". The + // --force-local flag disables that parsing. BSD tar (the System32 one) + // does not need or recognize it. Detect at runtime which tar is + // available. +#ifdef _WIN32 + static const bool tar_has_force_local = + (std::system("tar --force-local --version > nul 2>&1") == 0); auto cmd = - fmt::format("tar xzf \"{}\" --directory \"{}\"", + fmt::format("{} -xzf \"{}\" --directory \"{}\"", + tar_has_force_local ? "tar --force-local" : "tar", compressed_path.generic_string(), odir.generic_string()); +#else + auto cmd = + fmt::format("tar -xzf \"{}\" --directory \"{}\"", + compressed_path.generic_string(), odir.generic_string()); +#endif QDK_LOGGER().trace("Execute command: {}", cmd); int return_code = std::system(cmd.c_str()); if (return_code != 0) { @@ -425,7 +439,7 @@ Shell Shell::from_json(const nlohmann::ordered_json& rec, const std::shared_ptr mol) { QDK_LOG_TRACE_ENTERING(); - Shell sh; + Shell sh{}; sh.atom_index = rec["atom"].template get(); sh.angular_momentum = rec["am"].template get(); diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/exc/GauXC/gauxc_impl.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/exc/GauXC/gauxc_impl.cpp index e16f978b6f..2a91666036 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/exc/GauXC/gauxc_impl.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/exc/GauXC/gauxc_impl.cpp @@ -96,8 +96,8 @@ GauXC::BasisSet to_gauxc_basisset(const BasisSet& aimd_basisset) { for (size_t ishell = 0; ishell != nshell; ++ishell) { auto aimd_shell = aimd_basisset.shells[ishell]; - prim_array exponents; - prim_array coefficients; + prim_array exponents{}; + prim_array coefficients{}; for (size_t iprim = 0; iprim != aimd_shell.contraction; ++iprim) { exponents.at(iprim) = aimd_shell.exponents[iprim]; @@ -249,8 +249,6 @@ GAUXC::GAUXC(BasisSet& basis_set, const GAUXCInput& gauxc_input, void GAUXC::eval_dd_psi(int lmax, const double* D, double* dd_psi) { QDK_LOG_TRACE_ENTERING(); auto num_atomic_orbitals = integrator_->load_balancer().basis().nbf(); - auto natom = integrator_->load_balancer().molecule().size(); - auto nharmonics = (lmax + 1) * (lmax + 1); Eigen::MatrixXd D_eigen = Eigen::Map( D, num_atomic_orbitals, num_atomic_orbitals); auto dd_psi_vec = integrator_->eval_dd_psi(D_eigen, lmax); diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/scf_algorithm/line_search.h b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/scf_algorithm/line_search.h index 1824b96e70..c3bd239faa 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/scf_algorithm/line_search.h +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/scf_algorithm/line_search.h @@ -68,7 +68,8 @@ void nocedal_wright_line_search(Functor& op, const auto armijo_test_val = c1 * dgi; const auto wolfe_test_curv = -c2 * dgi; - ret_type step_hi, step_lo = 0, fx_hi, fx_lo = fx0, dg_hi, dg_lo = dgi; + ret_type step_hi = 0, step_lo = 0, fx_hi = fx0, fx_lo = fx0, dg_hi = dgi, + dg_lo = dgi; int iter = 0; const size_t max_iter = 100; diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/scf_algorithm/scf_algorithm.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/scf_algorithm/scf_algorithm.cpp index 48c620e792..f1f4250ff7 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/scf_algorithm/scf_algorithm.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/scf_algorithm/scf_algorithm.cpp @@ -36,8 +36,8 @@ SCFAlgorithm::SCFAlgorithm(const SCFContext& ctx) : ctx_(ctx), step_count_(0), last_energy_(0.0), - density_rms_(std::numeric_limits::infinity()), - delta_energy_(std::numeric_limits::infinity()) { + delta_energy_(std::numeric_limits::infinity()), + density_rms_(std::numeric_limits::infinity()) { QDK_LOG_TRACE_ENTERING(); auto num_atomic_orbitals = ctx.basis_set->num_atomic_orbitals; auto num_density_matrices = diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/util/int1e.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/util/int1e.cpp index 5e58736ba1..cd1b238dcc 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/util/int1e.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/src/util/int1e.cpp @@ -91,7 +91,7 @@ class Libint2Engine : public OneBodyIntegralEngine { * @brief Get number of operator components * @return Number of matrices/operators this engine computes */ - size_t nopers() const { + size_t nopers() const override { QDK_LOG_TRACE_ENTERING(); return engine_.results().size(); } @@ -179,7 +179,7 @@ class ECPIntEngine : public OneBodyIntegralEngine { * @return Number of matrices this engine computes (1 for integrals, 3*natom * for gradients) */ - size_t nopers() const { + size_t nopers() const override { QDK_LOG_TRACE_ENTERING(); return buf_.size(); } diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/tests/scf_test.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/tests/scf_test.cpp index e8d20b79f9..84558265cf 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/tests/scf_test.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/tests/scf_test.cpp @@ -8,18 +8,18 @@ TEST_P(SCFTest, CheckEnergyGradients) { Run(); } // clang-format off /* ==================== H2O, RHF/RKS, spherical ==================== */ -INSTANTIATE_TEST_CASE_P(RKS_INCORE_h2o, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RKS_INCORE_h2o, SCFTest, ::testing::Values( std::make_tuple("h2o_gauxc", "RHF/HF/6-31g*/pure", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "RKS/M06-2X/6-31g*/pure", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "RKS/wB97x/def2-svp/pure", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5) ) ); -INSTANTIATE_TEST_CASE_P(RKS_CPU_h2o, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RKS_CPU_h2o, SCFTest, ::testing::Values( std::make_tuple("h2o_gauxc", "RHF/HF/6-31g*/pure", "CPU", "CPU", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "RKS/M06-2X/6-31g*/pure", "CPU", "CPU", "GAUXC", 1e-6, 1e-5))); //std::make_tuple("h2o_gauxc", "RKS/wB97x/def2-svp/pure", "CPU", "CPU", "GAUXC", 1e-6, 1e-5))); -INSTANTIATE_TEST_CASE_P(RKS_DFJ_h2o, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RKS_DFJ_h2o, SCFTest, ::testing::Values( std::make_tuple("h2o_gauxc", "RHF-DFJ/def2-svp/def2-universal-jfit/pure", "INCORE", "DEFAULT", "GAUXC", 1e-8, 1e-8), std::make_tuple("h2o_gauxc", "RKS-DFJ/PBE/def2-svp/def2-universal-jfit/pure", "INCORE", "DEFAULT", "GAUXC", 1e-8, 1e-8), std::make_tuple("h2o_gauxc", "RKS-DFJ/M06-2X/def2-svp/def2-universal-jfit/pure", "INCORE", "DEFAULT", "GAUXC", 1e-8, 1e-8) @@ -27,76 +27,76 @@ INSTANTIATE_TEST_CASE_P(RKS_DFJ_h2o, SCFTest, ::testing::Values( /* ==================== H2O, UHF/UKS, spherical ==================== */ -INSTANTIATE_TEST_CASE_P(UKS_INCORE_h2o, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(UKS_INCORE_h2o, SCFTest, ::testing::Values( std::make_tuple("h2o_gauxc", "UHF/HF/6-31g*/pure", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "UKS/M06-2X/6-31g*/pure", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "UKS/wB97x/def2-svp/pure", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5))); -INSTANTIATE_TEST_CASE_P(UKS_CPU_h2o, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(UKS_CPU_h2o, SCFTest, ::testing::Values( std::make_tuple("h2o_gauxc", "UHF/HF/6-31g*/pure", "CPU", "CPU", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "UKS/M06-2X/6-31g*/pure", "CPU", "CPU", "GAUXC", 1e-6, 1e-5))); //std::make_tuple("h2o_gauxc", "UKS/wB97x/def2-svp/pure", "CPU", "CPU", "GAUXC", 1e-6, 1e-5))); /* ==================== H2O, RHF/RKS, cartesian ==================== */ -INSTANTIATE_TEST_CASE_P(RKS_INCORE_h2o_cart, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RKS_INCORE_h2o_cart, SCFTest, ::testing::Values( std::make_tuple("h2o_gauxc", "RHF/HF/6-31g*/cart", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "RKS/M06-2X/6-31g*/cart", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "RKS/wB97x/def2-svp/cart", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5))); -INSTANTIATE_TEST_CASE_P(RKS_CPU_h2o_cart, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RKS_CPU_h2o_cart, SCFTest, ::testing::Values( std::make_tuple("h2o_gauxc", "RHF/HF/6-31g*/cart", "CPU", "CPU", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "RKS/M06-2X/6-31g*/cart", "CPU", "CPU", "GAUXC", 1e-6, 1e-5))); //std::make_tuple("h2o_gauxc", "RKS/wB97x/def2-svp/cart", "CPU", "CPU", "GAUXC", 1e-6, 1e-5))); /* ==================== H2O, UHF/UKS, cartesian ==================== */ -INSTANTIATE_TEST_CASE_P(UKS_INCORE_h2o_cart, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(UKS_INCORE_h2o_cart, SCFTest, ::testing::Values( std::make_tuple("h2o_gauxc", "UHF/HF/6-31g*/cart", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "UKS/M06-2X/6-31g*/cart", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "UKS/wB97x/def2-svp/cart", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5))); -INSTANTIATE_TEST_CASE_P(UKS_CPU_h2o_cart, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(UKS_CPU_h2o_cart, SCFTest, ::testing::Values( std::make_tuple("h2o_gauxc", "UHF/HF/6-31g*/cart", "CPU", "CPU", "GAUXC", 1e-6, 1e-5), std::make_tuple("h2o_gauxc", "UKS/M06-2X/6-31g*/cart", "CPU", "CPU", "GAUXC", 1e-6, 1e-5))); //std::make_tuple("h2o_gauxc", "UKS/wB97x/def2-svp/cart", "CPU", "CPU", "GAUXC", 1e-6, 1e-5))); /* ==================== O2 UHF-DFJ, pure =========================*/ -INSTANTIATE_TEST_CASE_P(UHF_DFJ_o2, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(UHF_DFJ_o2, SCFTest, ::testing::Values( std::make_tuple("o2", "UHF-DFJ/HF/def2-svp/def2-universal-jfit/pure", "INCORE", "DEFAULT", "GAUXC", 1e-8, 1e-8) )); -INSTANTIATE_TEST_CASE_P(UKS_DFJ_bf, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(UKS_DFJ_bf, SCFTest, ::testing::Values( std::make_tuple("bf", "UKS-DFJ/PBE/sto-3g/def2-universal-jfit/pure", "INCORE", "DEFAULT", "GAUXC", 1e-8, 1e-8) )); #ifdef QDK_CHEMISTRY_RUN_LONG_TESTS /* ==================== Water10, RHF/RKS, spherical ==================== */ -INSTANTIATE_TEST_CASE_P(RKS_INCORE_water10, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RKS_INCORE_water10, SCFTest, ::testing::Values( std::make_tuple("water10_gauxc", "RHF/HF/6-31g*/pure", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5), std::make_tuple("water10_gauxc", "RKS/M06-2X/6-31g*/pure", "INCORE", "INCORE", "GAUXC", 2e-6, 2e-5) // std::make_tuple("water10_gauxc", "RKS/wB97x/def2-svp/pure", "INCORE", "INCORE", "GAUXC", 1e-6, 2e-5) // Too memory intensive for GPU w/ GAUXC requirements )); -INSTANTIATE_TEST_CASE_P(RKS_CPU_water10, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RKS_CPU_water10, SCFTest, ::testing::Values( std::make_tuple("water10_gauxc", "RHF/HF/6-31g*/pure", "CPU", "CPU", "GAUXC", 1e-6, 1e-5), std::make_tuple("water10_gauxc", "RKS/M06-2X/6-31g*/pure", "CPU", "CPU", "GAUXC", 2e-6, 2e-5))); //std::make_tuple("water10_gauxc", "RKS/wB97x/def2-svp/pure", "CPU", "CPU", "GAUXC", 1e-6, 2e-5))); /* ==================== Water10, UHF/UKS, spherical ==================== */ -INSTANTIATE_TEST_CASE_P(UKS_INCORE_water10, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(UKS_INCORE_water10, SCFTest, ::testing::Values( std::make_tuple("water10_gauxc", "UHF/HF/6-31g*/pure", "INCORE", "INCORE", "GAUXC", 1e-6, 2e-4), std::make_tuple("water10_gauxc", "UKS/M06-2X/6-31g*/pure", "INCORE", "INCORE", "GAUXC", 2e-5, 1e-4) // std::make_tuple("water10_gauxc", "UKS/wB97x/def2-svp/pure", "INCORE", "INCORE", "GAUXC", 2e-5, 5e-4) // Too memory intensive for GPUs w/ GAUXC )); -INSTANTIATE_TEST_CASE_P(UKS_CPU_water10, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(UKS_CPU_water10, SCFTest, ::testing::Values( std::make_tuple("water10_gauxc", "UHF/HF/6-31g*/pure", "CPU", "CPU", "GAUXC", 1e-6, 2e-4), std::make_tuple("water10_gauxc", "UKS/M06-2X/6-31g*/pure", "CPU", "CPU", "GAUXC", 2e-5, 1e-4))); //std::make_tuple("water10_gauxc", "UKS/wB97x/def2-svp/pure", "CPU", "CPU", "GAUXC", 2e-5, 5e-4))); /* ==================== Water10, RHF/RKS, cartesian ==================== */ -INSTANTIATE_TEST_CASE_P(RKS_INCORE_water10_cart, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RKS_INCORE_water10_cart, SCFTest, ::testing::Values( std::make_tuple("water10_gauxc", "RHF/HF/6-31g*/cart", "INCORE", "INCORE", "GAUXC", 1e-6, 1e-5), std::make_tuple("water10_gauxc", "RKS/M06-2X/6-31g*/cart", "INCORE", "INCORE", "GAUXC", 2e-6, 2e-5) // std::make_tuple("water10_gauxc", "RKS/wB97x/def2-svp/cart", "INCORE", "INCORE", "GAUXC", 1e-6, 2e-5) // Too memory intensive got GPU w/ GAUXC requirements )); /* ==================== Water10, UHF/UKS, cartesian ==================== */ -INSTANTIATE_TEST_CASE_P(UKS_INCORE_water10_cart, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(UKS_INCORE_water10_cart, SCFTest, ::testing::Values( std::make_tuple("water10_gauxc", "UHF/HF/6-31g*/cart", "INCORE", "INCORE", "GAUXC", 1e-6, 5e-4), std::make_tuple("water10_gauxc", "UKS/M06-2X/6-31g*/cart", "INCORE", "INCORE", "GAUXC", 2e-6, 2e-4) // std::make_tuple("water10_gauxc", "UKS/wB97x/def2-svp/cart", "INCORE", "INCORE", "GAUXC", 2e-5, 5e-4) // Too memory intensive for GPU w/ GAUXC @@ -104,19 +104,19 @@ INSTANTIATE_TEST_CASE_P(UKS_INCORE_water10_cart, SCFTest, ::testing::Values( /* ==================== Water10, RHF, LinearDep =========================== */ -INSTANTIATE_TEST_CASE_P(RHF_LINEAR_DEP, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RHF_LINEAR_DEP, SCFTest, ::testing::Values( std::make_tuple("water10", "RHF/HF/6-311++g**/pure/lindep", "CPU", "CPU", "GAUXC", 1e-6, 5e-4))); #endif // QDK_CHEMISTRY_RUN_LONG_TESTS /* ==================== Read Guess, c2h4 RKS, pure ===================== */ -INSTANTIATE_TEST_CASE_P(RKS_HGP_Rd_c2h4, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RKS_HGP_Rd_c2h4, SCFTest, ::testing::Values( std::make_tuple("read_guess", "RKS/B3LYP/def2-svp/Rd", "CPU", "CPU", "GAUXC", 1e-6, 1e-5))); /* ==================== Read Guess, O UKS, pure ======================== */ -INSTANTIATE_TEST_CASE_P(UKS_CPU_Rd_o, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(UKS_CPU_Rd_o, SCFTest, ::testing::Values( std::make_tuple("read_guess", "UKS/B3LYP/def2-svp/Rd", "CPU", "CPU", "GAUXC", 1e-6, 1e-5))); /* ==================== GAUXC Tests ===================== */ -INSTANTIATE_TEST_CASE_P(RKS_CPU_GAUXC_h2o, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RKS_CPU_GAUXC_h2o, SCFTest, ::testing::Values( std::make_tuple("h2o_gauxc", "RKS/M06-2X/6-31g*/cart", "CPU", "CPU", "GAUXC", 1e-8, 1e-8), std::make_tuple("h2o_gauxc", "RKS/M06-2X/6-31g*/pure", "CPU", "CPU", "GAUXC", 1e-8, 1e-8), std::make_tuple("h2o_gauxc", "UKS/M06-2X/6-31g*/pure", "CPU", "CPU", "GAUXC", 1e-8, 1e-8), @@ -130,7 +130,7 @@ INSTANTIATE_TEST_CASE_P(RKS_CPU_GAUXC_h2o, SCFTest, ::testing::Values( )); // Level shifting Tests -INSTANTIATE_TEST_CASE_P(RKS_LEVEL_SHIFTING, SCFTest, ::testing::Values( +INSTANTIATE_TEST_SUITE_P(RKS_LEVEL_SHIFTING, SCFTest, ::testing::Values( std::make_tuple("benzene", "RKS/PBE/cc-pvdz", "HGP", "HGP", "GAUXC", 1e-8, 1e0) )); diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/tests/util_tests.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/tests/util_tests.cpp index d8c2b8c478..614cdf9b78 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/scf/tests/util_tests.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/scf/tests/util_tests.cpp @@ -472,11 +472,11 @@ TEST(ClassRegistryTest, PrimitiveKey) { TEST(AtomGuessTest, BasisSetMap) { // Test that the map correctly identifies equivalent basis sets auto mol = std::make_shared(); - mol->atomic_nums = {1}; + mol->atomic_nums = {3}; mol->n_atoms = 1; - mol->atomic_charges = {1}; - mol->total_nuclear_charge = 1; - mol->n_electrons = 1; + mol->atomic_charges = {3}; + mol->total_nuclear_charge = 3; + mol->n_electrons = 3; mol->coords = {{0.0, 0.0, 0.0}}; // Create two identical basis sets @@ -485,10 +485,12 @@ TEST(AtomGuessTest, BasisSetMap) { auto basis2 = BasisSet::from_database_json(mol, "sto-3g", BasisMode::PSI4, true, false); - // Create same basis set in different shell order + // Create same basis set with reversed shell order via JSON round-trip. + // Lithium STO-3G has 3 shells, so reversing produces a different ordering + // that the BasisEqChecker should reject. auto basis_json = basis1->to_json(); - // Reverse the shells - std::reverse(basis_json["shells"].begin(), basis_json["shells"].end()); + std::reverse(basis_json["electron_shells"].begin(), + basis_json["electron_shells"].end()); auto basis3 = BasisSet::from_serialized_json(mol, basis_json); // Create a different basis set (different basis name) @@ -518,7 +520,7 @@ TEST(AtomGuessTest, BasisSetMap) { EXPECT_NE(it2, basis_map.end()); EXPECT_TRUE(it2->second.isApprox(RowMajorMatrix::Identity( basis2->num_atomic_orbitals, basis2->num_atomic_orbitals))); - // Retrieve using basis3 (should not be found) + // Retrieve using basis3 (should not be found — shells are reversed) auto it3 = basis_map.find(*basis3); EXPECT_EQ(it3, basis_map.end()); // Retrieve using basis4 (should not be found) diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/stability.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/stability.cpp index 0de48b41d1..00d6de6e43 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/stability.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/stability.cpp @@ -56,7 +56,8 @@ void compute_trial_fock(const std::shared_ptr eri, RowMajorMatrix& trial_fock, bool rhf_external) { QDK_LOG_TRACE_ENTERING(); const size_t num_atomic_orbitals = ground_density.cols(); - const bool unrestricted = (ground_density.rows() == 2 * num_atomic_orbitals); + const bool unrestricted = + (static_cast(ground_density.rows()) == 2 * num_atomic_orbitals); // Get hybrid coefficients (0,0,0 for HF) double alpha = 1.0, beta = 0.0, omega = 0.0; @@ -181,8 +182,8 @@ class StabilityOperator { const size_t num_alpha_virtual_orbitals = num_molecular_orbitals - num_alpha_; const size_t num_beta_virtual_orbitals = num_molecular_orbitals - num_beta_; - const bool unrestricted = - (ground_density_.rows() == 2 * num_atomic_orbitals); + const bool unrestricted = (static_cast(ground_density_.rows()) == + 2 * num_atomic_orbitals); const size_t nova = num_alpha_ * num_alpha_virtual_orbitals; const size_t eigensize = diff --git a/cpp/src/qdk/chemistry/algorithms/microsoft/utils.cpp b/cpp/src/qdk/chemistry/algorithms/microsoft/utils.cpp index c5b571bca2..b62628ee2c 100644 --- a/cpp/src/qdk/chemistry/algorithms/microsoft/utils.cpp +++ b/cpp/src/qdk/chemistry/algorithms/microsoft/utils.cpp @@ -394,13 +394,14 @@ std::vector compute_shell_map( const auto& itrn_shells = itrn_basis_set.shells; for (size_t i = 0; i < nshells; ++i) { const auto& qdk_shell = qdk_shells[i]; - const auto nprim = qdk_shell.exponents.size(); + const auto nprim = static_cast(qdk_shell.exponents.size()); const auto l = static_cast(qdk_shell.orbital_type); for (size_t j = 0; j < nshells; ++j) { const auto& itrn_shell = itrn_shells[j]; - if (qdk_shell.atom_index != itrn_shell.atom_index) continue; - if (l != itrn_shell.angular_momentum) continue; - if (nprim != itrn_shell.contraction) continue; + if (qdk_shell.atom_index != static_cast(itrn_shell.atom_index)) + continue; + if (l != static_cast(itrn_shell.angular_momentum)) continue; + if (nprim != static_cast(itrn_shell.contraction)) continue; bool exp_equiv = true; for (size_t k = 0; k < nprim; ++k) { exp_equiv &= std::abs(qdk_shell.exponents[k] - diff --git a/cpp/src/qdk/chemistry/data/basis_set.cpp b/cpp/src/qdk/chemistry/data/basis_set.cpp index 5ef4dbd8ed..d8ca6a79f3 100644 --- a/cpp/src/qdk/chemistry/data/basis_set.cpp +++ b/cpp/src/qdk/chemistry/data/basis_set.cpp @@ -63,7 +63,17 @@ std::filesystem::path unpack_basis_set_archive(std::string& basis_set_name) { } // unpack the tar.gz file - auto cmd = "tar xzf \"" + file_path.generic_string() + "\" --directory \"" + + // On Windows, GNU tar needs --force-local so it doesn't treat "C:/..." as a + // host:path. BSD tar (System32\tar.exe) rejects the flag, detect at runtime. +#ifdef _WIN32 + static const bool tar_has_force_local = + (std::system("tar --force-local --version > nul 2>&1") == 0); + const std::string tar_cmd = + tar_has_force_local ? "tar --force-local -xzf " : "tar -xzf "; +#else + const std::string tar_cmd = "tar -xzf "; +#endif + auto cmd = tar_cmd + "\"" + file_path.generic_string() + "\" --directory \"" + temp_dir.generic_string() + "\""; int return_code = std::system(cmd.c_str()); if (return_code != 0) { @@ -597,8 +607,8 @@ std::shared_ptr BasisSet::from_basis_name( std::vector all_ecp_electrons; // loop over each atom in the structure and get basis set shells auto nuclear_charges = structure->get_nuclear_charges(); - for (size_t atom_index = 0; atom_index < nuclear_charges.size(); - ++atom_index) { + for (size_t atom_index = 0; + atom_index < static_cast(nuclear_charges.size()); ++atom_index) { double nuclear_charge = nuclear_charges[atom_index]; auto [shells, ecp_shells, ecp_electrons] = @@ -678,8 +688,8 @@ std::shared_ptr BasisSet::from_index_map( std::vector all_ecp_electrons; // loop over each atom in the structure and get basis set shells auto nuclear_charges = structure->get_nuclear_charges(); - for (size_t atom_index = 0; atom_index < nuclear_charges.size(); - ++atom_index) { + for (size_t atom_index = 0; + atom_index < static_cast(nuclear_charges.size()); ++atom_index) { double nuclear_charge = nuclear_charges[atom_index]; auto it = index_to_basis_map.find(atom_index); if (it == index_to_basis_map.end()) { @@ -720,8 +730,8 @@ BasisSet::BasisSet(const BasisSet& other) : _name(other._name), _atomic_orbital_type(other._atomic_orbital_type), _shells_per_atom(other._shells_per_atom), - _ecp_name(other._ecp_name), _ecp_shells_per_atom(other._ecp_shells_per_atom), + _ecp_name(other._ecp_name), _ecp_electrons(other._ecp_electrons), _ao_symmetries(other._ao_symmetries), _ao_extents(other._ao_extents) { diff --git a/cpp/src/qdk/chemistry/data/hdf5_serialization.cpp b/cpp/src/qdk/chemistry/data/hdf5_serialization.cpp index 4a77ea0d27..749978212c 100644 --- a/cpp/src/qdk/chemistry/data/hdf5_serialization.cpp +++ b/cpp/src/qdk/chemistry/data/hdf5_serialization.cpp @@ -187,9 +187,11 @@ void save_vector_to_group(H5::Group& group, const std::string& dataset_name, if (!vector.empty()) { hsize_t dims[1] = {vector.size()}; H5::DataSpace dataspace(1, dims); + // Windows LLP64: unsigned long is 4 bytes, so use NATIVE_UINT64 for size_t. + static_assert(sizeof(size_t) == 8); H5::DataSet dataset = group.createDataSet( - dataset_name, H5::PredType::NATIVE_ULONG, dataspace); - dataset.write(vector.data(), H5::PredType::NATIVE_ULONG); + dataset_name, H5::PredType::NATIVE_UINT64, dataspace); + dataset.write(vector.data(), H5::PredType::NATIVE_UINT64); } } @@ -225,7 +227,8 @@ std::vector load_size_vector_from_group( hsize_t dims[1]; dataspace.getSimpleExtentDims(dims); std::vector vector(dims[0]); - dataset.read(vector.data(), H5::PredType::NATIVE_ULONG); + static_assert(sizeof(size_t) == 8); + dataset.read(vector.data(), H5::PredType::NATIVE_UINT64); return vector; } diff --git a/cpp/src/qdk/chemistry/data/orbitals.cpp b/cpp/src/qdk/chemistry/data/orbitals.cpp index 1b1c73d334..b52409ea9e 100644 --- a/cpp/src/qdk/chemistry/data/orbitals.cpp +++ b/cpp/src/qdk/chemistry/data/orbitals.cpp @@ -460,8 +460,8 @@ Orbitals::calculate_ao_density_matrix( } const size_t num_molecular_orbitals = get_num_molecular_orbitals(); - if (occupations_alpha.size() != num_molecular_orbitals || - occupations_beta.size() != num_molecular_orbitals) { + if (static_cast(occupations_alpha.size()) != num_molecular_orbitals || + static_cast(occupations_beta.size()) != num_molecular_orbitals) { throw std::runtime_error( "Occupation vector size must match number of molecular orbitals"); } @@ -488,7 +488,7 @@ Eigen::MatrixXd Orbitals::calculate_ao_density_matrix( } const size_t num_molecular_orbitals = get_num_molecular_orbitals(); - if (occupations.size() != num_molecular_orbitals) { + if (static_cast(occupations.size()) != num_molecular_orbitals) { throw std::runtime_error( "Occupation vector size must match number of molecular orbitals"); } @@ -512,10 +512,10 @@ Orbitals::calculate_ao_density_matrix_from_rdm( } const size_t num_molecular_orbitals = get_num_molecular_orbitals(); - if (rdm_alpha.rows() != num_molecular_orbitals || - rdm_alpha.cols() != num_molecular_orbitals || - rdm_beta.rows() != num_molecular_orbitals || - rdm_beta.cols() != num_molecular_orbitals) { + if (static_cast(rdm_alpha.rows()) != num_molecular_orbitals || + static_cast(rdm_alpha.cols()) != num_molecular_orbitals || + static_cast(rdm_beta.rows()) != num_molecular_orbitals || + static_cast(rdm_beta.cols()) != num_molecular_orbitals) { throw std::runtime_error( "1RDM matrix size must match number of molecular orbitals"); } @@ -539,8 +539,8 @@ Eigen::MatrixXd Orbitals::calculate_ao_density_matrix_from_rdm( } const size_t num_molecular_orbitals = get_num_molecular_orbitals(); - if (rdm.rows() != num_molecular_orbitals || - rdm.cols() != num_molecular_orbitals) { + if (static_cast(rdm.rows()) != num_molecular_orbitals || + static_cast(rdm.cols()) != num_molecular_orbitals) { throw std::runtime_error( "1RDM matrix size must match number of molecular orbitals"); } @@ -1208,7 +1208,7 @@ void Orbitals::to_hdf5(H5::Group& group) const { // Save essential metadata that can't be computed from data unsigned num_atomic_orbitals = get_num_atomic_orbitals(); unsigned num_molecular_orbitals = get_num_molecular_orbitals(); - bool restricted = is_restricted(); + hbool_t restricted = static_cast(is_restricted()); H5::DataSet aos_dataset = metadata_group.createDataSet( "num_atomic_orbitals", H5::PredType::NATIVE_UINT, scalar_space); @@ -1327,7 +1327,11 @@ std::shared_ptr Orbitals::from_hdf5(H5::Group& group) { try { H5::Group metadata_group = group.openGroup("metadata"); H5::DataSet ds = metadata_group.openDataSet("is_restricted"); - ds.read(&restricted, H5::PredType::NATIVE_HBOOL); + // hbool_t is typically unsigned int (4 bytes) while C++ bool is 1 byte. + // Reading into a bool* with NATIVE_HBOOL is UB; stage through hbool_t. + hbool_t hb_restricted = 0; + ds.read(&hb_restricted, H5::PredType::NATIVE_HBOOL); + restricted = (hb_restricted != 0); } catch (const H5::Exception&) { throw std::invalid_argument( "HDF5 file missing 'metadata' group or 'is_restricted' dataset"); @@ -1658,17 +1662,22 @@ void Orbitals::_save_orbital_metadata_to_hdf5( mos_dataset.write(&num_molecular_orbitals, H5::PredType::NATIVE_UINT); // Save boolean flags + // Use hbool_t intermediaries — hbool_t is typically unsigned int (4 bytes), + // while C++ bool is 1 byte. Writing a bool* with NATIVE_HBOOL is UB. + hbool_t hb_is_restricted = static_cast(is_restricted); H5::DataSet restricted_dataset = metadata_group.createDataSet( "is_restricted", H5::PredType::NATIVE_HBOOL, scalar_space); - restricted_dataset.write(&is_restricted, H5::PredType::NATIVE_HBOOL); + restricted_dataset.write(&hb_is_restricted, H5::PredType::NATIVE_HBOOL); + hbool_t hb_has_overlap_matrix = static_cast(has_overlap_matrix); H5::DataSet overlap_dataset = metadata_group.createDataSet( "has_overlap_matrix", H5::PredType::NATIVE_HBOOL, scalar_space); - overlap_dataset.write(&has_overlap_matrix, H5::PredType::NATIVE_HBOOL); + overlap_dataset.write(&hb_has_overlap_matrix, H5::PredType::NATIVE_HBOOL); + hbool_t hb_has_basis_set = static_cast(has_basis_set); H5::DataSet basis_dataset = metadata_group.createDataSet( "has_basis_set", H5::PredType::NATIVE_HBOOL, scalar_space); - basis_dataset.write(&has_basis_set, H5::PredType::NATIVE_HBOOL); + basis_dataset.write(&hb_has_basis_set, H5::PredType::NATIVE_HBOOL); } bool Orbitals::is_unrestricted() const { @@ -2199,7 +2208,7 @@ void ModelOrbitals::to_hdf5(H5::Group& group) const { // Save ModelOrbitals metadata unsigned num_orbitals = _num_orbitals; - bool is_restricted = _is_restricted; + hbool_t hb_is_restricted = static_cast(_is_restricted); H5::DataSet orbitals_dataset = metadata_group.createDataSet( "num_orbitals", H5::PredType::NATIVE_UINT, scalar_space); @@ -2207,7 +2216,7 @@ void ModelOrbitals::to_hdf5(H5::Group& group) const { H5::DataSet restricted_dataset = metadata_group.createDataSet( "is_restricted", H5::PredType::NATIVE_HBOOL, scalar_space); - restricted_dataset.write(&is_restricted, H5::PredType::NATIVE_HBOOL); + restricted_dataset.write(&hb_is_restricted, H5::PredType::NATIVE_HBOOL); // Save active space indices save_vector_to_group(group, "active_space_indices_alpha", diff --git a/cpp/src/qdk/chemistry/data/wavefunction.cpp b/cpp/src/qdk/chemistry/data/wavefunction.cpp index 774164b9d1..5e8a4fbd2d 100644 --- a/cpp/src/qdk/chemistry/data/wavefunction.cpp +++ b/cpp/src/qdk/chemistry/data/wavefunction.cpp @@ -214,11 +214,11 @@ WavefunctionContainer::WavefunctionContainer( std::shared_ptr> active_one_rdm, std::shared_ptr> active_two_rdm, const OrbitalEntropies& entropies, WavefunctionType type) - : _one_rdm_spin_traced(std::move(one_rdm_spin_traced)), + : _type(type), + _one_rdm_spin_traced(std::move(one_rdm_spin_traced)), _two_rdm_spin_traced(std::move(two_rdm_spin_traced)), _active_one_rdm(std::move(active_one_rdm)), _active_two_rdm(std::move(active_two_rdm)), - _type(type), _entropies(entropies) { QDK_LOG_TRACE_ENTERING(); } diff --git a/cpp/src/qdk/chemistry/data/wavefunction_containers/state_vector.cpp b/cpp/src/qdk/chemistry/data/wavefunction_containers/state_vector.cpp index 14bab35682..55c9c2ba70 100644 --- a/cpp/src/qdk/chemistry/data/wavefunction_containers/state_vector.cpp +++ b/cpp/src/qdk/chemistry/data/wavefunction_containers/state_vector.cpp @@ -472,10 +472,10 @@ const SymmetryBlockedTensorVariant<2>& StateVectorContainer::active_one_rdm() Eigen::MatrixXd tmp_one_rdm_aa = Eigen::MatrixXd::Zero(n_orbs, n_orbs); Eigen::MatrixXd tmp_one_rdm_bb = Eigen::MatrixXd::Zero(n_orbs, n_orbs); - for (size_t i = 0; i < alpha_occupations.size(); ++i) { + for (size_t i = 0; i < static_cast(alpha_occupations.size()); ++i) { if (alpha_occupations(i) > 0.0) tmp_one_rdm_aa(i, i) = 1.0; } - for (size_t i = 0; i < beta_occupations.size(); ++i) { + for (size_t i = 0; i < static_cast(beta_occupations.size()); ++i) { if (beta_occupations(i) > 0.0) tmp_one_rdm_bb(i, i) = 1.0; } @@ -581,10 +581,10 @@ const MatrixVariant& StateVectorContainer::get_active_one_rdm_spin_traced() auto [alpha_occupations, beta_occupations] = _active_occupations_pair(); size_t n_orbs = get_orbitals()->get_active_space_indices().first.size(); Eigen::MatrixXd tmp_one_rdm = Eigen::MatrixXd::Zero(n_orbs, n_orbs); - for (size_t i = 0; i < alpha_occupations.size(); ++i) { + for (size_t i = 0; i < static_cast(alpha_occupations.size()); ++i) { if (alpha_occupations(i) > 0.0) tmp_one_rdm(i, i) += 1.0; } - for (size_t i = 0; i < beta_occupations.size(); ++i) { + for (size_t i = 0; i < static_cast(beta_occupations.size()); ++i) { if (beta_occupations(i) > 0.0) tmp_one_rdm(i, i) += 1.0; } _one_rdm_spin_traced = @@ -843,16 +843,18 @@ StateVectorContainer::_total_occupations_pair() const { auto [alpha_active_indices, beta_active_indices] = get_orbitals()->get_active_space_indices(); - for (size_t active_idx = 0; active_idx < alpha_active_indices.size() && - active_idx < alpha_active_occs.size(); + for (size_t active_idx = 0; + active_idx < alpha_active_indices.size() && + active_idx < static_cast(alpha_active_occs.size()); ++active_idx) { size_t orbital_idx = alpha_active_indices[active_idx]; if (orbital_idx < static_cast(num_orbitals)) { alpha_occupations(orbital_idx) = alpha_active_occs(active_idx); } } - for (size_t active_idx = 0; active_idx < beta_active_indices.size() && - active_idx < beta_active_occs.size(); + for (size_t active_idx = 0; + active_idx < beta_active_indices.size() && + active_idx < static_cast(beta_active_occs.size()); ++active_idx) { size_t orbital_idx = beta_active_indices[active_idx]; if (orbital_idx < static_cast(num_orbitals)) { diff --git a/cpp/src/qdk/chemistry/utils/logger.cpp b/cpp/src/qdk/chemistry/utils/logger.cpp index ec87e79157..163ed8abb4 100644 --- a/cpp/src/qdk/chemistry/utils/logger.cpp +++ b/cpp/src/qdk/chemistry/utils/logger.cpp @@ -4,6 +4,11 @@ #include +#ifdef _WIN32 +#include +#endif + +#include #include #include #include @@ -15,6 +20,21 @@ namespace qdk::chemistry::utils { +#ifdef _WIN32 +// spdlog's Windows stdout sink caches a HANDLE at construction, so pytest's +// fd-1 dup2() redirection bypasses it. Write via fwrite(stdout) instead, which +// respects fd redirection (this is what spdlog already does on Linux). +class stdout_fd_sink final : public spdlog::sinks::base_sink { + protected: + void sink_it_(const spdlog::details::log_msg& msg) override { + spdlog::memory_buf_t formatted; + formatter_->format(msg, formatted); + std::fwrite(formatted.data(), 1, formatted.size(), stdout); + } + void flush_() override { std::fflush(stdout); } +}; +#endif + // Track our own global level to avoid spdlog::get_level() issues // Map compile-time level to spdlog level static constexpr spdlog::level::level_enum default_level_from_config() { @@ -197,7 +217,13 @@ static void apply_spdlog_global_level_and_flush_policy( static void init_global_logger() { try { +#ifdef _WIN32 + auto sink = std::make_shared(); + g_logger = std::make_shared("qdk-chemistry", sink); + spdlog::register_logger(g_logger); +#else g_logger = spdlog::stdout_color_mt("qdk-chemistry"); +#endif } catch (const spdlog::spdlog_ex&) { g_logger = spdlog::get("qdk-chemistry"); } diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index d4d2f1b59d..ab4735dcdc 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -74,12 +74,17 @@ function(add_gtest_executable test_source) if(QDK_CHEMISTRY_ENABLE_COVERAGE) # Handle coverage of inline functions - target_compile_options(${test_name} PRIVATE --coverage -fprofile-arcs -ftest-coverage) - target_link_libraries(${test_name} PRIVATE --coverage) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang" AND NOT MSVC) + target_compile_options(${test_name} PRIVATE --coverage -fprofile-arcs -ftest-coverage) + target_link_libraries(${test_name} PRIVATE --coverage) + endif() endif() - # Discover tests automatically - gtest_discover_tests(${test_name}) + # Discover tests automatically. The default 5s timeout for test + # discovery is unreliable on Windows when many test exes link and + # discover in parallel — disk/IO contention causes spurious timeouts. + # Bump to 60s. + gtest_discover_tests(${test_name} DISCOVERY_TIMEOUT 60) # Set properties for better organization set_target_properties(${test_name} PROPERTIES diff --git a/cpp/tests/symmetry/test_symmetry_blocked_tensor.cpp b/cpp/tests/symmetry/test_symmetry_blocked_tensor.cpp index 822753dfee..b2a0b8ef0e 100644 --- a/cpp/tests/symmetry/test_symmetry_blocked_tensor.cpp +++ b/cpp/tests/symmetry/test_symmetry_blocked_tensor.cpp @@ -208,20 +208,26 @@ TEST(SymmetryBlockedTensorTest, Hdf5RoundTripStoresNativeDoubleBlocks) { EXPECT_EQ(restored->block_ptr(aa()).get(), restored->block_ptr(bb()).get()); EXPECT_TRUE(restored->block(aa()).isApprox(data)); - H5::H5File file(filename.string(), H5F_ACC_RDONLY); - auto metadata = file.openDataSet("symmetry_blocked_tensor_metadata"); - EXPECT_EQ(metadata.getTypeClass(), H5T_STRING); - - auto block_dataset = file.openDataSet("block_0"); - EXPECT_EQ(block_dataset.getTypeClass(), H5T_FLOAT); - EXPECT_EQ(block_dataset.getDataType().getSize(), sizeof(double)); - - auto dataspace = block_dataset.getSpace(); - EXPECT_EQ(dataspace.getSimpleExtentNdims(), 2); - hsize_t dims[2] = {0, 0}; - dataspace.getSimpleExtentDims(dims); - EXPECT_EQ(dims[0], 2u); - EXPECT_EQ(dims[1], 2u); + // On Windows, the file stays locked until every HDF5 handle to it is + // destroyed (DataSet, DataSpace, etc. all keep the underlying H5File alive + // via reference counting). Scope them in a block so std::filesystem::remove + // below can succeed. + { + H5::H5File file(filename.string(), H5F_ACC_RDONLY); + auto metadata = file.openDataSet("symmetry_blocked_tensor_metadata"); + EXPECT_EQ(metadata.getTypeClass(), H5T_STRING); + + auto block_dataset = file.openDataSet("block_0"); + EXPECT_EQ(block_dataset.getTypeClass(), H5T_FLOAT); + EXPECT_EQ(block_dataset.getDataType().getSize(), sizeof(double)); + + auto dataspace = block_dataset.getSpace(); + EXPECT_EQ(dataspace.getSimpleExtentNdims(), 2); + hsize_t dims[2] = {0, 0}; + dataspace.getSimpleExtentDims(dims); + EXPECT_EQ(dims[0], 2u); + EXPECT_EQ(dims[1], 2u); + } std::filesystem::remove(filename); } @@ -250,11 +256,14 @@ TEST(SymmetryBlockedTensorTest, EXPECT_TRUE(restored->block(SBT1c::Labels{SymmetryLabel({axes::alpha()})}) .isApprox(v)); - H5::H5File file(filename.string(), H5F_ACC_RDONLY); - auto block_dataset = file.openDataSet("block_0"); - EXPECT_EQ(block_dataset.getTypeClass(), H5T_COMPOUND); - EXPECT_FALSE(file.nameExists("block_0_real")); - EXPECT_FALSE(file.nameExists("block_0_imag")); + // Scope HDF5 handles so Windows releases the file lock before remove. + { + H5::H5File file(filename.string(), H5F_ACC_RDONLY); + auto block_dataset = file.openDataSet("block_0"); + EXPECT_EQ(block_dataset.getTypeClass(), H5T_COMPOUND); + EXPECT_FALSE(file.nameExists("block_0_real")); + EXPECT_FALSE(file.nameExists("block_0_imag")); + } std::filesystem::remove(filename); } @@ -285,15 +294,18 @@ TEST(SymmetryBlockedTensorTest, Hdf5MetadataCarriesSerializationVersion) { make_simple_tensor().to_hdf5_file(filename.string()); - H5::H5File file(filename.string(), H5F_ACC_RDONLY); - auto dataset = file.openDataSet("symmetry_blocked_tensor_metadata"); - H5::StrType str_type(H5::PredType::C_S1, H5T_VARIABLE); - std::string metadata_str; - dataset.read(metadata_str, str_type); - auto metadata = nlohmann::json::parse(metadata_str); - ASSERT_TRUE(metadata.contains("version")); - EXPECT_TRUE(metadata["version"].is_string()); - EXPECT_FALSE(metadata["version"].get().empty()); + // Scope HDF5 handles so Windows releases the file lock before remove. + { + H5::H5File file(filename.string(), H5F_ACC_RDONLY); + auto dataset = file.openDataSet("symmetry_blocked_tensor_metadata"); + H5::StrType str_type(H5::PredType::C_S1, H5T_VARIABLE); + std::string metadata_str; + dataset.read(metadata_str, str_type); + auto metadata = nlohmann::json::parse(metadata_str); + ASSERT_TRUE(metadata.contains("version")); + EXPECT_TRUE(metadata["version"].is_string()); + EXPECT_FALSE(metadata["version"].get().empty()); + } std::filesystem::remove(filename); } diff --git a/cpp/tests/test_element_data.cpp b/cpp/tests/test_element_data.cpp index 0019cd5597..7e00017d41 100644 --- a/cpp/tests/test_element_data.cpp +++ b/cpp/tests/test_element_data.cpp @@ -74,7 +74,7 @@ TEST_F(ElementDataTest, ChargeToSymbol) { // Test that all elements from 1-118 have symbols TEST_F(ElementDataTest, AllElementsHaveSymbols) { for (unsigned z = 1; z <= 118; ++z) { - EXPECT_NO_THROW(CHARGE_TO_SYMBOL.at(z)); + EXPECT_NO_THROW((void)CHARGE_TO_SYMBOL.at(z)); EXPECT_FALSE(CHARGE_TO_SYMBOL.at(z).empty()); } } diff --git a/cpp/tests/test_hamiltonian.cpp b/cpp/tests/test_hamiltonian.cpp index 906bd4af4b..2171df57f8 100644 --- a/cpp/tests/test_hamiltonian.cpp +++ b/cpp/tests/test_hamiltonian.cpp @@ -636,49 +636,53 @@ TEST_F(HamiltonianConstructorTest, Default_EdgeCases) { }, std::runtime_error); - // Test that unrestricted orbitals throw when alpha is empty - EXPECT_THROW( - ({ - Eigen::MatrixXd coeffs_alpha = Eigen::MatrixXd::Identity(3, 3); - Eigen::MatrixXd coeffs_beta = Eigen::MatrixXd::Identity(3, 3); - std::vector alpha_active_indices{}; // Empty alpha - std::vector beta_active_indices{0, 1}; - std::vector alpha_inactive_indices{}; - std::vector beta_inactive_indices{2}; - // Create unrestricted orbitals with only beta active space - auto orbitals = std::make_shared( - coeffs_alpha, coeffs_beta, std::nullopt, std::nullopt, std::nullopt, - basis_set, - testing::unrestricted_index_set( - coeffs_alpha.cols(), alpha_active_indices, beta_active_indices), - testing::unrestricted_index_set(coeffs_alpha.cols(), - alpha_inactive_indices, - beta_inactive_indices)); - hc->run(orbitals); - }), - std::runtime_error); + // Test that unrestricted orbitals throw when alpha is empty. + // GCC statement expressions ({...}) are not supported by MSVC, so we use + // a named lambda invoked inside the macro to avoid unprotected commas. + { + auto throw_empty_alpha = [&]() { + Eigen::MatrixXd coeffs_alpha = Eigen::MatrixXd::Identity(3, 3); + Eigen::MatrixXd coeffs_beta = Eigen::MatrixXd::Identity(3, 3); + std::vector alpha_active_indices{}; // Empty alpha + std::vector beta_active_indices{0, 1}; + std::vector alpha_inactive_indices{}; + std::vector beta_inactive_indices{2}; + // Create unrestricted orbitals with only beta active space + auto orbitals = std::make_shared( + coeffs_alpha, coeffs_beta, std::nullopt, std::nullopt, std::nullopt, + basis_set, + testing::unrestricted_index_set( + coeffs_alpha.cols(), alpha_active_indices, beta_active_indices), + testing::unrestricted_index_set(coeffs_alpha.cols(), + alpha_inactive_indices, + beta_inactive_indices)); + hc->run(orbitals); + }; + EXPECT_THROW(throw_empty_alpha(), std::runtime_error); + } // Test that unrestricted orbitals throw when beta is empty - EXPECT_THROW( - ({ - Eigen::MatrixXd coeffs_alpha = Eigen::MatrixXd::Identity(3, 3); - Eigen::MatrixXd coeffs_beta = Eigen::MatrixXd::Identity(3, 3); - std::vector alpha_active_indices{0, 1}; - std::vector beta_active_indices{}; // Empty beta - std::vector alpha_inactive_indices{2}; - std::vector beta_inactive_indices{}; - // Create unrestricted orbitals with only alpha active space - auto orbitals = std::make_shared( - coeffs_alpha, coeffs_beta, std::nullopt, std::nullopt, std::nullopt, - basis_set, - testing::unrestricted_index_set( - coeffs_alpha.cols(), alpha_active_indices, beta_active_indices), - testing::unrestricted_index_set(coeffs_alpha.cols(), - alpha_inactive_indices, - beta_inactive_indices)); - hc->run(orbitals); - }), - std::runtime_error); + { + auto throw_empty_beta = [&]() { + Eigen::MatrixXd coeffs_alpha = Eigen::MatrixXd::Identity(3, 3); + Eigen::MatrixXd coeffs_beta = Eigen::MatrixXd::Identity(3, 3); + std::vector alpha_active_indices{0, 1}; + std::vector beta_active_indices{}; // Empty beta + std::vector alpha_inactive_indices{2}; + std::vector beta_inactive_indices{}; + // Create unrestricted orbitals with only alpha active space + auto orbitals = std::make_shared( + coeffs_alpha, coeffs_beta, std::nullopt, std::nullopt, std::nullopt, + basis_set, + testing::unrestricted_index_set( + coeffs_alpha.cols(), alpha_active_indices, beta_active_indices), + testing::unrestricted_index_set(coeffs_alpha.cols(), + alpha_inactive_indices, + beta_inactive_indices)); + hc->run(orbitals); + }; + EXPECT_THROW(throw_empty_beta(), std::runtime_error); + } // Throw if the active space is larger than the MO set EXPECT_THROW( @@ -1416,29 +1420,32 @@ TEST_F(HamiltonianTest, SparseContainerFCIDUMP) { std::string filename = "test.sparse.hamiltonian.fcidump"; EXPECT_NO_THROW(h.to_fcidump_file(filename, 1, 1)); - std::ifstream file(filename); - EXPECT_TRUE(file.is_open()); - - std::stringstream buffer; - buffer << file.rdbuf(); - std::string fcidump_content = buffer.str(); - - // Two-body integrals from sparse map (sorted by key: (0,0,0,0) then - // (1,1,1,1)), one-body lower triangle in column-major order, then core - // energy. - const std::string reference_fcidump_contents = - "&FCI NORB=2, NELEC=2, MS2=0,\n" - "ORBSYM=1,1,\n" - "ISYM=1,\n" - "&END\n" - " 2.0000000000000000e+00 1 1 1 1\n" - " 3.0000000000000000e+00 2 2 2 2\n" - " 1.0000000000000000e+00 1 1 0 0\n" - " 5.0000000000000000e-01 2 1 0 0\n" - " 1.0000000000000000e+00 2 2 0 0\n" - " 1.5000000000000000e+00 0 0 0 0\n"; - - EXPECT_EQ(fcidump_content, reference_fcidump_contents); + // Scope the stream so it closes before remove() (Windows file lock). + { + std::ifstream file(filename); + EXPECT_TRUE(file.is_open()); + + std::stringstream buffer; + buffer << file.rdbuf(); + std::string fcidump_content = buffer.str(); + + // Two-body integrals from sparse map (sorted by key: (0,0,0,0) then + // (1,1,1,1)), one-body lower triangle in column-major order, then core + // energy. + const std::string reference_fcidump_contents = + "&FCI NORB=2, NELEC=2, MS2=0,\n" + "ORBSYM=1,1,\n" + "ISYM=1,\n" + "&END\n" + " 2.0000000000000000e+00 1 1 1 1\n" + " 3.0000000000000000e+00 2 2 2 2\n" + " 1.0000000000000000e+00 1 1 0 0\n" + " 5.0000000000000000e-01 2 1 0 0\n" + " 1.0000000000000000e+00 2 2 0 0\n" + " 1.5000000000000000e+00 0 0 0 0\n"; + + EXPECT_EQ(fcidump_content, reference_fcidump_contents); + } std::filesystem::remove(filename); } @@ -1787,13 +1794,15 @@ TEST_F(HamiltonianTest, FCIDUMPActiveSpaceConsistency) { 1); }); - // Verify file was created and has correct NORB (should be 2, not 3) - std::ifstream file("test_active_space.hamiltonian.fcidump"); - EXPECT_TRUE(file.is_open()); + // Verify file was created and has correct NORB (should be 2, not 3). + { + std::ifstream file("test_active_space.hamiltonian.fcidump"); + EXPECT_TRUE(file.is_open()); - std::string first_line; - std::getline(file, first_line); - EXPECT_TRUE(first_line.find("NORB=2") != std::string::npos); + std::string first_line; + std::getline(file, first_line); + EXPECT_TRUE(first_line.find("NORB=2") != std::string::npos); + } // Clean up std::filesystem::remove("test_active_space.hamiltonian.fcidump"); diff --git a/cpp/tests/test_mp2.cpp b/cpp/tests/test_mp2.cpp index 1d63286868..4d99b18aa2 100644 --- a/cpp/tests/test_mp2.cpp +++ b/cpp/tests/test_mp2.cpp @@ -129,7 +129,7 @@ TEST_F(MP2Test, UMP2Energies_CCPVDZ) { TEST_F(MP2Test, RMP2Energies_CCPVDZ) { // Test the RMP2 energies against PySCF reference for singlet O2 with // cc-pvdz - float pyscf_rmp2_corr_cc_pvdz = -0.38428662586339435; + double pyscf_rmp2_corr_cc_pvdz = -0.38428662586339435; // Singlet O2 (restricted) auto setup = create_o2_hf_setup(1); @@ -165,7 +165,7 @@ TEST_F(MP2Test, RMP2Energies_CCPVDZ) { TEST_F(MP2Test, ActiveRMP2Energies_CCPVDZ) { // Test the RMP2 energies against Psi4 reference for singlet O2 with // cc-pvdz - float psi4_act_rmp2_corr_cc_pvdz = -0.0779663051614509; + double psi4_act_rmp2_corr_cc_pvdz = -0.0779663051614509; // Singlet O2 (restricted) auto setup = create_o2_hf_setup(1, "cc-pvdz", 2.3, std::make_pair(12, 8)); diff --git a/examples/qpe_stretched_n2.ipynb b/examples/qpe_stretched_n2.ipynb index 9a11dd5f55..b3ea994201 100644 --- a/examples/qpe_stretched_n2.ipynb +++ b/examples/qpe_stretched_n2.ipynb @@ -14,7 +14,8 @@ "```bash\n", "pip install 'qdk-chemistry[jupyter,qiskit-extras,qre]'\n", "```\n", - "This installs the additional dependencies required by this notebook (ipykernel, pandas, pyscf, qiskit, qre)." + "\n", + "This installs the additional dependencies required by this notebook (ipykernel, pandas, qiskit, qre)." ] }, { diff --git a/examples/state_prep_energy.ipynb b/examples/state_prep_energy.ipynb index bcaf3bfd89..5a2ea1e787 100644 --- a/examples/state_prep_energy.ipynb +++ b/examples/state_prep_energy.ipynb @@ -16,12 +16,12 @@ "pip install 'qdk-chemistry[jupyter,qiskit-extras]'\n", "```\n", "\n", - "This installs the additional dependencies required by this notebook (ipykernel, pandas, pyscf, and qiskit).\n", + "This installs the additional dependencies required by this notebook (ipykernel, pandas, and qiskit).\n", "\n", "---\n", "\n", - "In many molecular systems\u2014such as bond dissociation or transition-metal complexes\u2014a single electronic configuration cannot describe the true electronic structure.\n", - "These multi-configurational systems exhibit strong electron correlation that challenges mean-field and single-determinant methods like [Hartree\u2013Fock](https://en.wikipedia.org/wiki/Hartree%E2%80%93Fock_method) or standard [coupled cluster theory](https://en.wikipedia.org/wiki/Coupled_cluster).\n", + "In many molecular systems—such as bond dissociation or transition-metal complexes—a single electronic configuration cannot describe the true electronic structure.\n", + "These multi-configurational systems exhibit strong electron correlation that challenges mean-field and single-determinant methods like [Hartree–Fock](https://en.wikipedia.org/wiki/Hartree%E2%80%93Fock_method) or standard [coupled cluster theory](https://en.wikipedia.org/wiki/Coupled_cluster).\n", "\n", "While classical multi-configurational approaches can capture these effects, their computational cost grows exponentially with system size.\n", "Quantum computers offer a complementary route: they can represent superpositions of many configurations natively and solve these problems with polynomial scaling.\n", @@ -258,7 +258,7 @@ "id": "c73ad541", "metadata": {}, "source": [ - "Reducing the wavefunction to these determinants allows us to optimize the computational requirements for loading the quantum computer with a state that has high overlap with the true wavefunction\u2014an important metric for quantum algorithms like QPE.\n", + "Reducing the wavefunction to these determinants allows us to optimize the computational requirements for loading the quantum computer with a state that has high overlap with the true wavefunction—an important metric for quantum algorithms like QPE.\n", "However, this reduction of the wavefunction also changes our description of the quantum system, particularly its energy.\n", "Therefore, for the purposes of benchmarking, we need to recalculate the energy of the truncated wavefunction classically to provide a reference for evaluating the accuracy of the quantum calculation.\n", "This cell shows how to recalculate this energy." @@ -290,7 +290,7 @@ "\n", "One possibility for loading the multi-configuration wavefunction onto a quantum computer is to use general state preparation approaches such as the [isometry method](https://arxiv.org/abs/1501.06911), as offered in software such as [Qiskit](https://qiskit.org/documentation/stubs/qiskit.circuit.library.Isometry.html).\n", "While this is a very powerful general-purpose approach, it can be resource intensive, requiring very deep circuits even for modest-sized wavefunctions due to its exponential scaling in the number of qubits.\n", - "This approach also requires numerous fine rotations\u2014operations that can be challenging for near-term fault-tolerant quantum hardware.\n", + "This approach also requires numerous fine rotations—operations that can be challenging for near-term fault-tolerant quantum hardware.\n", "This cell demonstrates how to use the isometry method to generate a quantum circuit for preparing the multi-configuration wavefunction on a quantum computer.\n", "\n", "**Note**: the generated circuits are so deep that you will need to adjust the \"zoom\" selection in the visualization window to see the detailed operations." @@ -325,7 +325,7 @@ "source": [ "### Loading the wavefunction using optimized state preparation methods\n", "\n", - "As the cell above illustrates, the general isometry method for state preparation can be very resource intensive\u2014requiring thousands of fine rotations for this benzene diradical example.\n", + "As the cell above illustrates, the general isometry method for state preparation can be very resource intensive—requiring thousands of fine rotations for this benzene diradical example.\n", "However, we can optimize this process by taking advantage of the sparse multi-configuration wavefunction structure, generating much more efficient quantum circuits for state preparation.\n", "The cell below demonstrates how the `qdk-chemistry` library can be used for optimized wavefunction loading, producing a circuit that is orders of magnitude more efficient than the general isometry method.\n", "\n", @@ -360,7 +360,7 @@ "id": "6635d626", "metadata": {}, "source": [ - "Rather than requiring thousands of fine rotations, this optimized approach requires only a single fine rotation for the two-determinant benzene diradical wavefunction\u2014demonstrating the power of chemistry-informed optimizations for quantum state preparation.\n", + "Rather than requiring thousands of fine rotations, this optimized approach requires only a single fine rotation for the two-determinant benzene diradical wavefunction—demonstrating the power of chemistry-informed optimizations for quantum state preparation.\n", "\n", "Close inspection of the generated circuit shows that it has also reduced our qubit count: several of the qubits have been converted to classical bits, which can be post-processed after measurement.\n", "We will revisit these classical bits in the next section on energy measurement." @@ -437,7 +437,7 @@ "energy_mean = energy_results.energy_expectation_value + hamiltonian.get_core_energy()\n", "energy_stddev = np.sqrt(energy_results.energy_variance)\n", "print(\n", - " f\"Estimated energy from quantum circuit: {energy_mean:.3f} \u00b1 {energy_stddev:.3f} Hartree\"\n", + " f\"Estimated energy from quantum circuit: {energy_mean:.3f} ± {energy_stddev:.3f} Hartree\"\n", ")\n", "\n", "# Print comparison with reference energy\n", diff --git a/external/macis/CMakeLists.txt b/external/macis/CMakeLists.txt index 173df3748a..e34a3e7469 100644 --- a/external/macis/CMakeLists.txt +++ b/external/macis/CMakeLists.txt @@ -55,7 +55,8 @@ if(MACIS_ENABLE_PYTHON) endif() # Tests +option( MACIS_ENABLE_TESTS "Build MACIS Tests" ON ) include(CTest) -if(BUILD_TESTING) +if(BUILD_TESTING AND MACIS_ENABLE_TESTS) add_subdirectory(tests) endif() diff --git a/external/macis/cmake/macis-ips4o.cmake b/external/macis/cmake/macis-ips4o.cmake index 136156270a..84681c4b3d 100644 --- a/external/macis/cmake/macis-ips4o.cmake +++ b/external/macis/cmake/macis-ips4o.cmake @@ -13,6 +13,6 @@ FetchContent_Declare( ips4o FetchContent_MakeAvailable( ips4o ) add_library( ips4o INTERFACE ) target_include_directories( ips4o INTERFACE ${ips4o_SOURCE_DIR} ) -if(NOT APPLE) +if(NOT WIN32 AND NOT APPLE) target_link_libraries( ips4o INTERFACE atomic ) endif() diff --git a/external/macis/cmake/macis-spdlog.cmake b/external/macis/cmake/macis-spdlog.cmake index 2358c3a0b0..a3df6c4837 100644 --- a/external/macis/cmake/macis-spdlog.cmake +++ b/external/macis/cmake/macis-spdlog.cmake @@ -16,7 +16,9 @@ if( NOT spdlog_FOUND ) ) set(SPDLOG_INSTALL "ON" CACHE BOOL "Install SPDLOG" FORCE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + if(NOT MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + endif() FetchContent_MakeAvailable( spdlog ) set(MACIS_SPDLOG_EXPORT spdlog CACHE STRING "" FORCE) else() diff --git a/external/macis/cmake/macis-uarch.cmake b/external/macis/cmake/macis-uarch.cmake index 31c6c9b6c0..21bb855e5a 100644 --- a/external/macis/cmake/macis-uarch.cmake +++ b/external/macis/cmake/macis-uarch.cmake @@ -1,19 +1,24 @@ if(DEFINED MACIS_UARCH) message(STATUS "Using user-defined uarch: ${MACIS_UARCH}") - # If compiler ID is not GNU or Clang, we cannot use -march flag, so we will not set MACIS_UARCH_FLAGS - if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")) - message(WARNING "Compiler ${CMAKE_CXX_COMPILER_ID} does not support -march flag. MACIS_UARCH_FLAGS will not be set.") - set(MACIS_UARCH_USED "NONE" CACHE STRING "User-defined microarchitecture for optimization") - else() - set(MACIS_UARCH_USED ${MACIS_UARCH} CACHE STRING "User-defined microarchitecture for optimization") - set(MACIS_UARCH_FLAGS "-march=${MACIS_UARCH}" CACHE STRING "Compiler flags for user-defined microarchitecture") - endif() else() message(WARNING "MACIS_UARCH not defined. This may degrade performance") set(MACIS_UARCH_USED "NONE" CACHE STRING "User-defined microarchitecture for optimization") + return() +endif() + +# Set MACIS_UARCH_FLAGS based on compiler and uarch +set(MACIS_UARCH_USED ${MACIS_UARCH} CACHE STRING "Target microarchitecture for optimization") +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") + set(MACIS_UARCH_FLAGS "-march=${MACIS_UARCH}" CACHE STRING "Compiler flags for target microarchitecture") +elseif(MSVC) + # Users should set MACIS_UARCH to a valid MSVC /arch: argument (e.g. AVX2, AVX512) + set(MACIS_UARCH_FLAGS "/arch:${MACIS_UARCH}" CACHE STRING "Compiler flags for target microarchitecture") +else() + message(WARNING "Compiler ${CMAKE_CXX_COMPILER_ID}: unknown flag syntax for ISA selection. MACIS_UARCH_FLAGS will not be set.") + set(MACIS_UARCH_USED "NONE" CACHE STRING "Target microarchitecture for optimization") endif() -# Test that the produced flags are sane +# Validate the flags if(MACIS_UARCH_FLAGS) message(STATUS "Testing MACIS_UARCH_FLAGS: ${MACIS_UARCH_FLAGS}") include(CheckCXXCompilerFlag) @@ -21,6 +26,6 @@ if(MACIS_UARCH_FLAGS) if(NOT COMPILER_SUPPORTS_MACIS_UARCH_FLAGS) message(WARNING "The compiler does not support the specified MACIS_UARCH_FLAGS: ${MACIS_UARCH_FLAGS}. Unsetting these flags.") unset(MACIS_UARCH_FLAGS CACHE) - set(MACIS_UARCH_USED "NONE" CACHE STRING "User-defined microarchitecture for optimization") + set(MACIS_UARCH_USED "NONE" CACHE STRING "Target microarchitecture for optimization") endif() endif() diff --git a/external/macis/include/macis/asci/determinant_search.hpp b/external/macis/include/macis/asci/determinant_search.hpp index c9d2a14aec..6fd7be6ab3 100644 --- a/external/macis/include/macis/asci/determinant_search.hpp +++ b/external/macis/include/macis/asci/determinant_search.hpp @@ -452,7 +452,7 @@ asci_contrib_container> asci_contributions_constraint( // Precompute cumulative alpha offsets for direct indexing std::vector uniq_alpha_ioff(nuniq_alpha); std::transform_exclusive_scan( - uniq_alpha.begin(), uniq_alpha.end(), uniq_alpha_ioff.begin(), 0ul, + uniq_alpha.begin(), uniq_alpha.end(), uniq_alpha_ioff.begin(), size_t{0}, std::plus(), [](const auto& p) { return p.second; }); const auto num_alpha_occupied_orbitals = @@ -1019,7 +1019,7 @@ std::vector> asci_search( // Determine local counts size_t n_greater = std::distance(g_begin, e_begin); size_t n_equal = std::distance(e_begin, l_begin); - size_t n_less = std::distance(l_begin, _end); + [[maybe_unused]] size_t n_less = std::distance(l_begin, _end); const int n_geq_local = n_greater + n_equal; // Strip bitsrings @@ -1068,7 +1068,7 @@ std::vector> asci_search( [=](const auto& p, const auto& s) { return std::abs(p.rv()) == s; }); size_t n_greater = std::distance(g_begin, e_begin); size_t n_equal = std::distance(e_begin, l_begin); - size_t n_less = std::distance(l_begin, _end); + [[maybe_unused]] size_t n_less = std::distance(l_begin, _end); const int n_geq = n_greater + n_equal; top_k_elements = n_geq; topk.resize(n_geq); diff --git a/external/macis/include/macis/asci/determinant_sort.hpp b/external/macis/include/macis/asci/determinant_sort.hpp index 53a850cf4a..b1d1106494 100644 --- a/external/macis/include/macis/asci/determinant_sort.hpp +++ b/external/macis/include/macis/asci/determinant_sort.hpp @@ -54,7 +54,7 @@ void reorder_ci_on_coeff(std::vector& dets, std::vector& C) { std::vector reorder_C(nlocal); std::vector reorder_dets(ndets); assert(nlocal == ndets); - for (auto i = 0ul; i < ndets; ++i) { + for (size_t i = 0; i < ndets; ++i) { reorder_C[i] = C[idx[i]]; reorder_dets[i] = dets[idx[i]]; } @@ -91,7 +91,7 @@ void reorder_ci_on_alpha(WfnIterator begin, WfnIterator end, double* C) { std::vector reorder_C(ndets); std::vector reorder_dets(ndets); - for (auto i = 0ul; i < ndets; ++i) { + for (size_t i = 0; i < ndets; ++i) { reorder_C[i] = C[idx[i]]; reorder_dets[i] = *(begin + idx[i]); } diff --git a/external/macis/include/macis/asci/mask_constraints.hpp b/external/macis/include/macis/asci/mask_constraints.hpp index 6abc5e8a74..61e90ecc21 100644 --- a/external/macis/include/macis/asci/mask_constraints.hpp +++ b/external/macis/include/macis/asci/mask_constraints.hpp @@ -749,14 +749,16 @@ auto gen_constraints_general(size_t nlevels, size_t norb, size_t ns_othr, // Build up higher-order constraints as base if requested if (nrec_min < 0 or - nrec_min >= constraint_sizes.size()) // nrec_min < 0 implies that you - // want all the constraints upfront + nrec_min >= + static_cast( + constraint_sizes.size())) // nrec_min < 0 implies that you + // want all the constraints upfront for (size_t ilevel = 0; ilevel < nlevel_min; ++ilevel) { decltype(constraint_sizes) cur_constraints; cur_constraints.reserve(constraint_sizes.size() * norb); for (auto [c, nw] : constraint_sizes) { const auto C_min = c.C_min(); - for (auto q_l = 0; q_l < C_min; ++q_l) { + for (int q_l = 0; q_l < static_cast(C_min); ++q_l) { // Generate masks / counts string_type cn_C = c.C(); cn_C.flip(q_l); @@ -832,10 +834,10 @@ auto gen_constraints_general(size_t nlevels, size_t norb, size_t ns_othr, } // Compute average work per worker. - size_t total_work = - std::accumulate(constraint_sizes.begin(), constraint_sizes.end(), 0ul, - [](auto s, const auto& p) { return s + p.second; }); - size_t local_average = std::max(1ul, total_work / world_size); + size_t total_work = std::accumulate( + constraint_sizes.begin(), constraint_sizes.end(), size_t{0}, + [](auto s, const auto& p) { return s + p.second; }); + size_t local_average = std::max(1, total_work / world_size); auto cgen_logger = spdlog::get("asci_search"); if (cgen_logger) { @@ -848,7 +850,8 @@ auto gen_constraints_general(size_t nlevels, size_t norb, size_t ns_othr, } // Manual refinement of top configurations - if (nrec_min > 0 and nrec_min < constraint_sizes.size()) { + if (nrec_min > 0 and + nrec_min < static_cast(constraint_sizes.size())) { const size_t nleave = constraint_sizes.size() - nrec_min; std::vector> constraint_to_refine, constraint_to_leave; @@ -869,7 +872,7 @@ auto gen_constraints_general(size_t nlevels, size_t norb, size_t ns_othr, cur_constraints.reserve(constraint_to_refine.size() * norb); for (auto [c, nw] : constraint_to_refine) { const auto C_min = c.C_min(); - for (auto q_l = 0; q_l < C_min; ++q_l) { + for (int q_l = 0; q_l < static_cast(C_min); ++q_l) { // Generate masks / counts string_type cn_C = c.C(); cn_C.flip(q_l); @@ -941,9 +944,9 @@ auto gen_constraints_general(size_t nlevels, size_t norb, size_t ns_othr, std::copy_n(constraint_to_leave.begin(), nleave, std::back_inserter(constraint_sizes)); - size_t tmp = - std::accumulate(constraint_sizes.begin(), constraint_sizes.end(), 0ul, - [](auto s, const auto& p) { return s + p.second; }); + size_t tmp = std::accumulate( + constraint_sizes.begin(), constraint_sizes.end(), size_t{0}, + [](auto s, const auto& p) { return s + p.second; }); if (tmp != total_work) throw std::runtime_error("Incorrect Refinement"); } // Selective refinement logic @@ -972,7 +975,7 @@ auto gen_constraints_general(size_t nlevels, size_t norb, size_t ns_othr, const auto C_min = c.C_min(); // Loop over possible constraints with one more element - for (auto q_l = 0; q_l < C_min; ++q_l) { + for (int q_l = 0; q_l < static_cast(C_min); ++q_l) { // Generate masks / counts string_type cn_C = c.C(); cn_C.flip(q_l); @@ -995,10 +998,10 @@ auto gen_constraints_general(size_t nlevels, size_t norb, size_t ns_othr, } // Recompute average with updated total_work and constraint count - total_work = - std::accumulate(constraint_sizes.begin(), constraint_sizes.end(), 0ul, - [](auto s, const auto& p) { return s + p.second; }); - local_average = std::max(1ul, total_work / world_size); + total_work = std::accumulate( + constraint_sizes.begin(), constraint_sizes.end(), size_t{0}, + [](auto s, const auto& p) { return s + p.second; }); + local_average = std::max(1, total_work / world_size); if (cgen_logger) { size_t max_w = 0; diff --git a/external/macis/include/macis/asci/pt2.hpp b/external/macis/include/macis/asci/pt2.hpp index 4ce63a3904..4234c2f86a 100644 --- a/external/macis/include/macis/asci/pt2.hpp +++ b/external/macis/include/macis/asci/pt2.hpp @@ -183,7 +183,7 @@ double asci_pt2_constraint(ASCISettings asci_settings, logger->info(" * NUNIQ_ALPHA = {}", nuniq_alpha); std::vector uniq_alpha_ioff(nuniq_alpha); std::transform_exclusive_scan( - uniq_alpha.begin(), uniq_alpha.end(), uniq_alpha_ioff.begin(), 0ul, + uniq_alpha.begin(), uniq_alpha.end(), uniq_alpha_ioff.begin(), size_t{0}, std::plus(), [](const auto& p) { return p.second; }); using unique_alpha_data = std::vector; @@ -208,9 +208,9 @@ double asci_pt2_constraint(ASCISettings asci_settings, logger->info("MEM REQ DETS = {:.2e}", ncdets * sizeof(wfn_t) / gib); logger->info("MEM REQ C = {:.2e}", ncdets * sizeof(double) / gib); size_t mem_alpha = 0; - for (auto i = 0ul; i < nuniq_alpha; ++i) { + for (size_t i = 0; i < nuniq_alpha; ++i) { mem_alpha += sizeof(spin_wfn_type); - for (auto j = 0ul; j < uad[i].size(); ++j) { + for (size_t j = 0; j < uad[i].size(); ++j) { mem_alpha += uad[i][j].mem(); } } diff --git a/external/macis/include/macis/bitset_operations.hpp b/external/macis/include/macis/bitset_operations.hpp index 77352e64f3..c5d5634612 100644 --- a/external/macis/include/macis/bitset_operations.hpp +++ b/external/macis/include/macis/bitset_operations.hpp @@ -17,6 +17,51 @@ namespace macis { +// Portable bit-manipulation helpers +#if defined(_MSC_VER) + +/** + * @brief CLZ (count leading zeros) for MSVC + */ +inline auto clz(unsigned int i) { + if (i == 0) return static_cast(sizeof(unsigned int) * CHAR_BIT); + + unsigned long idx; + [[maybe_unused]] const auto scanned = _BitScanReverse(&idx, i); + assert(scanned); + return 31 - static_cast(idx); +} +inline auto clz(unsigned long int i) { + return clz(static_cast(i)); +} +inline auto clz(unsigned long long int i) { + if (i == 0) + return static_cast(sizeof(unsigned long long int) * CHAR_BIT); + + unsigned long idx; + [[maybe_unused]] const auto scanned = _BitScanReverse64(&idx, i); + assert(scanned); + return 63 - static_cast(idx); +} + +/** + * @brief ffsl/ffsll equivalents for MSVC + * + * Returns the position of the first set bit (1-indexed), or 0 if none. + */ +inline int macis_ffsl(unsigned long i) { + unsigned long idx; + if (_BitScanForward(&idx, i)) return static_cast(idx) + 1; + return 0; +} +inline int macis_ffsll(unsigned long long i) { + unsigned long idx; + if (_BitScanForward64(&idx, i)) return static_cast(idx) + 1; + return 0; +} + +#else // GCC/Clang + /** * @brief Typesafe CLZ * @@ -47,6 +92,14 @@ inline auto clz(unsigned long int i) { return __builtin_clzl(i); } */ inline auto clz(unsigned long long int i) { return __builtin_clzll(i); } +/** + * @brief ffsl/ffsll wrappers for GCC/Clang + */ +inline int macis_ffsl(unsigned long i) { return ffsl(i); } +inline int macis_ffsll(unsigned long long i) { return ffsll(i); } + +#endif // _MSC_VER + /** * @brief Typesafe FLS * @@ -127,9 +180,18 @@ template uint128_t to_uint128(std::bitset bits) { static_assert(N <= 128, "N > 128"); if constexpr (N == 128) { +#if defined(_MSC_VER) && !defined(__clang__) + uint128_t result; + result.lo = fast_to_ullong(bits); + std::bitset<64> hi_bits; + for (size_t i = 0; i < 64; ++i) hi_bits[i] = bits[i + 64]; + result.hi = fast_to_ullong(hi_bits); + return result; +#else alignas(alignof(uint128_t)) std::bitset cpy = bits; auto _x = reinterpret_cast(&cpy); return *_x; +#endif } else { return fast_to_ullong(bits); } @@ -202,16 +264,16 @@ std::bitset full_mask(size_t i) { template uint32_t ffs(const std::bitset& bits) { if constexpr (N <= 32) - return ffsl(fast_to_ulong(bits)); + return macis_ffsl(fast_to_ulong(bits)); else if constexpr (N <= 64) - return ffsll(fast_to_ullong(bits)); + return macis_ffsll(fast_to_ullong(bits)); else if constexpr (N % 64 == 0) { // Word-aligned path: scan 64-bit words using hardware ffsll. // This replaces the old bit-by-bit loop for N<=128 and N>128. auto words = reinterpret_cast(&bits); constexpr int n_words = N / 64; for (int w = 0; w < n_words; ++w) { - if (words[w]) return __builtin_ffsll(words[w]) + w * 64; + if (words[w]) return macis_ffsll(words[w]) + w * 64; } return 0; } else { diff --git a/external/macis/include/macis/hamiltonian_generator/sorted_double_loop.hpp b/external/macis/include/macis/hamiltonian_generator/sorted_double_loop.hpp index ee3750caeb..173f85ca4f 100644 --- a/external/macis/include/macis/hamiltonian_generator/sorted_double_loop.hpp +++ b/external/macis/include/macis/hamiltonian_generator/sorted_double_loop.hpp @@ -95,13 +95,13 @@ class SortedDoubleLoopHamiltonianGenerator const bool is_symm = bra_begin == ket_begin and bra_end == ket_end; #ifdef MACIS_ENABLE_MPI - auto world_rank = comm_rank(MPI_COMM_WORLD); + [[maybe_unused]] auto world_rank = comm_rank(MPI_COMM_WORLD); #else - auto world_rank = 0; + [[maybe_unused]] auto world_rank = 0; #endif /* MACIS_ENABLE_MPI */ // Get unique alpha strings - auto setup_st = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto setup_st = std::chrono::high_resolution_clock::now(); auto unique_alpha_bra = get_unique_alpha(bra_begin, bra_end); auto unique_alpha_ket = is_symm ? unique_alpha_bra : get_unique_alpha(ket_begin, ket_end); @@ -113,7 +113,7 @@ class SortedDoubleLoopHamiltonianGenerator std::vector unique_alpha_bra_idx(nuniq_bra + 1); std::transform_exclusive_scan( unique_alpha_bra.begin(), unique_alpha_bra.end(), - unique_alpha_bra_idx.begin(), 0ul, std::plus{}, + unique_alpha_bra_idx.begin(), size_t{0}, std::plus{}, [](auto& x) { return x.second; }); std::vector unique_alpha_ket_idx(nuniq_ket + 1); if (is_symm) { @@ -121,7 +121,7 @@ class SortedDoubleLoopHamiltonianGenerator } else { std::transform_exclusive_scan( unique_alpha_ket.begin(), unique_alpha_ket.end(), - unique_alpha_ket_idx.begin(), 0ul, std::plus{}, + unique_alpha_ket_idx.begin(), size_t{0}, std::plus{}, [](auto& x) { return x.second; }); } @@ -132,7 +132,7 @@ class SortedDoubleLoopHamiltonianGenerator // 1. First pass - Count non-zero matrix elements per row // 2. Second pass - Compute and fill the CSR matrix directly - auto count_st = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto count_st = std::chrono::high_resolution_clock::now(); // First pass: Count non-zeros per row to allocate CSR structure std::vector row_nnz(nbra_dets, 0); @@ -425,17 +425,17 @@ class SortedDoubleLoopHamiltonianGenerator auto thresh_en = std::chrono::high_resolution_clock::now(); // Print timing information if needed - auto duration_setup = + [[maybe_unused]] auto duration_setup = std::chrono::duration(count_st - setup_st).count(); - auto duration_compute = + [[maybe_unused]] auto duration_compute = std::chrono::duration(count_en - count_st).count(); - auto duration_alloc = + [[maybe_unused]] auto duration_alloc = std::chrono::duration(alloc_en - alloc_st).count(); - auto duration_fill = + [[maybe_unused]] auto duration_fill = std::chrono::duration(fill_en - fill_st).count(); - auto duration_sort = + [[maybe_unused]] auto duration_sort = std::chrono::duration(sort_en - sort_st).count(); - auto duration_thresh = + [[maybe_unused]] auto duration_thresh = std::chrono::duration(thresh_en - thresh_st).count(); auto h_logger = spdlog::get("h_build"); @@ -528,7 +528,7 @@ class SortedDoubleLoopHamiltonianGenerator #endif /* MACIS_ENABLE_MPI */ // Get unique alpha strings - auto setup_st = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto setup_st = std::chrono::high_resolution_clock::now(); auto unique_alpha_bra = get_unique_alpha(bra_begin, bra_end); auto unique_alpha_ket = is_symm ? unique_alpha_bra : get_unique_alpha(ket_begin, ket_end); @@ -540,7 +540,7 @@ class SortedDoubleLoopHamiltonianGenerator std::vector unique_alpha_bra_idx(nuniq_bra + 1); std::transform_exclusive_scan( unique_alpha_bra.begin(), unique_alpha_bra.end(), - unique_alpha_bra_idx.begin(), 0ul, std::plus{}, + unique_alpha_bra_idx.begin(), size_t{0}, std::plus{}, [](auto& x) { return x.second; }); std::vector unique_alpha_ket_idx(nuniq_ket + 1); if (is_symm) { @@ -548,14 +548,14 @@ class SortedDoubleLoopHamiltonianGenerator } else { std::transform_exclusive_scan( unique_alpha_ket.begin(), unique_alpha_ket.end(), - unique_alpha_ket_idx.begin(), 0ul, std::plus{}, + unique_alpha_ket_idx.begin(), size_t{0}, std::plus{}, [](auto& x) { return x.second; }); } unique_alpha_bra_idx.back() = nbra_dets; unique_alpha_ket_idx.back() = nket_dets; - auto count_st = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto count_st = std::chrono::high_resolution_clock::now(); #pragma omp parallel { @@ -650,7 +650,7 @@ class SortedDoubleLoopHamiltonianGenerator #endif /* MACIS_ENABLE_MPI */ // Get unique alpha strings - auto setup_st = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto setup_st = std::chrono::high_resolution_clock::now(); auto unique_alpha_bra = get_unique_alpha(bra_begin, bra_end); auto unique_alpha_ket = is_symm ? unique_alpha_bra : get_unique_alpha(ket_begin, ket_end); @@ -662,7 +662,7 @@ class SortedDoubleLoopHamiltonianGenerator std::vector unique_alpha_bra_idx(nuniq_bra + 1); std::transform_exclusive_scan( unique_alpha_bra.begin(), unique_alpha_bra.end(), - unique_alpha_bra_idx.begin(), 0ul, std::plus{}, + unique_alpha_bra_idx.begin(), size_t{0}, std::plus{}, [](auto& x) { return x.second; }); std::vector unique_alpha_ket_idx(nuniq_ket + 1); if (is_symm) { @@ -670,14 +670,14 @@ class SortedDoubleLoopHamiltonianGenerator } else { std::transform_exclusive_scan( unique_alpha_ket.begin(), unique_alpha_ket.end(), - unique_alpha_ket_idx.begin(), 0ul, std::plus{}, + unique_alpha_ket_idx.begin(), size_t{0}, std::plus{}, [](auto& x) { return x.second; }); } unique_alpha_bra_idx.back() = nbra_dets; unique_alpha_ket_idx.back() = nket_dets; - auto count_st = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto count_st = std::chrono::high_resolution_clock::now(); #pragma omp parallel { @@ -783,7 +783,7 @@ class SortedDoubleLoopHamiltonianGenerator #endif /* MACIS_ENABLE_MPI */ // Get unique alpha strings - auto setup_st = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto setup_st = std::chrono::high_resolution_clock::now(); auto unique_alpha_bra = get_unique_alpha(bra_begin, bra_end); auto unique_alpha_ket = is_symm ? unique_alpha_bra : get_unique_alpha(ket_begin, ket_end); @@ -795,7 +795,7 @@ class SortedDoubleLoopHamiltonianGenerator std::vector unique_alpha_bra_idx(nuniq_bra + 1); std::transform_exclusive_scan( unique_alpha_bra.begin(), unique_alpha_bra.end(), - unique_alpha_bra_idx.begin(), 0ul, std::plus{}, + unique_alpha_bra_idx.begin(), size_t{0}, std::plus{}, [](auto& x) { return x.second; }); std::vector unique_alpha_ket_idx(nuniq_ket + 1); if (is_symm) { @@ -803,14 +803,14 @@ class SortedDoubleLoopHamiltonianGenerator } else { std::transform_exclusive_scan( unique_alpha_ket.begin(), unique_alpha_ket.end(), - unique_alpha_ket_idx.begin(), 0ul, std::plus{}, + unique_alpha_ket_idx.begin(), size_t{0}, std::plus{}, [](auto& x) { return x.second; }); } unique_alpha_bra_idx.back() = nbra_dets; unique_alpha_ket_idx.back() = nket_dets; - auto count_st = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto count_st = std::chrono::high_resolution_clock::now(); #pragma omp parallel { diff --git a/external/macis/include/macis/mcscf/cas.hpp b/external/macis/include/macis/mcscf/cas.hpp index 1c9b79eaa9..62ff1e76cd 100644 --- a/external/macis/include/macis/mcscf/cas.hpp +++ b/external/macis/include/macis/mcscf/cas.hpp @@ -40,7 +40,7 @@ double compute_casci_rdms( int rank; MPI_Comm_rank(comm, &rank); #else - int rank = 0; + [[maybe_unused]] int rank = 0; #endif /* MACIS_ENABLE_MPI */ // Hamiltonian Matrix Element Generator diff --git a/external/macis/include/macis/mcscf/mcscf_impl.hpp b/external/macis/include/macis/mcscf/mcscf_impl.hpp index 76af129d60..72f96a29f5 100644 --- a/external/macis/include/macis/mcscf/mcscf_impl.hpp +++ b/external/macis/include/macis/mcscf/mcscf_impl.hpp @@ -183,10 +183,10 @@ double mcscf_impl(const Functor& rdm_op, MCSCFSettings settings, logger->info("Computing Initial RDMs"); std::fill_n(A1RDM, na2, 0.0); std::fill_n(A2RDM, na4, 0.0); - rdm_op.rdms(settings, NumOrbital(na), nalpha.get(), nbeta.get(), - T_active.data(), V_active.data(), A1RDM, A2RDM, - X_CI MACIS_MPI_CODE(, comm)) + - E_inactive; + (void)(rdm_op.rdms(settings, NumOrbital(na), nalpha.get(), nbeta.get(), + T_active.data(), V_active.data(), A1RDM, A2RDM, + X_CI MACIS_MPI_CODE(, comm)) + + E_inactive); } else { logger->info("Using Passed RDMs"); } diff --git a/external/macis/include/macis/mcscf/orbital_hessian.hpp b/external/macis/include/macis/mcscf/orbital_hessian.hpp index 4605cc0e69..28241889ab 100644 --- a/external/macis/include/macis/mcscf/orbital_hessian.hpp +++ b/external/macis/include/macis/mcscf/orbital_hessian.hpp @@ -103,9 +103,9 @@ void approx_diag_hessian(NumOrbital norb, NumInactive ninact, NumActive nact, const double* A1RDM, size_t LDD1, const double* A2RDM, size_t LDD2, Args&&... args) { const size_t no = norb.get(); - const size_t ni = ninact.get(); + [[maybe_unused]] const size_t ni = ninact.get(); const size_t na = nact.get(); - const size_t nv = num_virtual_orbitals.get(); + [[maybe_unused]] const size_t nv = num_virtual_orbitals.get(); // Compute inactive Fock std::vector Fi(no * no); diff --git a/external/macis/include/macis/sd_operations.hpp b/external/macis/include/macis/sd_operations.hpp index 1800fc1672..4e68d7b810 100644 --- a/external/macis/include/macis/sd_operations.hpp +++ b/external/macis/include/macis/sd_operations.hpp @@ -510,7 +510,7 @@ WfnType from_canonical_string(std::string str) { using wfn_traits = wavefunction_traits; using spin_wfn_traits = wavefunction_traits; spin_wfn_type state_alpha(0), state_beta(0); - for (auto i = 0ul; i < std::min(str.length(), spin_wfn_traits::size()); ++i) { + for (size_t i = 0; i < std::min(str.length(), spin_wfn_traits::size()); ++i) { if (str[i] == '2') { state_alpha = spin_wfn_traits::create_no_check(state_alpha, i); state_beta = spin_wfn_traits::create_no_check(state_beta, i); diff --git a/external/macis/include/macis/types.hpp b/external/macis/include/macis/types.hpp index 1949faed9d..83d5ea0bf6 100644 --- a/external/macis/include/macis/types.hpp +++ b/external/macis/include/macis/types.hpp @@ -8,7 +8,12 @@ */ #pragma once + +#if defined(_MSC_VER) +#include +#else #include +#endif #include #include @@ -64,11 +69,50 @@ using wfn_t = std::bitset; template using wavefunction_iterator_t = typename std::vector >::iterator; +#if defined(_MSC_VER) && !defined(__clang__) +#pragma warning(push) +#pragma warning(disable : 4146) // unary minus on unsigned +// MSVC does not support __uint128_t; use a struct-based polyfill +struct uint128_t { + uint64_t lo; + uint64_t hi; + uint128_t() : lo(0), hi(0) {} + uint128_t(uint64_t v) : lo(v), hi(0) {} + bool operator<(const uint128_t& o) const { + return hi < o.hi || (hi == o.hi && lo < o.lo); + } + bool operator==(const uint128_t& o) const { return lo == o.lo && hi == o.hi; } + uint128_t operator<<(int shift) const { + uint128_t r; + if (shift >= 128) { + r.lo = 0; + r.hi = 0; + } else if (shift >= 64) { + r.lo = 0; + r.hi = lo << (shift - 64); + } else if (shift > 0) { + r.lo = lo << shift; + r.hi = (hi << shift) | (lo >> (64 - shift)); + } else { + r.lo = lo; + r.hi = hi; + } + return r; + } + uint128_t& operator|=(const uint128_t& o) { + lo |= o.lo; + hi |= o.hi; + return *this; + } +}; +#pragma warning(pop) +#else #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" /// @brief 128-bit unsigned integer type using uint128_t = __uint128_t; #pragma GCC diagnostic pop +#endif /** * @brief A type-safe wrapper that associates a value with a specific parameter diff --git a/external/macis/python/tests/test_pymacis.py b/external/macis/python/tests/test_pymacis.py index c0d4056a01..9cce5c759e 100644 --- a/external/macis/python/tests/test_pymacis.py +++ b/external/macis/python/tests/test_pymacis.py @@ -336,9 +336,9 @@ def test_write_fcidump_with_threshold(tmp_path): pymacis.write_fcidump(str(output_path2), header, T, V, core_energy, 1e-13) # Read file contents - with open(output_path1, "r") as f: + with open(output_path1, "r", encoding="utf-8") as f: content_tight = f.read() - with open(output_path2, "r") as f: + with open(output_path2, "r", encoding="utf-8") as f: content_loose = f.read() # With tight threshold, small integrals should be present @@ -465,7 +465,7 @@ def test_write_fcidump_file_format(tmp_path): pymacis.write_fcidump(str(output_path), header, T, V, core_energy) # Read file content and verify format - with open(output_path, "r") as f: + with open(output_path, "r", encoding="utf-8") as f: content = f.read() # Check header format @@ -520,7 +520,7 @@ def test_fcidump_format_compatibility(tmp_path): # Manually create FCIDUMP file in indices first format fcidump_indices_first = tmp_path / "test_indices_first.fcidump" - with open(fcidump_indices_first, "w") as f: + with open(fcidump_indices_first, "w", encoding="utf-8") as f: # Write header f.write("&FCI NORB=2,NELEC=2,MS2=0,\n") f.write(" ISYM=1,\n") diff --git a/external/macis/src/lobpcgxx/CMakeLists.txt b/external/macis/src/lobpcgxx/CMakeLists.txt index bb25d79a7c..c965ca1936 100644 --- a/external/macis/src/lobpcgxx/CMakeLists.txt +++ b/external/macis/src/lobpcgxx/CMakeLists.txt @@ -16,29 +16,65 @@ endif() set( gpu_backend "none" CACHE STRING "GPU backend to use" FORCE) message( WARNING "Turning off GPU Backend for BLAS++/LAPACK++") set(build_tests OFF CACHE BOOL "Build BLAS++/LAPACK++ tests" FORCE) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") +if(NOT MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") +endif() find_package(lapackpp CONFIG QUIET) if(NOT lapackpp_FOUND ) -include( FetchContent ) + include( FetchContent ) + # MSVC (native cl and clang-cl): apply combined compatibility patch + # (replaces __PRETTY_FUNCTION__ with __FUNCSIG__ and fixes BLASFinder cache + # bug). The cache fix is always required on MSVC; the __FUNCSIG__ swap is + # benign under clang-cl since it also supports __FUNCSIG__. + if(MSVC) + set(_blaspp_patch PATCH_COMMAND "${CMAKE_COMMAND}" + -DPATCH_FILE=${QDK_CMAKE_PATCHES_DIR}/blaspp-msvc.patch + -P ${QDK_CMAKE_PATCHES_DIR}/apply-patch.cmake) + else() + set(_blaspp_patch "") + endif() FetchContent_Declare( blaspp GIT_REPOSITORY https://github.com/icl-utk-edu/blaspp.git GIT_TAG v2025.05.28 + ${_blaspp_patch} + UPDATE_DISCONNECTED TRUE ) + if(MSVC) + set(_lapackpp_patch PATCH_COMMAND "${CMAKE_COMMAND}" + -DPATCH_FILE=${QDK_CMAKE_PATCHES_DIR}/lapackpp-msvc-allocator.patch + -P ${QDK_CMAKE_PATCHES_DIR}/apply-patch.cmake) + else() + set(_lapackpp_patch "") + endif() FetchContent_Declare( lapackpp GIT_REPOSITORY https://github.com/icl-utk-edu/lapackpp.git GIT_TAG v2025.05.28 + UPDATE_DISCONNECTED TRUE + ${_lapackpp_patch} ) FetchContent_MakeAvailable( blaspp ) FetchContent_MakeAvailable( lapackpp ) - target_compile_definitions( lapackpp PUBLIC LAPACK_COMPLEX_CPP ) + + if(MSVC) + # lapackpp sources use std::complex directly but on Windows _MSC_VER causes + # config.h to skip #include . Force-include it. + target_compile_options( lapackpp PRIVATE /FIcomplex ) + endif() endif() add_library( lobpcgxx INTERFACE ) target_link_libraries( lobpcgxx INTERFACE blaspp lapackpp ) target_compile_features( lobpcgxx INTERFACE cxx_std_20 ) -target_compile_definitions( lobpcgxx INTERFACE LAPACK_COMPLEX_CPP ) +if(NOT MSVC) + # On non-MSVC (Linux/macOS), instruct lapack/config.h to use (C++) + # rather than (C99). The C99 header defines the `I` macro as + # `__extension__ _Complex_I`, which conflicts with Boost template parameter + # names used in downstream headers. On MSVC the equivalent is achieved via + # /FIcomplex on the lapackpp target directly (see above). + target_compile_definitions( lobpcgxx INTERFACE LAPACK_COMPLEX_CPP ) +endif() target_include_directories( lobpcgxx INTERFACE $ diff --git a/external/macis/src/lobpcgxx/include/lobpcgxx/lobpcg.hpp b/external/macis/src/lobpcgxx/include/lobpcgxx/lobpcg.hpp index bbd179cb32..1597d3942d 100644 --- a/external/macis/src/lobpcgxx/include/lobpcgxx/lobpcg.hpp +++ b/external/macis/src/lobpcgxx/include/lobpcgxx/lobpcg.hpp @@ -5,11 +5,15 @@ #include #include +#ifndef _MSC_VER #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" +#endif #include #include +#ifndef _MSC_VER #pragma GCC diagnostic pop +#endif #include "driver.hpp" #include "simplified_interface.hpp" diff --git a/external/macis/src/macis/CMakeLists.txt b/external/macis/src/macis/CMakeLists.txt index e587237e0f..765f93c936 100644 --- a/external/macis/src/macis/CMakeLists.txt +++ b/external/macis/src/macis/CMakeLists.txt @@ -69,13 +69,18 @@ include(macis-mdspan) target_link_libraries(macis PUBLIC mdspan) # IPS4O parallel sorting (used by residue arrays and dynamic bit masking -# headers via __has_include). Build-time only — consumers that need it -# must fetch ips4o themselves; the headers fall back to std::sort. -include(macis-ips4o) -target_include_directories(macis SYSTEM PUBLIC - $) -if(NOT APPLE) - target_link_libraries(macis PUBLIC atomic) +# headers via __has_include). Only used by the parallel-sort code paths, +# which are gated behind defined(_OPENMP); the headers fall back to +# std::sort otherwise. Skip the fetch entirely when OpenMP is disabled. +if(MACIS_ENABLE_OPENMP) + include(macis-ips4o) + target_include_directories(macis SYSTEM PUBLIC + $) + # libatomic is a GCC/Clang runtime; it does not exist on Windows (MSVC and + # clang-cl provide atomics intrinsically) or on Apple. + if(NOT APPLE AND NOT WIN32) + target_link_libraries(macis PUBLIC atomic) + endif() endif() diff --git a/external/macis/src/macis/fcidump.cxx b/external/macis/src/macis/fcidump.cxx index 78f66dd0c7..90eaa6a1e8 100644 --- a/external/macis/src/macis/fcidump.cxx +++ b/external/macis/src/macis/fcidump.cxx @@ -115,16 +115,26 @@ FCIDumpFormat detect_fcidump_format(const std::string& line) { auto fcidump_line_integral_first(const std::string& line) { int32_t p, q, r, s; double integral; +#ifdef _MSC_VER + int parsed = + sscanf_s(line.c_str(), "%lf %d %d %d %d", &integral, &p, &q, &r, &s); +#else int parsed = sscanf(line.c_str(), "%lf %d %d %d %d", &integral, &p, &q, &r, &s); +#endif return std::make_tuple(bool(parsed == 5), p, q, r, s, integral); } auto fcidump_line_indices_first(const std::string& line) { int32_t p, q, r, s; double integral; +#ifdef _MSC_VER + int parsed = + sscanf_s(line.c_str(), "%d %d %d %d %lf", &p, &q, &r, &s, &integral); +#else int parsed = sscanf(line.c_str(), "%d %d %d %d %lf", &p, &q, &r, &s, &integral); +#endif return std::make_tuple(bool(parsed == 5), p, q, r, s, integral); } diff --git a/external/macis/src/macis/hamiltonian_generator/base.ipp b/external/macis/src/macis/hamiltonian_generator/base.ipp index 5d87755276..5715e48331 100644 --- a/external/macis/src/macis/hamiltonian_generator/base.ipp +++ b/external/macis/src/macis/hamiltonian_generator/base.ipp @@ -11,11 +11,15 @@ #include #include -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" +#ifndef _MSC_VER +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-parameter" +#endif #include #include -#pragma GCC diagnostic pop +#ifndef _MSC_VER +# pragma GCC diagnostic pop +#endif namespace macis { diff --git a/external/macis/src/macis/mcscf/fock_matrices.cxx b/external/macis/src/macis/mcscf/fock_matrices.cxx index 65e8a3626b..a45a2dd3ef 100644 --- a/external/macis/src/macis/mcscf/fock_matrices.cxx +++ b/external/macis/src/macis/mcscf/fock_matrices.cxx @@ -186,7 +186,7 @@ void generalized_fock_matrix_comp_mat1(NumOrbital _norb, NumInactive _ninact, size_t LDD1, const double* A2RDM, size_t LDD2, double* F, size_t LDF) { const auto norb = _norb.get(); - const auto ninact = _ninact.get(); + [[maybe_unused]] const auto ninact = _ninact.get(); const auto nact = _nact.get(); const size_t norb2 = norb * norb; @@ -212,8 +212,8 @@ void generalized_fock_matrix_comp_mat2(NumOrbital _norb, NumInactive _ninact, size_t LDD1, const double* A2RDM, size_t LDD2, double* F, size_t LDF) { const auto norb = _norb.get(); - const auto ninact = _ninact.get(); - const auto nact = _nact.get(); + [[maybe_unused]] const auto ninact = _ninact.get(); + [[maybe_unused]] const auto nact = _nact.get(); std::vector Fi(norb * norb); inactive_fock_matrix(_norb, _ninact, T, LDT, V_full, LDV, Fi.data(), norb); diff --git a/external/macis/src/macis/mcscf/orbital_gradient.cxx b/external/macis/src/macis/mcscf/orbital_gradient.cxx index 81f74ee909..2da479f61f 100644 --- a/external/macis/src/macis/mcscf/orbital_gradient.cxx +++ b/external/macis/src/macis/mcscf/orbital_gradient.cxx @@ -33,7 +33,7 @@ void compute_orbital_rotation(NumOrbital _norb, double alpha, const double* K, void fock_to_gradient(NumOrbital _norb, NumInactive _ninact, NumActive _nact, NumVirtual _num_virtual_orbitals, const double* F, size_t LDF, double* OG, size_t LDOG) { - const auto norb = _norb.get(); + [[maybe_unused]] const auto norb = _norb.get(); const auto nact = _nact.get(); const auto ninact = _ninact.get(); const auto num_virtual_orbitals = _num_virtual_orbitals.get(); @@ -74,8 +74,8 @@ void orbital_rotated_generalized_fock( double* T_trans, size_t LDTT, double* V_trans, size_t LDVT, double* F, size_t LDF) { const auto norb = _norb.get(); - const auto nact = _nact.get(); - const auto ninact = _ninact.get(); + [[maybe_unused]] const auto nact = _nact.get(); + [[maybe_unused]] const auto ninact = _ninact.get(); // Transform Integrals two_index_transform(norb, norb, T, LDT, U, LDU, T_trans, LDTT); @@ -201,19 +201,19 @@ void numerical_orbital_hessian(NumOrbital _norb, NumInactive ninact, std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = 2 * Kx[p] + Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xp2_yp1 = energy(); + [[maybe_unused]] auto E_xp2_yp1 = energy(); // E(x+h,y+2h) std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = Kx[p] + 2 * Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xp1_yp2 = energy(); + [[maybe_unused]] auto E_xp1_yp2 = energy(); // E(x+2h,y+2h) std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = 2 * Kx[p] + 2 * Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xp2_yp2 = energy(); + [[maybe_unused]] auto E_xp2_yp2 = energy(); // E(x+h,y-h) std::fill(K.begin(), K.end(), 0); @@ -225,19 +225,19 @@ void numerical_orbital_hessian(NumOrbital _norb, NumInactive ninact, std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = 2 * Kx[p] - Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xp2_ym1 = energy(); + [[maybe_unused]] auto E_xp2_ym1 = energy(); // E(x+h,y-2h) std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = Kx[p] - 2 * Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xp1_ym2 = energy(); + [[maybe_unused]] auto E_xp1_ym2 = energy(); // E(x+2h,y-2h) std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = 2 * Kx[p] - 2 * Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xp2_ym2 = energy(); + [[maybe_unused]] auto E_xp2_ym2 = energy(); // E(x-h,y+h) std::fill(K.begin(), K.end(), 0); @@ -249,19 +249,19 @@ void numerical_orbital_hessian(NumOrbital _norb, NumInactive ninact, std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = -2 * Kx[p] + Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xm2_yp1 = energy(); + [[maybe_unused]] auto E_xm2_yp1 = energy(); // E(x-h,y+2h) std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = -Kx[p] + 2 * Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xm1_yp2 = energy(); + [[maybe_unused]] auto E_xm1_yp2 = energy(); // E(x-2h,y+2h) std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = -2 * Kx[p] + 2 * Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xm2_yp2 = energy(); + [[maybe_unused]] auto E_xm2_yp2 = energy(); // E(x-h,y-h) std::fill(K.begin(), K.end(), 0); @@ -273,19 +273,19 @@ void numerical_orbital_hessian(NumOrbital _norb, NumInactive ninact, std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = -2 * Kx[p] - Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xm2_ym1 = energy(); + [[maybe_unused]] auto E_xm2_ym1 = energy(); // E(x-h,y-2h) std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = -Kx[p] - 2 * Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xm1_ym2 = energy(); + [[maybe_unused]] auto E_xm1_ym2 = energy(); // E(x-2h,y-2h) std::fill(K.begin(), K.end(), 0); for (size_t p = 0; p < norb2; ++p) K[p] = -2 * Kx[p] - 2 * Ky[p]; compute_orbital_rotation(_norb, 1.0, K.data(), norb, U.data(), norb); - auto E_xm2_ym2 = energy(); + [[maybe_unused]] auto E_xm2_ym2 = energy(); OH[a + i * LDOH + b * LDOH * LDOH + j * LDOH * LDOH * LDOH] = (E_xp1_yp1 + E_xm1_ym1 - E_xp1_ym1 - E_xm1_yp1) / (4 * dk * dk); diff --git a/external/macis/src/macis/mcscf/orbital_hessian.cxx b/external/macis/src/macis/mcscf/orbital_hessian.cxx index d8996dd967..3a7cc36e07 100644 --- a/external/macis/src/macis/mcscf/orbital_hessian.cxx +++ b/external/macis/src/macis/mcscf/orbital_hessian.cxx @@ -110,7 +110,7 @@ void orb_orb_hessian_contract(NumOrbital norb, NumInactive ninact, const double* OG, const double* K_lin, double* HK_lin) { const size_t no = norb.get(); - const size_t na = nact.get(); + [[maybe_unused]] const size_t na = nact.get(); const size_t no2 = no * no; const size_t no4 = no2 * no2; const size_t orb_rot_sz = diff --git a/external/macis/src/macis/mcscf/orbital_steps.cxx b/external/macis/src/macis/mcscf/orbital_steps.cxx index bf50752c66..901823a589 100644 --- a/external/macis/src/macis/mcscf/orbital_steps.cxx +++ b/external/macis/src/macis/mcscf/orbital_steps.cxx @@ -18,9 +18,10 @@ void precond_cg_orbital_step(NumOrbital norb, NumInactive ninact, size_t LDFa, const double* F, size_t LDF, const double* A1RDM, size_t LDD, const double* OG, double* K_lin) { - const size_t no = norb.get(), ni = ninact.get(), na = nact.get(), - nv = num_virtual_orbitals.get(), - orb_rot_sz = nv * (na + ni) + na * ni; + [[maybe_unused]] const size_t no = norb.get(), ni = ninact.get(), + na = nact.get(), + nv = num_virtual_orbitals.get(), + orb_rot_sz = nv * (na + ni) + na * ni; std::vector DH(orb_rot_sz); // Compute approximate diagonal hessian diff --git a/external/macis/src/macis/moller_plesset.cxx b/external/macis/src/macis/moller_plesset.cxx index f8712d0529..a74896a7c9 100644 --- a/external/macis/src/macis/moller_plesset.cxx +++ b/external/macis/src/macis/moller_plesset.cxx @@ -27,10 +27,10 @@ void mp2_t2(NumCanonicalOccupied _num_occupied_orbitals, const size_t LDV3 = LDV2 * LDV; // T2(i,j,a,b) = (ia|jb) / (eps[i] + eps[j] - eps[a] - eps[b]) - for (auto i = 0ul; i < num_occupied_orbitals; ++i) - for (auto j = 0ul; j < num_occupied_orbitals; ++j) - for (auto a = 0ul; a < num_virtual_orbitals; ++a) - for (auto b = 0ul; b < num_virtual_orbitals; ++b) { + for (size_t i = 0; i < num_occupied_orbitals; ++i) + for (size_t j = 0; j < num_occupied_orbitals; ++j) + for (size_t a = 0; a < num_virtual_orbitals; ++a) + for (size_t b = 0; b < num_virtual_orbitals; ++b) { const auto a_off = a + num_occupied_orbitals; const auto b_off = b + num_occupied_orbitals; @@ -52,7 +52,7 @@ void mp2_1rdm(NumOrbital _norb, NumCanonicalOccupied _num_occupied_orbitals, num_occupied_orbitals * num_occupied_orbitals; const size_t nocc2v = num_occupied_orbitals2 * num_virtual_orbitals; const size_t LDV2 = LDV * LDV; - const size_t LDV3 = LDV2 * LDV; + [[maybe_unused]] const size_t LDV3 = LDV2 * LDV; // Compute canonical eigenenergies // XXX: This will not generally replicate full precision diff --git a/external/macis/src/sparsexx/include/sparsexx/spblas/pspmbv.hpp b/external/macis/src/sparsexx/include/sparsexx/spblas/pspmbv.hpp index 9c7fad17a4..459d232cd6 100644 --- a/external/macis/src/sparsexx/include/sparsexx/spblas/pspmbv.hpp +++ b/external/macis/src/sparsexx/include/sparsexx/spblas/pspmbv.hpp @@ -253,9 +253,9 @@ auto generate_spmv_comm_info(const DistSpMatrixType& A) { // Linearize the send/recv data structures size_t nrecv_indices = - std::accumulate(recv_counts.begin(), recv_counts.end(), 0ul); + std::accumulate(recv_counts.begin(), recv_counts.end(), size_t{0}); size_t nsend_indices = - std::accumulate(send_counts.begin(), send_counts.end(), 0ul); + std::accumulate(send_counts.begin(), send_counts.end(), size_t{0}); std::vector send_indices, recv_indices; send_indices.reserve(nsend_indices); recv_indices.reserve(nrecv_indices); diff --git a/external/macis/src/sparsexx/include/sparsexx/util/submatrix.hpp b/external/macis/src/sparsexx/include/sparsexx/util/submatrix.hpp index 00f0570d09..26eafb66cd 100644 --- a/external/macis/src/sparsexx/include/sparsexx/util/submatrix.hpp +++ b/external/macis/src/sparsexx/include/sparsexx/util/submatrix.hpp @@ -110,7 +110,7 @@ SpMatType extract_submatrix(const SpMatType& A, std::pair lo, // Calculate total NNZ const auto nnz_sub = - std::accumulate(row_counts.begin(), row_counts.end(), 0ul); + std::accumulate(row_counts.begin(), row_counts.end(), size_t{0}); // Allocate submatrix SpMatType sub(M_sub, N_sub, nnz_sub, indexing); @@ -207,7 +207,7 @@ SpMatType extract_submatrix_inclrow_exclcol(const SpMatType& A, // Calculate total NNZ const auto nnz_sub = - std::accumulate(row_counts.begin(), row_counts.end(), 0ul); + std::accumulate(row_counts.begin(), row_counts.end(), size_t{0}); // Allocate submatrix SpMatType sub(M_sub, N_sub, nnz_sub, indexing); diff --git a/external/macis/src/sparsexx/src/metis_wrapper.cxx b/external/macis/src/sparsexx/src/metis_wrapper.cxx index ff7156055a..d9e8c2617b 100644 --- a/external/macis/src/sparsexx/src/metis_wrapper.cxx +++ b/external/macis/src/sparsexx/src/metis_wrapper.cxx @@ -16,9 +16,11 @@ namespace sparsexx::detail { template -void metis_kway_partitioning(int64_t _nvert, int64_t _npart, IndexType* _xadj, - IndexType* _adjncy, - std::vector& _part) { +void metis_kway_partitioning([[maybe_unused]] int64_t _nvert, + [[maybe_unused]] int64_t _npart, + [[maybe_unused]] IndexType* _xadj, + [[maybe_unused]] IndexType* _adjncy, + [[maybe_unused]] std::vector& _part) { #ifdef SPARSEXX_ENABLE_METIS idx_t nvert = _nvert; idx_t nweights = 1; diff --git a/external/macis/tests/asci.cxx b/external/macis/tests/asci.cxx index fe68e07119..e348cd7bba 100644 --- a/external/macis/tests/asci.cxx +++ b/external/macis/tests/asci.cxx @@ -360,7 +360,7 @@ TEST_CASE("Constraints") { using constraint_type = macis::alpha_constraint; const size_t norb = 10; - size_t nalpha, nbeta; + size_t nalpha = 0, nbeta = 0; SECTION("Closed Shell") { nalpha = 6; diff --git a/external/macis/tests/csr_hamiltonian.cxx b/external/macis/tests/csr_hamiltonian.cxx index 79eb90d2e8..483d007c99 100644 --- a/external/macis/tests/csr_hamiltonian.cxx +++ b/external/macis/tests/csr_hamiltonian.cxx @@ -76,8 +76,8 @@ TEMPLATE_TEST_CASE("CSR Hamiltonian", "[ham_gen]", size_t ref_n = 12636; size_t ref_nnz = 3517816; - REQUIRE(H.m() == ref_n); - REQUIRE(H.nnz() == ref_nnz); + REQUIRE(static_cast(H.m()) == ref_n); + REQUIRE(static_cast(H.nnz()) == ref_nnz); // Read reference data std::vector ref_rowptr(H.rowptr().size()), diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 3ac21adcd9..a404f2804c 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -76,6 +76,7 @@ if( NOT linalg-cmake-modules_POPULATED ) endif() # list( APPEND CMAKE_MODULE_PATH ${linalg-cmake-modules_SOURCE_DIR} ) + # First try to find an installed qdk library find_package(qdk QUIET COMPONENTS chemistry) @@ -112,6 +113,18 @@ find_package(Python 3.10 REQUIRED COMPONENTS Interpreter Development.Module) # Find pybind11 - tell it to use the FindPython we already called set(PYBIND11_FINDPYTHON ON) +# vcpkg's find_package wrapper may miss pip-installed pybind11; ask pybind11 for its cmakedir. +if(NOT pybind11_DIR) + execute_process( + COMMAND "${Python_EXECUTABLE}" -m pybind11 --cmakedir + OUTPUT_VARIABLE _pybind11_cmakedir + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE _pybind11_result) + if(_pybind11_result EQUAL 0 AND _pybind11_cmakedir) + set(pybind11_DIR "${_pybind11_cmakedir}" CACHE PATH "" FORCE) + endif() +endif() find_package(pybind11 REQUIRED) # Create the Python extension module @@ -170,8 +183,12 @@ target_compile_features(_core PUBLIC cxx_std_20) if(QDK_CHEMISTRY_ENABLE_COVERAGE) if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") message(STATUS "Enabling coverage build") - target_compile_options(_core PRIVATE --coverage -fprofile-arcs -ftest-coverage) - target_link_libraries(_core PRIVATE --coverage) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang" AND NOT MSVC) + target_compile_options(_core PRIVATE --coverage -fprofile-arcs -ftest-coverage) + target_link_libraries(_core PRIVATE --coverage) + else() + message(WARNING "Coverage build is only supported with GCC or Clang compilers") + endif() else() message(FATAL_ERROR "Coverage build is only supported in CMAKE_BUILD_TYPE=Debug or RelWithDebInfo mode") endif() @@ -205,13 +222,50 @@ target_compile_definitions(_core PRIVATE VERSION_INFO="${QDK_FULL_VERSION}") set_target_properties(_core PROPERTIES CXX_VISIBILITY_PRESET "hidden" VISIBILITY_INLINES_HIDDEN YES - BUILD_RPATH "$ORIGIN:$ORIGIN/.." - INSTALL_RPATH "$ORIGIN:$ORIGIN/.." - BUILD_RPATH_USE_ORIGIN ON ) +if(NOT WIN32) + set_target_properties(_core PROPERTIES + BUILD_RPATH "$ORIGIN:$ORIGIN/.." + INSTALL_RPATH "$ORIGIN:$ORIGIN/.." + BUILD_RPATH_USE_ORIGIN ON + ) +endif() # Install the Python extension module install(TARGETS _core LIBRARY DESTINATION . COMPONENT _core ) + +# Windows (Python 3.8+): DLL dependencies of .pyd files are only found if they +# reside in the same directory as the .pyd or are registered via os.add_dll_directory(). +# PATH is NOT searched. When vcpkg provides dynamic libraries (x64-windows triplet), +# copy the runtime DLLs next to _core.pyd so the package works out of the box. +# With static triplets (x64-windows-static-md) all dependency code is linked directly +# into _core.pyd, so no bundling is needed. +if(VCPKG_LIBRARY_LINKAGE STREQUAL "static") + set(_bundle_dlls_default OFF) +else() + set(_bundle_dlls_default ON) +endif() +option(QDK_BUNDLE_RUNTIME_DLLS "Bundle vcpkg runtime DLLs into the install tree (Windows only)" ${_bundle_dlls_default}) + +if(WIN32 AND QDK_BUNDLE_RUNTIME_DLLS) + message(STATUS "Bundling runtime DLLs into install tree") + # -- vcpkg dependencies (openblas, hdf5, fmt, spdlog and their transitive deps) -- + if(VCPKG_INSTALLED_DIR AND VCPKG_TARGET_TRIPLET) + set(_vcpkg_bin "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/bin") + set(_vcpkg_runtime_dlls) + foreach(_dll fmt hdf5 hdf5_cpp openblas spdlog szip zlib1) + if(EXISTS "${_vcpkg_bin}/${_dll}.dll") + list(APPEND _vcpkg_runtime_dlls "${_vcpkg_bin}/${_dll}.dll") + endif() + endforeach() + if(_vcpkg_runtime_dlls) + install(FILES ${_vcpkg_runtime_dlls} + DESTINATION . + COMPONENT _core + ) + endif() + endif() +endif() diff --git a/python/pyproject.toml b/python/pyproject.toml index e86098eb47..107b9ab6b2 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -60,6 +60,7 @@ dev = [ "mypy", "nbclient", "nbformat", + "pre-commit", "pytest", "pytest-env", "ruff" @@ -87,7 +88,7 @@ openfermion-extras = [ "openfermion>=1.0.0; python_version < '3.14'" ] plugins = [ - "pyscf>=2.9.0,<2.12.1" + "pyscf>=2.9.0,<2.12.1; sys_platform != 'win32'" ] qiskit-extras = [ "qiskit[qasm3-import]>=2.2.0; python_version < '3.14'", @@ -261,7 +262,7 @@ cmake.build-type = "Release" # Build in current directory (python/) cmake.source-dir = "." # Enable verbose output for debugging -cmake.verbose = true +build.verbose = true install.components = ["_core", "qdk_chemistry_resources"] metadata.version.provider = "_dev_version" metadata.version.provider-path = "." diff --git a/python/src/qdk_chemistry/__init__.py b/python/src/qdk_chemistry/__init__.py index 3d448916f8..4cad90a9bc 100644 --- a/python/src/qdk_chemistry/__init__.py +++ b/python/src/qdk_chemistry/__init__.py @@ -5,16 +5,35 @@ # Licensed under the MIT License. See LICENSE.txt in the project root for license information. # -------------------------------------------------------------------------------------------- +# On Windows, register extra DLL directories (QDK_DLL_DIR) before importing _core. +import os as _os +import sys as _sys from importlib.metadata import PackageNotFoundError as _PackageNotFoundError from importlib.metadata import version as _get_version from pathlib import Path +# Force UTF-8 stdout/stderr; Windows' default cp1252 can't encode Q# circuit diagrams. +if hasattr(_sys.stdout, "reconfigure"): + _sys.stdout.reconfigure(encoding="utf-8") +if hasattr(_sys.stderr, "reconfigure"): + _sys.stderr.reconfigure(encoding="utf-8") + +if _sys.platform == "win32": + # QDK_DLL_DIR: semicolon-separated extra DLL dirs; bundled DLLs are found automatically. + _dll_dirs = _os.environ.get("QDK_DLL_DIR", "") + _dll_dir_handles = [] + for _d in _dll_dirs.split(";"): + _d = _d.strip() + if _d and _os.path.isdir(_d): + _dll_dir_handles.append(_os.add_dll_directory(_d)) # type: ignore[attr-defined] + del _dll_dirs + try: __version__ = _get_version("qdk-chemistry") except _PackageNotFoundError: # Fallback for development/uninstalled use - read from VERSION file try: - __version__ = (Path(__file__).parent.parent.parent.parent / "VERSION").read_text().strip() + __version__ = (Path(__file__).parent.parent.parent.parent / "VERSION").read_text(encoding="utf-8").strip() except (OSError, UnicodeDecodeError): # VERSION file not reachable or unreadable (e.g. vendored copy without repo root) __version__ = "0.0.0+local" @@ -141,7 +160,7 @@ def _is_placeholder_stub(stub_file: Path) -> bool: if not stub_file.exists(): return True try: - content = stub_file.read_text() + content = stub_file.read_text(encoding="utf-8") return "placeholder" in content.lower() except (OSError, PermissionError): return False @@ -157,7 +176,7 @@ def _update_stub_references(stub_file: Path) -> None: Also adds necessary imports if they don't exist. """ try: - content = stub_file.read_text() + content = stub_file.read_text(encoding="utf-8") original_content = content needs_data_import = False needs_algorithms_import = False @@ -205,7 +224,7 @@ def _update_stub_references(stub_file: Path) -> None: lines[import_section_end:import_section_end] = new_imports content = "\n".join(lines) - stub_file.write_text(content) + stub_file.write_text(content, encoding="utf-8") except (OSError, PermissionError): pass # Skip files that can't be read/written @@ -249,6 +268,7 @@ def _generate_stubs_on_first_import() -> None: check=False, capture_output=True, text=True, + encoding="utf-8", cwd=str(chemistry_dir), ) @@ -369,7 +389,7 @@ def _generate_registry_stubs() -> None: ) overload_code = "\n".join(overloads) - stub_file.write_text(overload_code) + stub_file.write_text(overload_code, encoding="utf-8") except (ImportError, AttributeError, RuntimeError, OSError) as e: # Log but don't fail - type stubs are optional diff --git a/python/src/qdk_chemistry/data/base.py b/python/src/qdk_chemistry/data/base.py index 503c4b8531..6dba57fa16 100644 --- a/python/src/qdk_chemistry/data/base.py +++ b/python/src/qdk_chemistry/data/base.py @@ -349,7 +349,7 @@ def to_json_file(self, filename: str | Path) -> None: """ if self._data_type_name: _validate_filename_suffix(filename, self._data_type_name, "write") - with Path(filename).open("w") as f: + with Path(filename).open("w", encoding="utf-8") as f: json.dump(self.to_json(), f, indent=2) def to_hdf5_file(self, filename: str | Path) -> None: @@ -440,7 +440,7 @@ def from_json_file(cls, filename: str | Path) -> "DataClass": """ if cls._data_type_name: _validate_filename_suffix(filename, cls._data_type_name, "read") - with Path(filename).open("r") as f: + with Path(filename).open("r", encoding="utf-8") as f: json_data = json.load(f) return cls.from_json(json_data) diff --git a/python/src/qdk_chemistry/data/noise_models.py b/python/src/qdk_chemistry/data/noise_models.py index 506e693110..60ee0a41cf 100644 --- a/python/src/qdk_chemistry/data/noise_models.py +++ b/python/src/qdk_chemistry/data/noise_models.py @@ -260,7 +260,7 @@ def to_yaml_file(self, yaml_file: str | Path) -> None: # Convert to serializable dict data = self.to_json() - with Path(yaml_file).open("w") as f: + with Path(yaml_file).open("w", encoding="utf-8") as f: yaml.dump(data, f) @classmethod @@ -278,7 +278,7 @@ def from_yaml_file(cls, yaml_file: str | Path) -> "QuantumErrorProfile": if not Path(yaml_file).exists(): raise FileNotFoundError(f"File {yaml_file} not found") - with Path(yaml_file).open("r") as f: + with Path(yaml_file).open("r", encoding="utf-8") as f: data = yaml.load(f) if not isinstance(data, dict): diff --git a/python/src/qdk_chemistry/utils/cubegen.py b/python/src/qdk_chemistry/utils/cubegen.py index 6335cc7b97..410286b204 100644 --- a/python/src/qdk_chemistry/utils/cubegen.py +++ b/python/src/qdk_chemistry/utils/cubegen.py @@ -73,7 +73,7 @@ def _generate_cube(coeff, label): cubegen.orbital(mol, outfile=outfile_name, coeff=coeff, nx=nx, ny=ny, nz=nz, margin=margin) if output_folder is None: - with open(outfile_name) as f: + with open(outfile_name, encoding="utf-8") as f: assert isinstance(cubefile_paths, dict) cubefile_paths[label.replace(".cube", "")] = f.read() os.remove(outfile_name) diff --git a/python/src/qdk_chemistry/utils/qsharp/__init__.py b/python/src/qdk_chemistry/utils/qsharp/__init__.py index 5c7c9499c0..d21c93f932 100644 --- a/python/src/qdk_chemistry/utils/qsharp/__init__.py +++ b/python/src/qdk_chemistry/utils/qsharp/__init__.py @@ -30,7 +30,7 @@ def get_qsharp_utils(): try: return qdk.code.QDKChemistry.Utils except AttributeError: - code = "\n".join(f.read_text() for f in _QS_FILES) + code = "\n".join(f.read_text(encoding="utf-8") for f in _QS_FILES) qsharp.eval(code) return qdk.code.QDKChemistry.Utils diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 5b9e9696f6..208553274e 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -6,13 +6,24 @@ # -------------------------------------------------------------------------------------------- import os +import sys # Disable telemetry before any qdk_chemistry imports. # Uses setdefault so an explicit env override is still respected. os.environ.setdefault("QSHARP_PYTHON_TELEMETRY", "false") +# Force UTF-8 stdout/stderr (Windows cp1252 breaks circuit diagrams). +if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8") # type: ignore +if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8") # type: ignore + +# Non-interactive backend; must precede any matplotlib.pyplot import. +import matplotlib + +matplotlib.use("Agg") + import platform as plt -import sys import tempfile from pathlib import Path diff --git a/python/tests/test_basis_set.py b/python/tests/test_basis_set.py index d5111a19af..a0c44be041 100644 --- a/python/tests/test_basis_set.py +++ b/python/tests/test_basis_set.py @@ -343,7 +343,9 @@ def test_json_serialization(): assert basis_in.get_num_shells() == 2 assert basis_in.get_num_atomic_orbitals() == 4 - # Test file-based serialization + # Test file-based serialization. + # delete=False + manual unlink() avoids the Windows file lock from NamedTemporaryFile. + # NOTE: Python 3.12+ supports `delete=False, delete_on_close=True` which would avoid the manual unlink() at the end. with tempfile.NamedTemporaryFile(suffix=".basis_set.json", mode="w", delete=False) as tmp: filename = tmp.name @@ -357,7 +359,7 @@ def test_json_serialization(): assert basis_file.get_num_shells() == 2 assert basis_file.get_num_atomic_orbitals() == 4 finally: - Path(filename).unlink() + Path(filename).unlink(missing_ok=True) def test_hdf5_serialization(): @@ -540,9 +542,10 @@ def test_basis_set_file_io_generic(): basis = BasisSet("STO-3G", shells) # Test JSON file I/O - with tempfile.NamedTemporaryFile(suffix=".basis_set.json") as tmp_json: + with tempfile.NamedTemporaryFile(suffix=".basis_set.json", delete=False) as tmp_json: json_filename = tmp_json.name + try: # Save using generic method basis.to_file(json_filename, "json") @@ -554,11 +557,14 @@ def test_basis_set_file_io_generic(): assert basis2.get_num_shells() == basis.get_num_shells() assert basis2.get_num_atomic_orbitals() == basis.get_num_atomic_orbitals() assert basis2.get_num_atoms() == basis.get_num_atoms() + finally: + Path(json_filename).unlink(missing_ok=True) # Test HDF5 file I/O - with tempfile.NamedTemporaryFile(suffix=".basis_set.h5") as tmp_hdf5: + with tempfile.NamedTemporaryFile(suffix=".basis_set.h5", delete=False) as tmp_hdf5: hdf5_filename = tmp_hdf5.name + try: # Save using generic method basis.to_file(hdf5_filename, "hdf5") @@ -570,6 +576,8 @@ def test_basis_set_file_io_generic(): assert basis3.get_num_shells() == basis.get_num_shells() assert basis3.get_num_atomic_orbitals() == basis.get_num_atomic_orbitals() assert basis3.get_num_atoms() == basis.get_num_atoms() + finally: + Path(hdf5_filename).unlink(missing_ok=True) # Test unsupported file type with pytest.raises(RuntimeError, match="Unsupported file type"): @@ -598,9 +606,10 @@ def test_basis_set_hdf5_specific(): basis = BasisSet("6-31G", shells) # Test new to_hdf5_file method - with tempfile.NamedTemporaryFile(suffix=".basis_set.h5") as tmp_hdf5: + with tempfile.NamedTemporaryFile(suffix=".basis_set.h5", delete=False) as tmp_hdf5: hdf5_filename = tmp_hdf5.name + try: # Save using new method basis.to_hdf5_file(hdf5_filename) @@ -633,6 +642,8 @@ def test_basis_set_hdf5_specific(): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(hdf5_filename).unlink(missing_ok=True) def test_basis_set_json_specific(): @@ -654,9 +665,10 @@ def test_basis_set_json_specific(): basis = BasisSet("cc-pVDZ", shells, AOType.Cartesian) # Test updated JSON file I/O methods - with tempfile.NamedTemporaryFile(suffix=".basis_set.json") as tmp_json: + with tempfile.NamedTemporaryFile(suffix=".basis_set.json", delete=False) as tmp_json: json_filename = tmp_json.name + try: # Save using to_json_file method basis.to_json_file(json_filename) @@ -690,6 +702,8 @@ def test_basis_set_json_specific(): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(json_filename).unlink(missing_ok=True) def test_basis_set_file_io_validation(): @@ -755,9 +769,10 @@ def test_basis_set_file_io_round_trip(): basis = BasisSet("complex-basis", shells, AOType.Spherical) # Test JSON round-trip - with tempfile.NamedTemporaryFile(suffix=".basis_set.json") as tmp_json: + with tempfile.NamedTemporaryFile(suffix=".basis_set.json", delete=False) as tmp_json: json_filename = tmp_json.name + try: # Save and reload basis.to_json_file(json_filename) basis_json = BasisSet.from_json_file(json_filename) @@ -789,11 +804,14 @@ def test_basis_set_file_io_round_trip(): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(json_filename).unlink(missing_ok=True) # Test HDF5 round-trip - with tempfile.NamedTemporaryFile(suffix=".basis_set.h5") as tmp_hdf5: + with tempfile.NamedTemporaryFile(suffix=".basis_set.h5", delete=False) as tmp_hdf5: hdf5_filename = tmp_hdf5.name + try: # Save and reload basis.to_hdf5_file(hdf5_filename) basis_hdf5 = BasisSet.from_hdf5_file(hdf5_filename) @@ -823,6 +841,8 @@ def test_basis_set_file_io_round_trip(): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(hdf5_filename).unlink(missing_ok=True) def test_basis_set_consistency_between_methods(): diff --git a/python/tests/test_circuit.py b/python/tests/test_circuit.py index 156833be26..e62042cd7c 100644 --- a/python/tests/test_circuit.py +++ b/python/tests/test_circuit.py @@ -292,7 +292,7 @@ def test_to_json_file(self, simple_qasm, simple_qir): try: circuit.to_json_file(tmp_path) - with open(tmp_path) as f: + with open(tmp_path, encoding="utf-8") as f: loaded_data = json.load(f) assert "qasm" in loaded_data diff --git a/python/tests/test_docs_examples.py b/python/tests/test_docs_examples.py index 120fdc984c..de0fc972b7 100644 --- a/python/tests/test_docs_examples.py +++ b/python/tests/test_docs_examples.py @@ -13,6 +13,7 @@ import sys import unittest from pathlib import Path +from tempfile import TemporaryDirectory from typing import ClassVar from qdk_chemistry.plugins.qiskit import ( @@ -48,7 +49,7 @@ def check_example_requirements(example_file: Path) -> tuple[bool, bool, bool, bo requires_openfermion, is_slow) """ - content = example_file.read_text() + content = example_file.read_text(encoding="utf-8") requires_pyscf = False requires_qiskit = False @@ -150,18 +151,21 @@ def setUpClass(cls): def _run_python_example(self, example_file: Path): """Helper method to run a Python example file.""" - result = subprocess.run( - [sys.executable, str(example_file)], - check=False, - capture_output=True, - text=True, - timeout=360, - cwd=example_file.parent, - ) - - assert result.returncode == 0, ( - f"Example {example_file.name} failed:\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}" - ) + with TemporaryDirectory(dir=example_file.parent.parent) as tmpdir: + result = subprocess.run( + [sys.executable, str(example_file)], + check=False, + capture_output=True, + text=True, + encoding="utf-8", + timeout=360, + cwd=tmpdir, + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, + ) + + assert result.returncode == 0, ( + f"Example {example_file.name} failed:\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}" + ) # Dynamically create test methods for each example file diff --git a/python/tests/test_energy_estimator.py b/python/tests/test_energy_estimator.py index 3b6adfeec2..c22e3982a4 100644 --- a/python/tests/test_energy_estimator.py +++ b/python/tests/test_energy_estimator.py @@ -242,7 +242,7 @@ def test_measurement_data_to_json(): # Verify file was created and contains expected structure assert Path(temp_path).exists() - with open(temp_path) as f: + with open(temp_path, encoding="utf-8") as f: data = json.load(f) assert isinstance(data, dict) # Should have one entry for the pauli group plus version field diff --git a/python/tests/test_energy_expectation_result.py b/python/tests/test_energy_expectation_result.py index aa37d48edf..f7ac3d2d76 100644 --- a/python/tests/test_energy_expectation_result.py +++ b/python/tests/test_energy_expectation_result.py @@ -69,7 +69,7 @@ def test_energy_expectation_result_json_file_io(): assert Path(filename).exists() # Verify file contents - with open(filename) as f: + with open(filename, encoding="utf-8") as f: data = json.load(f) assert data["energy_expectation_value"] == -1.0 diff --git a/python/tests/test_estimator_data.py b/python/tests/test_estimator_data.py index ee8927646d..b24450f7f0 100644 --- a/python/tests/test_estimator_data.py +++ b/python/tests/test_estimator_data.py @@ -49,7 +49,7 @@ def test_measurement_data_serialization(): tmpfile_path = tmpfile.name # Load from json file and verify contents - with open(tmpfile_path) as f: + with open(tmpfile_path, encoding="utf-8") as f: data = json.load(f) assert data == measurement_data_dict diff --git a/python/tests/test_majorana_mapping.py b/python/tests/test_majorana_mapping.py index 6df7985bd8..c2ce89fd1c 100644 --- a/python/tests/test_majorana_mapping.py +++ b/python/tests/test_majorana_mapping.py @@ -19,6 +19,7 @@ # -------------------------------------------------------------------------------------------- import tempfile +from pathlib import Path import h5py import numpy as np @@ -274,11 +275,15 @@ def test_hdf5_round_trip_with_tapering(self) -> None: from qdk_chemistry.data import Symmetries # noqa: PLC0415 scbk = MajoranaMapping.symmetry_conserving_bravyi_kitaev(8, Symmetries(2, 2)) - with tempfile.NamedTemporaryFile(suffix=".h5") as f: - with h5py.File(f.name, "w") as hf: + with tempfile.NamedTemporaryFile(suffix=".h5", delete=False) as f: + fname = f.name + try: + with h5py.File(fname, "w") as hf: scbk.to_hdf5(hf) - with h5py.File(f.name, "r") as hf: + with h5py.File(fname, "r") as hf: loaded = MajoranaMapping.from_hdf5(hf) + finally: + Path(fname).unlink(missing_ok=True) assert loaded.table == scbk.table assert loaded.name == scbk.name assert loaded.tapering is not None @@ -557,11 +562,15 @@ def test_hdf5_roundtrip_via_mapping(self) -> None: from qdk_chemistry.data import Symmetries # noqa: PLC0415 scbk = MajoranaMapping.symmetry_conserving_bravyi_kitaev(8, Symmetries(2, 2)) - with tempfile.NamedTemporaryFile(suffix=".h5") as f: - with h5py.File(f.name, "w") as hf: + with tempfile.NamedTemporaryFile(suffix=".h5", delete=False) as f: + fname = f.name + try: + with h5py.File(fname, "w") as hf: scbk.to_hdf5(hf) - with h5py.File(f.name, "r") as hf: + with h5py.File(fname, "r") as hf: loaded = MajoranaMapping.from_hdf5(hf) + finally: + Path(fname).unlink(missing_ok=True) assert loaded.tapering is not None assert loaded.tapering.qubit_indices == scbk.tapering.qubit_indices assert loaded.tapering.eigenvalues == scbk.tapering.eigenvalues @@ -582,15 +591,19 @@ def test_hdf5_standalone_roundtrip(self) -> None: from qdk_chemistry.data import Symmetries, TaperingSpecification # noqa: PLC0415 tap = TaperingSpecification.symmetry_conserving_bravyi_kitaev(8, Symmetries(2, 2)) - with tempfile.NamedTemporaryFile(suffix=".h5") as f: - tap.to_hdf5_file(f.name) - with h5py.File(f.name, "r") as hf: + with tempfile.NamedTemporaryFile(suffix=".h5", delete=False) as f: + fname = f.name + try: + tap.to_hdf5_file(fname) + with h5py.File(fname, "r") as hf: assert "qubit_indices" in hf assert "eigenvalues" in hf reconstructed = TaperingSpecification( qubit_indices=[int(x) for x in hf["qubit_indices"][:]], eigenvalues=[int(x) for x in hf["eigenvalues"][:]], ) + finally: + Path(fname).unlink(missing_ok=True) assert reconstructed == tap def test_parity_tapering_json_roundtrip_via_mapping(self) -> None: @@ -620,11 +633,15 @@ def test_tapering_standalone_hdf5_roundtrip(self) -> None: from qdk_chemistry.data import Symmetries, TaperingSpecification # noqa: PLC0415 tap = TaperingSpecification.symmetry_conserving_bravyi_kitaev(8, Symmetries(2, 2)) - with tempfile.NamedTemporaryFile(suffix=".h5") as f: - with h5py.File(f.name, "w") as hf: + with tempfile.NamedTemporaryFile(suffix=".h5", delete=False) as f: + fname = f.name + try: + with h5py.File(fname, "w") as hf: tap.to_hdf5(hf) - with h5py.File(f.name, "r") as hf: + with h5py.File(fname, "r") as hf: loaded = TaperingSpecification.from_hdf5(hf) + finally: + Path(fname).unlink(missing_ok=True) assert loaded == tap def test_tapering_hash(self) -> None: @@ -674,11 +691,15 @@ def test_json_roundtrip(self) -> None: def test_hdf5_roundtrip(self) -> None: """Bilinear-only mapping survives HDF5 round-trip.""" mapping = self._make_bilinear_mapping() - with tempfile.NamedTemporaryFile(suffix=".h5") as f: - with h5py.File(f.name, "w") as hf: + with tempfile.NamedTemporaryFile(suffix=".h5", delete=False) as f: + fname = f.name + try: + with h5py.File(fname, "w") as hf: mapping.to_hdf5(hf) - with h5py.File(f.name, "r") as hf: + with h5py.File(fname, "r") as hf: loaded = MajoranaMapping.from_hdf5(hf) + finally: + Path(fname).unlink(missing_ok=True) assert not loaded.is_majorana_atomic assert loaded.num_modes == mapping.num_modes assert loaded.num_qubits == mapping.num_qubits @@ -729,11 +750,15 @@ def test_hdf5_roundtrip(self) -> None: import numpy as np # noqa: PLC0415 qh = self._make_tapered_qh() - with tempfile.NamedTemporaryFile(suffix=".h5") as f: - with h5py.File(f.name, "w") as hf: + with tempfile.NamedTemporaryFile(suffix=".h5", delete=False) as f: + fname = f.name + try: + with h5py.File(fname, "w") as hf: qh.to_hdf5(hf) - with h5py.File(f.name, "r") as hf: + with h5py.File(fname, "r") as hf: loaded = type(qh).from_hdf5(hf) + finally: + Path(fname).unlink(missing_ok=True) assert loaded.tapering is not None assert loaded.tapering.qubit_indices == qh.tapering.qubit_indices assert loaded.tapering.eigenvalues == qh.tapering.eigenvalues diff --git a/python/tests/test_mc.py b/python/tests/test_mc.py index 97fb01035d..e23d58aa06 100644 --- a/python/tests/test_mc.py +++ b/python/tests/test_mc.py @@ -18,6 +18,13 @@ float_comparison_relative_tolerance, ) +try: + import pyscf # noqa: F401 + + PYSCF_AVAILABLE = True +except ImportError: + PYSCF_AVAILABLE = False + def create_water_structure(): """Create a water molecule structure. @@ -86,6 +93,7 @@ def test_mc_calculator_water_fci(self): ) assert wfn_fci.size() == 441 + @pytest.mark.skipif(not PYSCF_AVAILABLE, reason="PySCF not available") def test_mc_cas_entropies_doublet(self): """Test MACIS CAS entropy evaluation on NO doublet with full active space.""" # Create NO molecule @@ -166,7 +174,7 @@ def test_mc_cas_entropies_singlet(self): """Test MACIS CAS entropy evaluation on H2O singlet with full active space.""" h2o = create_water_structure() - # use pyscf for ROHF + # use default SCF solver for RHF scf_solver = algorithms.create("scf_solver") scf_solver.settings().set("scf_type", "restricted") mc_calculator = algorithms.create("multi_configuration_calculator", "macis_cas") diff --git a/python/tests/test_measurement_data.py b/python/tests/test_measurement_data.py index f0a18b3e9a..fa7839ab8b 100644 --- a/python/tests/test_measurement_data.py +++ b/python/tests/test_measurement_data.py @@ -89,7 +89,7 @@ def test_measurement_data_json_file_io(): assert Path(filename).exists() # Verify file contents - with open(filename) as f: + with open(filename, encoding="utf-8") as f: loaded = json.load(f) assert "0" in loaded diff --git a/python/tests/test_noise_models.py b/python/tests/test_noise_models.py index 3ee1878293..35955d46d7 100644 --- a/python/tests/test_noise_models.py +++ b/python/tests/test_noise_models.py @@ -5,6 +5,9 @@ # Licensed under the MIT License. See LICENSE.txt in the project root for license information. # -------------------------------------------------------------------------------------------- +import os +import subprocess +import sys import tempfile from pathlib import Path @@ -24,16 +27,63 @@ def test_profile_dumping(simple_error_profile): """Test dumping quantum error profile to YAML.""" - with tempfile.NamedTemporaryFile() as tmp_file: - simple_error_profile.to_yaml_file(tmp_file.name) + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + filename = tmp_file.name + try: + simple_error_profile.to_yaml_file(filename) + finally: + Path(filename).unlink(missing_ok=True) def test_yaml_save_and_load_equivalence(simple_error_profile): """Test that a saved error profile gives the same values on loading.""" - with tempfile.NamedTemporaryFile() as tmp_file: - simple_error_profile.to_yaml_file(tmp_file.name) - loaded_profile = QuantumErrorProfile.from_yaml_file(tmp_file.name) + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + filename = tmp_file.name + try: + simple_error_profile.to_yaml_file(filename) + loaded_profile = QuantumErrorProfile.from_yaml_file(filename) assert simple_error_profile == loaded_profile + finally: + Path(filename).unlink(missing_ok=True) + + +def test_yaml_unicode_round_trip_and_stdout(tmp_path): + """Test Unicode YAML round-trip and subprocess stdout decoding.""" + script_path = tmp_path / "utf8_roundtrip.py" + yaml_path = tmp_path / "unicode.quantum_error_profile.yaml" + script_path.write_text( + "\n".join( + [ + "from pathlib import Path", + "import sys", + "from qdk_chemistry.data.noise_models import QuantumErrorProfile", + "", + "yaml_path = Path(sys.argv[1])", + "profile = QuantumErrorProfile(", + " name='φ-profile Å',", + " description='ΔE αβγ 你好',", + " errors={'h': {'depolarizing_error': 0.01}},", + ")", + "profile.to_yaml_file(yaml_path)", + "loaded = QuantumErrorProfile.from_yaml_file(yaml_path)", + "assert loaded.name == 'φ-profile Å'", + "assert loaded.description == 'ΔE αβγ 你好'", + "print('UTF-8 ok: φ Å ΔE αβγ 你好')", + ] + ), + encoding="utf-8", + ) + + result = subprocess.run( + [sys.executable, str(script_path), str(yaml_path)], + capture_output=True, + text=True, + encoding="utf-8", + check=True, + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, + ) + + assert "UTF-8 ok: φ Å ΔE αβγ 你好" in result.stdout def test_basis_gates(simple_error_profile): diff --git a/python/tests/test_orbitals.py b/python/tests/test_orbitals.py index c28aee4a4b..8b6ac2c598 100644 --- a/python/tests/test_orbitals.py +++ b/python/tests/test_orbitals.py @@ -9,6 +9,7 @@ import pickle import re import tempfile +from pathlib import Path import numpy as np import pytest @@ -203,8 +204,10 @@ def test_json_serialization(): ) # Test file-based serialization - with tempfile.NamedTemporaryFile(suffix=".orbitals.json") as tmp: + with tempfile.NamedTemporaryFile(suffix=".orbitals.json", delete=False) as tmp: filename = tmp.name + + try: orb_out.to_json_file(filename) orb_file = Orbitals.from_json_file(filename) @@ -222,6 +225,8 @@ def test_json_serialization(): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(filename).unlink(missing_ok=True) def test_hdf5_serialization(): @@ -232,29 +237,32 @@ def test_hdf5_serialization(): basis_set = create_test_basis_set(3, "test-hdf5-serialization") orb_out = Orbitals(coeffs, energies, overlap, basis_set) + with tempfile.NamedTemporaryFile(suffix=".orbitals.h5", delete=False) as tmp: + filename = tmp.name + try: - with tempfile.NamedTemporaryFile(suffix=".orbitals.h5") as tmp: - filename = tmp.name - orb_out.to_hdf5_file(filename) - - orb_in = Orbitals.from_hdf5_file(filename) - - coeffs_out_a, coeffs_out_b = orb_out.get_coefficients() - coeffs_in_a, coeffs_in_b = orb_in.get_coefficients() - assert np.allclose( - coeffs_out_a, - coeffs_in_a, - rtol=float_comparison_relative_tolerance, - atol=float_comparison_absolute_tolerance, - ) - assert np.allclose( - coeffs_out_b, - coeffs_in_b, - rtol=float_comparison_relative_tolerance, - atol=float_comparison_absolute_tolerance, - ) + orb_out.to_hdf5_file(filename) + + orb_in = Orbitals.from_hdf5_file(filename) + + coeffs_out_a, coeffs_out_b = orb_out.get_coefficients() + coeffs_in_a, coeffs_in_b = orb_in.get_coefficients() + assert np.allclose( + coeffs_out_a, + coeffs_in_a, + rtol=float_comparison_relative_tolerance, + atol=float_comparison_absolute_tolerance, + ) + assert np.allclose( + coeffs_out_b, + coeffs_in_b, + rtol=float_comparison_relative_tolerance, + atol=float_comparison_absolute_tolerance, + ) except RuntimeError as e: pytest.skip(f"HDF5 test skipped - {e!s}") + finally: + Path(filename).unlink(missing_ok=True) def test_complete_orbitals_workflow(): @@ -269,8 +277,10 @@ def test_complete_orbitals_workflow(): assert orb.get_num_molecular_orbitals() == 2 assert orb.is_restricted() - with tempfile.NamedTemporaryFile(suffix=".orbitals.json") as tmp_json: + with tempfile.NamedTemporaryFile(suffix=".orbitals.json", delete=False) as tmp_json: json_filename = tmp_json.name + + try: orb.to_json_file(json_filename) orb2 = Orbitals.from_json_file(json_filename) assert orb2.get_num_atomic_orbitals() == orb.get_num_atomic_orbitals() @@ -289,6 +299,8 @@ def test_complete_orbitals_workflow(): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(json_filename).unlink(missing_ok=True) def test_orbitals_file_io_generic(): @@ -300,9 +312,10 @@ def test_orbitals_file_io_generic(): orb = Orbitals(coeffs, energies, overlap, basis_set) # Test JSON file I/O - with tempfile.NamedTemporaryFile(suffix=".orbitals.json") as tmp_json: + with tempfile.NamedTemporaryFile(suffix=".orbitals.json", delete=False) as tmp_json: json_filename = tmp_json.name + try: # Save using generic method orb.to_file(json_filename, "json") @@ -328,11 +341,14 @@ def test_orbitals_file_io_generic(): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(json_filename).unlink(missing_ok=True) # Test HDF5 file I/O - with tempfile.NamedTemporaryFile(suffix=".orbitals.h5") as tmp_hdf5: + with tempfile.NamedTemporaryFile(suffix=".orbitals.h5", delete=False) as tmp_hdf5: hdf5_filename = tmp_hdf5.name + try: # Save using generic method orb.to_file(hdf5_filename, "hdf5") @@ -358,6 +374,8 @@ def test_orbitals_file_io_generic(): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(hdf5_filename).unlink(missing_ok=True) # Test unsupported file type with pytest.raises(RuntimeError, match="Unsupported file type"): @@ -376,9 +394,10 @@ def test_orbitals_hdf5_specific(): orb = Orbitals(coeffs, energies, overlap, basis_set) # Test HDF5 file I/O methods - with tempfile.NamedTemporaryFile(suffix=".orbitals.h5") as tmp_hdf5: + with tempfile.NamedTemporaryFile(suffix=".orbitals.h5", delete=False) as tmp_hdf5: hdf5_filename = tmp_hdf5.name + try: # Save using new method orb.to_hdf5_file(hdf5_filename) @@ -428,11 +447,14 @@ def test_orbitals_hdf5_specific(): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(hdf5_filename).unlink(missing_ok=True) # Test HDF5 file I/O methods work correctly - with tempfile.NamedTemporaryFile(suffix=".orbitals.h5") as tmp_hdf5: + with tempfile.NamedTemporaryFile(suffix=".orbitals.h5", delete=False) as tmp_hdf5: hdf5_filename = tmp_hdf5.name + try: # Save using method orb.to_hdf5_file(hdf5_filename) @@ -442,6 +464,8 @@ def test_orbitals_hdf5_specific(): # Check equality assert orb3.get_num_atomic_orbitals() == orb.get_num_atomic_orbitals() assert orb3.get_num_molecular_orbitals() == orb.get_num_molecular_orbitals() + finally: + Path(hdf5_filename).unlink(missing_ok=True) def test_orbitals_file_io_validation(): @@ -492,9 +516,10 @@ def test_orbitals_file_io_round_trip(): ) # Test JSON round-trip - with tempfile.NamedTemporaryFile(suffix=".orbitals.json") as tmp_json: + with tempfile.NamedTemporaryFile(suffix=".orbitals.json", delete=False) as tmp_json: json_filename = tmp_json.name + try: # Save and reload orb.to_json_file(json_filename) orb_json = Orbitals.from_json_file(json_filename) @@ -542,11 +567,14 @@ def test_orbitals_file_io_round_trip(): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(json_filename).unlink(missing_ok=True) # Test HDF5 round-trip - with tempfile.NamedTemporaryFile(suffix=".orbitals.h5") as tmp_hdf5: + with tempfile.NamedTemporaryFile(suffix=".orbitals.h5", delete=False) as tmp_hdf5: hdf5_filename = tmp_hdf5.name + try: # Save and reload orb.to_hdf5_file(hdf5_filename) orb_hdf5 = Orbitals.from_hdf5_file(hdf5_filename) @@ -594,6 +622,8 @@ def test_orbitals_file_io_round_trip(): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(hdf5_filename).unlink(missing_ok=True) def test_active_space_management(): @@ -672,8 +702,10 @@ def test_active_space_serialization(): ) # Test JSON serialization - with tempfile.NamedTemporaryFile(suffix=".orbitals.json") as tmp_json: + with tempfile.NamedTemporaryFile(suffix=".orbitals.json", delete=False) as tmp_json: json_filename = tmp_json.name + + try: orb.to_json_file(json_filename) # Load into a new object @@ -686,26 +718,31 @@ def test_active_space_serialization(): json_alpha, json_beta = orb_json.get_active_space_indices() assert np.array_equal(json_alpha, active_indices) assert np.array_equal(json_beta, active_indices) + finally: + Path(json_filename).unlink(missing_ok=True) # Test HDF5 serialization + with tempfile.NamedTemporaryFile(suffix=".orbitals.h5", delete=False) as tmp_hdf5: + hdf5_filename = tmp_hdf5.name + try: - with tempfile.NamedTemporaryFile(suffix=".orbitals.h5") as tmp_hdf5: - hdf5_filename = tmp_hdf5.name - orb.to_hdf5_file(hdf5_filename) + orb.to_hdf5_file(hdf5_filename) - # Load into a new object - orb_hdf5 = Orbitals.from_hdf5_file(hdf5_filename) + # Load into a new object + orb_hdf5 = Orbitals.from_hdf5_file(hdf5_filename) - # Check that active space was preserved - assert orb_hdf5.has_active_space() + # Check that active space was preserved + assert orb_hdf5.has_active_space() - # Verify active space indices - hdf5_alpha, hdf5_beta = orb_hdf5.get_active_space_indices() - assert np.array_equal(hdf5_alpha, active_indices) - assert np.array_equal(hdf5_beta, active_indices) + # Verify active space indices + hdf5_alpha, hdf5_beta = orb_hdf5.get_active_space_indices() + assert np.array_equal(hdf5_alpha, active_indices) + assert np.array_equal(hdf5_beta, active_indices) except RuntimeError as e: pytest.skip(f"HDF5 test skipped - {e!s}") + finally: + Path(hdf5_filename).unlink(missing_ok=True) def test_active_space_copy_assign(): diff --git a/python/tests/test_qubit_hamiltonian.py b/python/tests/test_qubit_hamiltonian.py index a27f8f45a9..6a3c307d28 100644 --- a/python/tests/test_qubit_hamiltonian.py +++ b/python/tests/test_qubit_hamiltonian.py @@ -502,7 +502,7 @@ def test_json_to_json_file_no_complex_error(self, tmp_path): qubit_hamiltonian.to_json_file(str(filename)) # Verify the file can be read - with open(filename) as f: + with open(filename, encoding="utf-8") as f: data = json.load(f) assert "pauli_strings" in data diff --git a/python/tests/test_readme_snippets.py b/python/tests/test_readme_snippets.py index e0df4e04ec..4bda1f10e1 100644 --- a/python/tests/test_readme_snippets.py +++ b/python/tests/test_readme_snippets.py @@ -12,6 +12,7 @@ # -------------------------------------------------------------------------------------------- import importlib.util +import os import re as _re import subprocess import sys @@ -79,25 +80,34 @@ def run_snippet(code: str, snippet_index: int, readme_path: Path, log_dir: Path """ with tempfile.TemporaryDirectory() as td: fn = Path(td) / f"snippet_{snippet_index}.py" + out_path: Path | None = None + err_path: Path | None = None fn.write_text(code, encoding="utf-8") if log_dir: log_dir.mkdir(parents=True, exist_ok=True) out_path = log_dir / f"snippet_{snippet_index}.stdout.txt" err_path = log_dir / f"snippet_{snippet_index}.stderr.txt" meta_path = log_dir / f"snippet_{snippet_index}.meta.txt" - meta_path.write_text(f"readme: {readme_path}\nsnippet_index: {snippet_index}\n") + meta_path.write_text(f"readme: {readme_path}\nsnippet_index: {snippet_index}\n", encoding="utf-8") try: proc = subprocess.run( - [sys.executable, "-B", str(fn)], cwd=td, capture_output=True, text=True, check=True, timeout=60 + [sys.executable, "-B", str(fn)], + cwd=td, + capture_output=True, + text=True, + encoding="utf-8", + check=True, + timeout=60, + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) except (subprocess.CalledProcessError, OSError) as exc: - if log_dir: - out_path.write_text(getattr(exc, "stdout", "") or "") - err_path.write_text(getattr(exc, "stderr", "") or "") + if out_path is not None and err_path is not None: + out_path.write_text(getattr(exc, "stdout", "") or "", encoding="utf-8") + err_path.write_text(getattr(exc, "stderr", "") or "", encoding="utf-8") return False - if log_dir: - out_path.write_text(proc.stdout or "") - err_path.write_text(proc.stderr or "") + if out_path is not None and err_path is not None: + out_path.write_text(proc.stdout or "", encoding="utf-8") + err_path.write_text(proc.stderr or "", encoding="utf-8") return True diff --git a/python/tests/test_sample_workflow.py b/python/tests/test_sample_workflow.py index e224700e1a..97b80b87a0 100644 --- a/python/tests/test_sample_workflow.py +++ b/python/tests/test_sample_workflow.py @@ -47,6 +47,13 @@ except ImportError: _HAS_JUPYTER_CLIENT = False +try: + import pyscf # noqa: F401 + + PYSCF_AVAILABLE = True +except ImportError: + PYSCF_AVAILABLE = False + # Environment variable to enable slow tests (including notebook e2e tests) _RUN_SLOW_TESTS = os.getenv("QDK_CHEMISTRY_RUN_SLOW_TESTS", "").lower() in {"1", "true", "yes"} @@ -251,6 +258,10 @@ def test_factory_list(): not QDK_CHEMISTRY_HAS_QISKIT, reason="Qiskit dependencies not available", ) +@pytest.mark.skipif( + not PYSCF_AVAILABLE, + reason="PySCF not available", +) def test_state_prep_energy(): """Test the examples/state_prep_energy.ipynb notebook executes without errors.""" notebook_path = EXAMPLES_DIR / "state_prep_energy.ipynb" @@ -279,6 +290,10 @@ def test_state_prep_energy(): not QDK_CHEMISTRY_HAS_QISKIT, reason="Qiskit dependencies not available", ) +@pytest.mark.skipif( + not PYSCF_AVAILABLE, + reason="PySCF not available", +) def test_qpe_stretched_n2(): """Test the examples/qpe_stretched_n2.ipynb notebook executes without errors.""" notebook_path = EXAMPLES_DIR / "qpe_stretched_n2.ipynb" diff --git a/python/tests/test_sample_workflow_utils.py b/python/tests/test_sample_workflow_utils.py index 1b43bfec07..03d18a59f7 100644 --- a/python/tests/test_sample_workflow_utils.py +++ b/python/tests/test_sample_workflow_utils.py @@ -21,8 +21,9 @@ def _run_workflow(cmd, cwd: Path) -> subprocess.CompletedProcess[str]: cwd=cwd, capture_output=True, text=True, + encoding="utf-8", check=False, - env=os.environ.copy(), + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) diff --git a/python/tests/test_settings.py b/python/tests/test_settings.py index 294850cdee..e4ba87bba9 100644 --- a/python/tests/test_settings.py +++ b/python/tests/test_settings.py @@ -13,6 +13,13 @@ import numpy as np import pytest +try: + import pyscf # noqa: F401 + + PYSCF_AVAILABLE = True +except ImportError: + PYSCF_AVAILABLE = False + from qdk_chemistry.data import ( AlgorithmRef, SettingNotFoundError, @@ -570,7 +577,7 @@ def test_json_file_operations(self): assert Path(json_file).exists() # Verify file content - with open(json_file) as f: + with open(json_file, encoding="utf-8") as f: data = json.load(f) assert data["method"] == "hf" assert data["max_iterations"] == 100 @@ -650,7 +657,7 @@ def test_generic_file_operations(self): assert Path(json_file).exists() # Verify content - with open(json_file) as f: + with open(json_file, encoding="utf-8") as f: data = json.load(f) assert data["method"] == "hf" assert data["max_iterations"] == 100 @@ -825,7 +832,7 @@ def test_consistency_between_generic_and_specific_methods(self): settings.to_json_file(json_file2) # Files should be identical - with open(json_file1) as f1, open(json_file2) as f2: + with open(json_file1, encoding="utf-8") as f1, open(json_file2, encoding="utf-8") as f2: assert f1.read() == f2.read() # Load with both methods and verify consistency @@ -1391,6 +1398,7 @@ def test_algorithm_ref_type_immutability(self): with pytest.raises(ValueError, match="cannot be changed"): s.set("inner_algo", AlgorithmRef("wrong_type", "whatever")) + @pytest.mark.skipif(not PYSCF_AVAILABLE, reason="PySCF not available") def test_algorithm_ref_with_kwargs(self): """AlgorithmRef constructed with kwargs stores them in .settings.""" ref = AlgorithmRef("scf_solver", "pyscf", max_iterations=200, method="dft") @@ -1406,6 +1414,7 @@ def test_algorithm_ref_without_kwargs(self): # In either case the construction must succeed. assert ref.settings is None or isinstance(ref.settings, Settings) + @pytest.mark.skipif(not PYSCF_AVAILABLE, reason="PySCF not available") def test_single_level_nesting_in_settings(self): """An AlgorithmRef with kwargs can be stored in Settings.""" s = _SettingsWithAlgorithmRef() diff --git a/python/tests/test_stability.py b/python/tests/test_stability.py index 6deff5fcde..90b7afb37d 100644 --- a/python/tests/test_stability.py +++ b/python/tests/test_stability.py @@ -8,6 +8,7 @@ import pickle import tempfile import warnings +from pathlib import Path import numpy as np import pytest @@ -104,8 +105,10 @@ def test_stability_result_json_serialization(self): ) # Test file-based serialization - with tempfile.NamedTemporaryFile(suffix=".stability_result.json") as tmp: + with tempfile.NamedTemporaryFile(suffix=".stability_result.json", delete=False) as tmp: filename = tmp.name + + try: result_out.to_json_file(filename) result_file = StabilityResult.from_json_file(filename) @@ -125,56 +128,62 @@ def test_stability_result_json_serialization(self): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(filename).unlink(missing_ok=True) def test_stability_result_hdf5_serialization(self): """Test HDF5 serialization and deserialization.""" result_out = self.create_test_stability_result() + with tempfile.NamedTemporaryFile(suffix=".stability_result.h5", delete=False) as tmp: + filename = tmp.name + try: - with tempfile.NamedTemporaryFile(suffix=".stability_result.h5") as tmp: - filename = tmp.name - result_out.to_hdf5_file(filename) - - result_in = StabilityResult.from_hdf5_file(filename) - - # Verify data preservation - assert result_in.is_internal_stable() == result_out.is_internal_stable() - assert result_in.is_external_stable() == result_out.is_external_stable() - assert np.allclose( - result_out.get_internal_eigenvalues(), - result_in.get_internal_eigenvalues(), - rtol=float_comparison_relative_tolerance, - atol=float_comparison_absolute_tolerance, - ) - assert np.allclose( - result_out.get_external_eigenvalues(), - result_in.get_external_eigenvalues(), - rtol=float_comparison_relative_tolerance, - atol=float_comparison_absolute_tolerance, - ) - assert np.allclose( - result_out.get_internal_eigenvectors(), - result_in.get_internal_eigenvectors(), - rtol=float_comparison_relative_tolerance, - atol=float_comparison_absolute_tolerance, - ) - assert np.allclose( - result_out.get_external_eigenvectors(), - result_in.get_external_eigenvectors(), - rtol=float_comparison_relative_tolerance, - atol=float_comparison_absolute_tolerance, - ) + result_out.to_hdf5_file(filename) + + result_in = StabilityResult.from_hdf5_file(filename) + + # Verify data preservation + assert result_in.is_internal_stable() == result_out.is_internal_stable() + assert result_in.is_external_stable() == result_out.is_external_stable() + assert np.allclose( + result_out.get_internal_eigenvalues(), + result_in.get_internal_eigenvalues(), + rtol=float_comparison_relative_tolerance, + atol=float_comparison_absolute_tolerance, + ) + assert np.allclose( + result_out.get_external_eigenvalues(), + result_in.get_external_eigenvalues(), + rtol=float_comparison_relative_tolerance, + atol=float_comparison_absolute_tolerance, + ) + assert np.allclose( + result_out.get_internal_eigenvectors(), + result_in.get_internal_eigenvectors(), + rtol=float_comparison_relative_tolerance, + atol=float_comparison_absolute_tolerance, + ) + assert np.allclose( + result_out.get_external_eigenvectors(), + result_in.get_external_eigenvectors(), + rtol=float_comparison_relative_tolerance, + atol=float_comparison_absolute_tolerance, + ) except RuntimeError as e: pytest.skip(f"HDF5 test skipped - {e!s}") + finally: + Path(filename).unlink(missing_ok=True) def test_stability_result_file_io_generic(self): """Test generic file I/O methods for StabilityResult.""" result = self.create_test_stability_result() # Test JSON file I/O - with tempfile.NamedTemporaryFile(suffix=".stability_result.json") as tmp_json: + with tempfile.NamedTemporaryFile(suffix=".stability_result.json", delete=False) as tmp_json: json_filename = tmp_json.name + try: # Save using generic method result.to_file(json_filename, "json") @@ -198,11 +207,14 @@ def test_stability_result_file_io_generic(self): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(json_filename).unlink(missing_ok=True) # Test HDF5 file I/O - with tempfile.NamedTemporaryFile(suffix=".stability_result.h5") as tmp_hdf5: + with tempfile.NamedTemporaryFile(suffix=".stability_result.h5", delete=False) as tmp_hdf5: hdf5_filename = tmp_hdf5.name + try: # Save using generic method result.to_file(hdf5_filename, "hdf5") @@ -226,6 +238,8 @@ def test_stability_result_file_io_generic(self): rtol=float_comparison_relative_tolerance, atol=float_comparison_absolute_tolerance, ) + finally: + Path(hdf5_filename).unlink(missing_ok=True) # Test unsupported file type with pytest.raises(ValueError, match="Unsupported file type"): @@ -273,12 +287,16 @@ def test_stability_result_empty_data_io(self): assert from_json.external_size() == empty_result.external_size() # Test file I/O with empty data - with tempfile.NamedTemporaryFile(suffix=".stability_result.json") as tmp: + with tempfile.NamedTemporaryFile(suffix=".stability_result.json", delete=False) as tmp: filename = tmp.name + + try: empty_result.to_json_file(filename) empty_from_file = StabilityResult.from_json_file(filename) assert empty_from_file.is_stable() assert empty_from_file.empty() # Method call, not property + finally: + Path(filename).unlink(missing_ok=True) def test_stability_result_pickle_serialization_and_repr(self): """Test pickle serialization support and string representation for StabilityResult.""" diff --git a/python/tests/test_verstraete_cirac.py b/python/tests/test_verstraete_cirac.py index 1fd61f8856..b06b4f1d56 100644 --- a/python/tests/test_verstraete_cirac.py +++ b/python/tests/test_verstraete_cirac.py @@ -5,6 +5,7 @@ # Licensed under the MIT License. See LICENSE.txt in the project root for license information. # -------------------------------------------------------------------------------------------- +import os import tempfile import h5py @@ -17,6 +18,8 @@ from qdk_chemistry.data import LatticeGraph, MajoranaMapping, QubitHamiltonian from qdk_chemistry.utils.model_hamiltonians import create_hubbard_hamiltonian, create_huckel_hamiltonian +_RUN_SLOW_TESTS = os.getenv("QDK_CHEMISTRY_RUN_SLOW_TESTS", "").lower() in {"1", "true", "yes"} + class TestVerstraeteCiracMapping: """Tests covering the Verstraete-Cirac mapping factory, dimensions, and properties.""" @@ -102,11 +105,16 @@ def test_hdf5_serialization(self) -> None: terms_orig = sorted(zip(qh_orig.pauli_strings, qh_orig.coefficients, strict=False)) # HDF5 Round-trip - with tempfile.NamedTemporaryFile(suffix=".h5") as f: - with h5py.File(f.name, "w") as hf: + # Use delete=False so h5py can open the file on Windows (no exclusive lock) + with tempfile.NamedTemporaryFile(suffix=".h5", delete=False) as f: + fname = f.name + try: + with h5py.File(fname, "w") as hf: mapping.to_hdf5(hf) - with h5py.File(f.name, "r") as hf: + with h5py.File(fname, "r") as hf: loaded_hdf5 = MajoranaMapping.from_hdf5(hf) + finally: + os.unlink(fname) assert loaded_hdf5.name == mapping.name assert loaded_hdf5.num_modes == mapping.num_modes assert loaded_hdf5.num_qubits == mapping.num_qubits @@ -145,6 +153,7 @@ def test_stabilizers_and_commutation(self, lattice_type: str, args: tuple, kwarg """Verify that stabilizers mutually commute and commute with mapped H for various lattices.""" def commute(p1: str, p2: str) -> bool: + """Return True if Pauli strings p1 and p2 commute.""" assert len(p1) == len(p2) anti_commutes = 0 for c1, c2 in zip(p1, p2, strict=False): @@ -211,6 +220,7 @@ def test_pauli_weight_scaling(self, lattice_type: str, kwargs: dict, max_allowed mapper = create("qubit_mapper", "qdk") def multiply_pauli_labels(p1: str, p2: str) -> str: + """Return the Pauli string resulting from the product of p1 and p2 (ignoring phase).""" table = { ("I", "I"): "I", ("I", "X"): "X", @@ -232,6 +242,7 @@ def multiply_pauli_labels(p1: str, p2: str) -> str: return "".join(table[(c1, c2)] for c1, c2 in zip(p1, p2, strict=False)) def get_expected_weight(p1: str, p2: str) -> int: + """Return the analytical upper bound on the weight of the product of p1 and p2.""" indices1 = [idx for idx, char in enumerate(p1) if char != "I"] indices2 = [idx for idx, char in enumerate(p2) if char != "I"] if not indices1 or not indices2: @@ -255,6 +266,7 @@ def check_local_plaquette( thresh: int, max_weight: int, ) -> None: + """Assert that the product weight of a local stabilizer pair does not exceed max_weight.""" indices1 = [idx for idx, char in enumerate(stabs_list[u_idx]) if char != "I"] indices2 = [idx for idx, char in enumerate(stabs_list[v_idx]) if char != "I"] if indices1 and indices2: @@ -266,6 +278,7 @@ def check_local_plaquette( ) def get_mst_edges(stabs_list: list[str], start_idx: int, count: int, root: int) -> list[tuple[int, int]]: + """Return MST edges over a subset of stabilizers using Prim's algorithm.""" if count <= 1: return [] @@ -391,6 +404,7 @@ def get_mst_edges(stabs_list: list[str], start_idx: int, count: int, root: int) class TestVerstraeteCiracSpectral: """Tests covering the spectral validation of the Verstraete-Cirac mapping.""" + @pytest.mark.skipif(not _RUN_SLOW_TESTS, reason="Skipping slow test. Set QDK_CHEMISTRY_RUN_SLOW_TESTS=1 to enable.") def test_spectral_validation_2x2_hubbard(self) -> None: """Compare eigenvalues of 2x2 periodic Fermi-Hubbard model under VC and JW mappings. diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 0000000000..f9dacd5c25 --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg-configuration.schema.json", + "default-registry": { + "kind": "git", + "repository": "https://github.com/microsoft/vcpkg", + "baseline": "52f5569a8e867859fac967c1e3f10ca0a8a3cc39" + }, + "overlay-ports": ["./vcpkg-overlay/ports"] +} diff --git a/vcpkg-overlay/ports/openblas/cmake-project-include.cmake b/vcpkg-overlay/ports/openblas/cmake-project-include.cmake new file mode 100644 index 0000000000..bfefbafb8d --- /dev/null +++ b/vcpkg-overlay/ports/openblas/cmake-project-include.cmake @@ -0,0 +1,66 @@ +# TARGET: The target architecture +# +# Originally, OpenBLAS tries to optimize for the host CPU unless +# - being given an explicit TARGET, and +# - CMAKE_CROSSCOMPILING, and +# - not building for uwp (aka WINDOWSSTORE) +# For this optimization, it runs 'getarch' and 'getarch_2nd' which it builds +# from source. The getarch executables are not built when not optimizing. +# +# Consequences: +# - The port must ensure that TARGET is set when cross compiling for a different CPU or OS. +# - The port must install getarch executables when possible. +# +# DYNAMIC_ARCH enables support "for multiple targets with runtime detection". +# (But not for MSVC, https://github.com/OpenMathLib/OpenBLAS/wiki/How-to-use-OpenBLAS-in-Microsoft-Visual-Studio#cmake-and-visual-studio.) +# The OpenBLAS README.md suggests that this shall be used with TARGET being +# set "to the oldest model you expect to encounter". This affects "all the +# common code in the library". + +set(need_target 0) +if(NOT "${TARGET}" STREQUAL "") + message(STATUS "TARGET: ${TARGET} (user-defined)") +elseif(DYNAMIC_ARCH) + message(STATUS "DYNAMIC_ARCH: ${DYNAMIC_ARCH}") + set(need_target 1) # for C +elseif(CMAKE_CROSSCOMPILING AND NOT GETARCH_BINARY_DIR) + set(need_target 1) # for C and for optimized kernel +else() + message(STATUS "TARGET: (OpenBLAS getarch/getarch_2nd)") +endif() + +if(need_target) + set(target_default "GENERIC") + if(MSVC) + # "does not support the dialect of assembly used in the cpu-specific optimized files" + # https://github.com/OpenMathLib/OpenBLAS/wiki/How-to-use-OpenBLAS-in-Microsoft-Visual-Studio#cmake-and-visual-studio + elseif(VCPKG_TARGET_ARCHITECTURE MATCHES "^x64|^x86") + set(target_default "ATOM") + elseif(VCPKG_TARGET_ARCHITECTURE MATCHES "^arm64") + set(target_default "ARMV8") + elseif(VCPKG_TARGET_ARCHITECTURE MATCHES "^arm") + set(target_default "ARMV7") + endif() + set(TARGET "${target_default}" CACHE STRING "") + message(STATUS "TARGET: ${TARGET}") +endif() + +# NUM_THREADS: The number of threads expected to be used. +# +# This setting affects both the configuration with USE_THREAD enabled +# (multithreaded OpenBLAS) and disabled (multithreaded access to OpenBLAS). +# This shouldn't be set too low for generic packages. But it comes with a +# memory footprint. + +if(DEFINED NUM_THREADS) + message(STATUS "NUM_THREADS: ${NUM_THREADS} (user-defined)") +elseif(EMSCRIPTEN) + message(STATUS "NUM_THREADS: (for EMSCRIPTEN)") +elseif(need_target) + set(num_threads_default 24) + if(ANDROID OR IOS) + set(num_threads_default 8) + endif() + set(NUM_THREADS "${num_threads_default}" CACHE STRING "") + message(STATUS "NUM_THREADS: ${NUM_THREADS}") +endif() diff --git a/vcpkg-overlay/ports/openblas/disable-testing.diff b/vcpkg-overlay/ports/openblas/disable-testing.diff new file mode 100644 index 0000000000..5c01a519b4 --- /dev/null +++ b/vcpkg-overlay/ports/openblas/disable-testing.diff @@ -0,0 +1,20 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 2006604..c9fedb9 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -323,6 +323,7 @@ if (USE_THREAD) + endif() + endif() + ++if(BUILD_TESTING) + #if (MSVC OR NOT NOFORTRAN) + if (NOT NO_CBLAS) + if (NOT ONLY_CBLAS) +@@ -348,6 +349,7 @@ endif() + if (CPP_THREAD_SAFETY_TEST OR CPP_THREAD_SAFETY_GEMV) + add_subdirectory(cpp_thread_test) + endif() ++endif() + + if (NOT FIXED_LIBNAME) + set_target_properties(${OpenBLAS_LIBS} PROPERTIES diff --git a/vcpkg-overlay/ports/openblas/getarch.diff b/vcpkg-overlay/ports/openblas/getarch.diff new file mode 100644 index 0000000000..d91e5e106d --- /dev/null +++ b/vcpkg-overlay/ports/openblas/getarch.diff @@ -0,0 +1,73 @@ +diff --git a/cmake/prebuild.cmake b/cmake/prebuild.cmake +index 609fbe2..daeb25c 100644 +--- a/cmake/prebuild.cmake ++++ b/cmake/prebuild.cmake +@@ -95,7 +95,7 @@ else () + endif () + + # Cannot run getarch on target if we are cross-compiling +-if (DEFINED CORE AND CMAKE_CROSSCOMPILING AND NOT (${HOST_OS} STREQUAL "WINDOWSSTORE")) ++if(CMAKE_CROSSCOMPILING AND NOT DEFINED GETARCH_BINARY_DIR) + # Write to config as getarch would + if (DEFINED TARGET_CORE) + set(TCORE ${TARGET_CORE}) +@@ -1373,7 +1373,11 @@ endif () + file(MAKE_DIRECTORY ${TARGET_CONF_DIR}) + file(RENAME ${TARGET_CONF_TEMP} "${TARGET_CONF_DIR}/${TARGET_CONF}") + +-else(NOT CMAKE_CROSSCOMPILING) ++else() ++ if(NOT CMAKE_CROSSCOMPILING) ++ set(GETARCH_BINARY_DIR "${PROJECT_BINARY_DIR}") ++ endif() ++ + # compile getarch + set(GETARCH_SRC + ${PROJECT_SOURCE_DIR}/getarch.c +@@ -1420,6 +1424,7 @@ else(NOT CMAKE_CROSSCOMPILING) + if (NOT ${GETARCH_RESULT}) + MESSAGE(FATAL_ERROR "Compiling getarch failed ${GETARCH_LOG}") + endif () ++ install(PROGRAMS "${PROJECT_BINARY_DIR}/${GETARCH_BIN}" DESTINATION bin) + endif () + unset (HAVE_AVX2) + unset (HAVE_AVX) +@@ -1439,8 +1444,8 @@ else(NOT CMAKE_CROSSCOMPILING) + message(STATUS "Running getarch") + + # use the cmake binary w/ the -E param to run a shell command in a cross-platform way +-execute_process(COMMAND "${PROJECT_BINARY_DIR}/${GETARCH_BIN}" 0 OUTPUT_VARIABLE GETARCH_MAKE_OUT) +-execute_process(COMMAND "${PROJECT_BINARY_DIR}/${GETARCH_BIN}" 1 OUTPUT_VARIABLE GETARCH_CONF_OUT) ++execute_process(COMMAND "${GETARCH_BINARY_DIR}/${GETARCH_BIN}" 0 OUTPUT_VARIABLE GETARCH_MAKE_OUT) ++execute_process(COMMAND "${GETARCH_BINARY_DIR}/${GETARCH_BIN}" 1 OUTPUT_VARIABLE GETARCH_CONF_OUT) + + message(STATUS "GETARCH results:\n${GETARCH_MAKE_OUT}") + +@@ -1463,11 +1468,12 @@ execute_process(COMMAND "${PROJECT_BINARY_DIR}/${GETARCH_BIN}" 1 OUTPUT_VARIABLE + if (NOT ${GETARCH2_RESULT}) + MESSAGE(FATAL_ERROR "Compiling getarch_2nd failed ${GETARCH2_LOG}") + endif () ++ install(PROGRAMS "${PROJECT_BINARY_DIR}/${GETARCH2_BIN}" DESTINATION bin) + endif () + + # use the cmake binary w/ the -E param to run a shell command in a cross-platform way +-execute_process(COMMAND "${PROJECT_BINARY_DIR}/${GETARCH2_BIN}" 0 OUTPUT_VARIABLE GETARCH2_MAKE_OUT) +-execute_process(COMMAND "${PROJECT_BINARY_DIR}/${GETARCH2_BIN}" 1 OUTPUT_VARIABLE GETARCH2_CONF_OUT) ++execute_process(COMMAND "${GETARCH_BINARY_DIR}/${GETARCH2_BIN}" 0 OUTPUT_VARIABLE GETARCH2_MAKE_OUT) ++execute_process(COMMAND "${GETARCH_BINARY_DIR}/${GETARCH2_BIN}" 1 OUTPUT_VARIABLE GETARCH2_CONF_OUT) + + # append config data from getarch_2nd to the TARGET file and read in CMake vars + file(APPEND "${TARGET_CONF_TEMP}" ${GETARCH2_CONF_OUT}) +diff --git a/cmake/system.cmake b/cmake/system.cmake +index eae7436..b2a6da7 100644 +--- a/cmake/system.cmake ++++ b/cmake/system.cmake +@@ -13,7 +13,7 @@ if(CMAKE_CROSSCOMPILING AND NOT DEFINED TARGET) + set(TARGET "ARMV8") + elseif(ARM) + set(TARGET "ARMV7") # TODO: Ask compiler which arch this is +- else() ++ elseif(NOT DEFINED GETARCH_BINARY_DIR) + message(FATAL_ERROR "When cross compiling, a TARGET is required.") + endif() + endif() diff --git a/vcpkg-overlay/ports/openblas/portfile.cmake b/vcpkg-overlay/ports/openblas/portfile.cmake new file mode 100644 index 0000000000..80fbb9597a --- /dev/null +++ b/vcpkg-overlay/ports/openblas/portfile.cmake @@ -0,0 +1,77 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO OpenMathLib/OpenBLAS + REF "v${VERSION}" + SHA512 046316b4297460bffca09c890ecad17ea39d8b3db92ff445d03b547dd551663d37e40f38bce8ae11e2994374ff01e622b408da27aa8e40f4140185ee8f001a60 + HEAD_REF develop + PATCHES + disable-testing.diff + getarch.diff + system-check-msvc.diff +) + +vcpkg_check_features(OUT_FEATURE_OPTIONS OPTIONS + FEATURES + threads USE_THREAD + simplethread USE_SIMPLE_THREADED_LEVEL3 + dynamic-arch DYNAMIC_ARCH +) + +# If not explicitly configured for a cross build, OpenBLAS wants to run +# getarch executables in order to optimize for the target. +# Adapting this to vcpkg triplets: +# - install-getarch.diff introduces and uses GETARCH_BINARY_DIR, +# - architecture and system name are required to match for GETARCH_BINARY_DIR, but +# - uwp (aka WindowsStore) may run windows getarch. +string(REPLACE "WindowsStore_" "_" SYSTEM_KEY "${VCPKG_CMAKE_SYSTEM_NAME}_${VCPKG_TARGET_ARCHITECTURE}") +set(GETARCH_BINARY_DIR "${CURRENT_HOST_INSTALLED_DIR}/manual-tools/${PORT}/${SYSTEM_KEY}") +if(EXISTS "${GETARCH_BINARY_DIR}") + message(STATUS "OpenBLAS cross build, but may use ${PORT}:${HOST_TRIPLET} getarch") + list(APPEND OPTIONS "-DGETARCH_BINARY_DIR=${GETARCH_BINARY_DIR}") +elseif(VCPKG_CROSSCOMPILING) + message(STATUS "OpenBLAS cross build, may not be able to use getarch") +else() + message(STATUS "OpenBLAS native build") +endif() + +if(VCPKG_TARGET_IS_EMSCRIPTEN) + # Only the riscv64 kernel with riscv64_generic target is supported. + # Cf. https://github.com/OpenMathLib/OpenBLAS/issues/3640#issuecomment-1144029630 et al. + list(APPEND OPTIONS + -DEMSCRIPTEN_SYSTEM_PROCESSOR=riscv64 + -DTARGET=RISCV64_GENERIC + ) +endif() + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + ${OPTIONS} + "-DCMAKE_PROJECT_INCLUDE=${CURRENT_PORT_DIR}/cmake-project-include.cmake" + -DBUILD_TESTING=OFF + # QDK overlay change: BUILD_WITHOUT_LAPACK=OFF to include LAPACK routines. + # C_LAPACK=ON uses OpenBLAS's embedded C-translated LAPACK (no Fortran needed). + -DBUILD_WITHOUT_LAPACK=OFF + -DNOFORTRAN=ON + -DC_LAPACK=ON + MAYBE_UNUSED_VARIABLES + GETARCH_BINARY_DIR +) + +vcpkg_cmake_install() +vcpkg_copy_pdbs() +vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/OpenBLAS) +vcpkg_fixup_pkgconfig() + +# Required from native builds, optional from cross builds. +if(NOT VCPKG_CROSSCOMPILING OR EXISTS "${CURRENT_PACKAGES_DIR}/bin/getarch${VCPKG_TARGET_EXECUTABLE_SUFFIX}") + vcpkg_copy_tools( + TOOL_NAMES getarch getarch_2nd + DESTINATION "${CURRENT_PACKAGES_DIR}/manual-tools/${PORT}/${SYSTEM_KEY}" + AUTO_CLEAN + ) +endif() + +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include" "${CURRENT_PACKAGES_DIR}/debug/share") + +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") diff --git a/vcpkg-overlay/ports/openblas/system-check-msvc.diff b/vcpkg-overlay/ports/openblas/system-check-msvc.diff new file mode 100644 index 0000000000..f1c395e1a8 --- /dev/null +++ b/vcpkg-overlay/ports/openblas/system-check-msvc.diff @@ -0,0 +1,21 @@ +diff --git a/cmake/system_check.cmake b/cmake/system_check.cmake +index e94497a..d884727 100644 +--- a/cmake/system_check.cmake ++++ b/cmake/system_check.cmake +@@ -36,6 +36,16 @@ if(CMAKE_CL_64 OR MINGW64) + else() + set(X86_64 1) + endif() ++elseif(MSVC) ++ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") ++ set(X86_64 1) ++ elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM") ++ set(ARM 1) ++ elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64") ++ set(ARM64 1) ++ else() ++ set(X86 1) ++ endif() + elseif(MINGW OR (MSVC AND NOT CMAKE_CROSSCOMPILING)) + set(X86 1) + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc.*|power.*|Power.*" OR (CMAKE_SYSTEM_NAME MATCHES "Darwin" AND CMAKE_OSX_ARCHITECTURES MATCHES "ppc.*")) diff --git a/vcpkg-overlay/ports/openblas/vcpkg.json b/vcpkg-overlay/ports/openblas/vcpkg.json new file mode 100644 index 0000000000..9a22b2e473 --- /dev/null +++ b/vcpkg-overlay/ports/openblas/vcpkg.json @@ -0,0 +1,50 @@ +{ + "name": "openblas", + "version": "0.3.29", + "description": "OpenBLAS is an optimized BLAS library based on GotoBLAS2 1.13 BSD version.", + "homepage": "https://github.com/OpenMathLib/OpenBLAS", + "license": "BSD-3-Clause", + "dependencies": [ + { + "name": "openblas", + "host": true + }, + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "features": { + "dynamic-arch": { + "description": "Support for multiple targets in a single library", + "supports": "!windows | mingw" + }, + "simplethread": { + "description": [ + "Use simple thread safety for level3 functions", + "Alternative to serialization of concurrent access to parallelized level3 functions." + ], + "dependencies": [ + { + "name": "openblas", + "features": [ + "threads" + ] + } + ] + }, + "threads": { + "description": "Enable multi-threading", + "dependencies": [ + { + "name": "pthreads", + "platform": "!windows" + } + ] + } + } +} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000000..657dd01dc2 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "name": "qdk-chemistry", + "description": "Quantum Development Kit - Chemistry Library build dependencies", + "dependencies": [ + "eigen3", + { + "name": "hdf5", + "features": ["cpp"] + }, + "boost-headers", + "boost-container-hash", + "boost-dynamic-bitset", + "boost-sort", + "catch2", + "gtest", + "nlohmann-json", + "spdlog", + "fmt", + "openblas" + ] +}