diff --git a/.github/workflows/windows-installer-version.yml b/.github/workflows/windows-installer-version.yml new file mode 100644 index 00000000..2f30b29d --- /dev/null +++ b/.github/workflows/windows-installer-version.yml @@ -0,0 +1,28 @@ +name: Windows installer version + +on: + pull_request: + paths: + - packaging/windows/** + - .github/workflows/windows-installer-version.yml + push: + branches: [master] + paths: + - packaging/windows/** + - .github/workflows/windows-installer-version.yml + workflow_dispatch: + +permissions: + contents: read + +jobs: + version: + runs-on: windows-2022 + steps: + - uses: actions/checkout@v5 + - name: Install NSIS + shell: pwsh + run: choco install nsis --no-progress -y + - name: Check version ordering and installer metadata + shell: pwsh + run: ./packaging/windows/test-version.ps1 -BuildInstaller diff --git a/README.md b/README.md index 9d98f74e..4a620ed8 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ 当你需要了解或操作 SPONGE 时: -- 如果你是人类用户:请阅读 `docs` 文件夹中的开发与使用文档。 +- 如果你是人类用户:请从[快速入门](docs/getting-started.md)和 + [输入参数参考](docs/input-reference/README.md)开始。 - 如果你是 AI 助手: - 若用于使用 SPONGE:将本项目的 `skills` 安装到你的全局 skills 目录,例如 `~/.claude/skills` 或 `~/.agents/skills`。 - 若用于开发 SPONGE:将你的项目级 skills 目录链接到本项目的 `skills`,例如 `.claude/skills` 或 `.agents/skills`。 diff --git a/README_en.md b/README_en.md index ef7fa54c..8957f21e 100644 --- a/README_en.md +++ b/README_en.md @@ -20,7 +20,8 @@ Official website: https://spongemm.cn When you need to understand or work with SPONGE: -- If you are a human user, read the development and usage documents in the `docs` folder. +- If you are a human user, start with the [Getting Started guide](docs/getting-started.md) + and the [Input File Reference](docs/input-reference/README.md). - If you are an AI assistant: - For using SPONGE, install this project's `skills` into your global skills directory, for example `~/.claude/skills` or `~/.agents/skills`. - For developing SPONGE, link your project-level skills directory to this project's `skills`, for example `.claude/skills` or `.agents/skills`. diff --git a/SPONGE/NO_PBC/generalized_Born.cpp b/SPONGE/NO_PBC/generalized_Born.cpp index 218b808d..028fe003 100644 --- a/SPONGE/NO_PBC/generalized_Born.cpp +++ b/SPONGE/NO_PBC/generalized_Born.cpp @@ -285,7 +285,6 @@ void GENERALIZED_BORN_INFORMATION::Initial(CONTROLLER* controller, float cutoff, else { controller->printf(" Error: GB need radii and scaled factor"); - getchar(); exit(1); } diff --git a/SPONGE/control.cpp b/SPONGE/control.cpp index bea5b5fd..eba9b2d7 100644 --- a/SPONGE/control.cpp +++ b/SPONGE/control.cpp @@ -1,7 +1,6 @@ #include "control.h" -#define SPONGE_CODENAME "2026-04-01 April Fools' Day" -#define SPONGE_VERSION "v" SPONGE_VERSION_STR " " SPONGE_CODENAME +#define SPONGE_VERSION "v" SPONGE_VERSION_STR static const char* SPONGE_ASCII_ART = R"( ____ ____ ___ _ _ ____ _____ / ___|| _ \ / _ \| \ | |/ ___| ____| diff --git a/SPONGE/virtual_atoms/virtual_atom_math.h b/SPONGE/virtual_atoms/virtual_atom_math.h index 341eb77a..443291b5 100644 --- a/SPONGE/virtual_atoms/virtual_atom_math.h +++ b/SPONGE/virtual_atoms/virtual_atom_math.h @@ -13,9 +13,9 @@ __device__ __forceinline__ void Virtual_Atom_Add_Source_Force( } __host__ __device__ __forceinline__ VECTOR -Virtual_Atom_Type_0_Position(VECTOR source, float h_double) +Virtual_Atom_Type_0_Position(VECTOR source, float h) { - source.z = 2.0f * h_double - source.z; + source.z = 2.0f * h - source.z; return source; } diff --git a/SPONGE/virtual_atoms/virtual_atoms.cpp b/SPONGE/virtual_atoms/virtual_atoms.cpp index 7a472ecf..ad4722d8 100644 --- a/SPONGE/virtual_atoms/virtual_atoms.cpp +++ b/SPONGE/virtual_atoms/virtual_atoms.cpp @@ -18,8 +18,7 @@ static __global__ void v0_Coordinate_Refresh(const int virtual_numbers, VIRTUAL_TYPE_0 v_temp = v_info[i]; int atom_v = v_temp.virtual_atom; int atom_1 = v_temp.from_1; - crd[atom_v] = - Virtual_Atom_Type_0_Position(crd[atom_1], v_temp.h_double); + crd[atom_v] = Virtual_Atom_Type_0_Position(crd[atom_1], v_temp.h); } } @@ -592,7 +591,7 @@ void VIRTUAL_INFORMATION::Initial(CONTROLLER* controller, temp_vl->v0_info.h_virtual_type_0[count0[this_level]] .from_1 = record.from[0]; temp_vl->v0_info.h_virtual_type_0[count0[this_level]] - .h_double = 2 * record.parameter[0]; + .h = record.parameter[0]; count0[this_level]++; break; diff --git a/SPONGE/virtual_atoms/virtual_atoms.h b/SPONGE/virtual_atoms/virtual_atoms.h index 0a0b1934..9fc2e45f 100644 --- a/SPONGE/virtual_atoms/virtual_atoms.h +++ b/SPONGE/virtual_atoms/virtual_atoms.h @@ -13,16 +13,13 @@ // own source. Repeated source indices alone are not self-dependencies. // // Virtual atom type 0: reflection in a plane normal to the z axis. -// Intended: x_v = x_1, y_v = y_1, z_v = 2 * h - z_1. -// NOTE: the loader stores h_double = 2 * h, but the current position -// helper computes z_v = 2 * h_double - z_1 (i.e. 4 * h - z_1). -// This legacy factor-of-two discrepancy is not corrected here. +// Position: x_v = x_1, y_v = y_1, z_v = 2 * h - z_1. // Force: F_1 += (F_v.x, F_v.y, -F_v.z). struct VIRTUAL_TYPE_0 { int virtual_atom; int from_1; - float h_double; + float h; }; struct VIRTUAL_TYPE_0_INFROMATION diff --git a/benchmarks/bundled_io/tests/test_bundled_io_ab_production.py b/benchmarks/bundled_io/tests/test_bundled_io_ab_production.py index 8908f118..49f9ae34 100644 --- a/benchmarks/bundled_io/tests/test_bundled_io_ab_production.py +++ b/benchmarks/bundled_io/tests/test_bundled_io_ab_production.py @@ -19594,11 +19594,10 @@ def _canonicalize_virtual_atom_positions( for record in records_by_level[level]: source = [coordinates[index] for index in record.source_atoms] if record.kind == 0: - h_double = 2.0 * record.parameters[0] coordinates[record.atom] = [ source[0][0], source[0][1], - 2.0 * h_double - source[0][2], + 2.0 * record.parameters[0] - source[0][2], ] elif record.kind == 1: delta = _lower_triangular_periodic_displacement( diff --git a/benchmarks/validation/nopbc/tests/test_virtual_atoms.py b/benchmarks/validation/nopbc/tests/test_virtual_atoms.py index f5186ed9..edb9b1b8 100644 --- a/benchmarks/validation/nopbc/tests/test_virtual_atoms.py +++ b/benchmarks/validation/nopbc/tests/test_virtual_atoms.py @@ -142,7 +142,7 @@ def test_type0_virtual_atom_reflects_force_and_clears_target( output_coordinates, forces = _run(case_dir, len(coordinates), mpi_np) np.testing.assert_allclose( - output_coordinates[1], (100.0, 100.0, 5.0), atol=2.0e-5 + output_coordinates[1], (100.0, 100.0, 1.0), atol=2.0e-5 ) np.testing.assert_allclose(forces[1], 0.0, atol=1.0e-7) virtual_force = -forces[2] diff --git a/docs/getting-started.md b/docs/getting-started.md index d5e713c0..0f13064a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -94,11 +94,13 @@ dt = 0.002 cutoff = 8.0 default_in_file_prefix = "WAT" constrain_mode = "SHAKE" -thermostat = "middle_langevin" -thermostat_tau = 0.1 -thermostat_seed = 2026 target_temperature = 300.0 write_information_interval = 1000 + +[thermostat] +mode = "middle_langevin" +tau = 0.1 +seed = 2026 ``` Use the command that matches how SPONGE was installed. diff --git a/docs/input-reference/README.md b/docs/input-reference/README.md index 664a2d99..92756196 100644 --- a/docs/input-reference/README.md +++ b/docs/input-reference/README.md @@ -1,6 +1,8 @@ # Input File Reference -SPONGE input files use TOML format. The default filename is `mdin.spg.toml`, specified via the `-mdin` flag: +SPONGE automatically reads `mdin.spg.toml` from the working directory. If it +is absent, the legacy `mdin.txt` input is also recognized. Use `-mdin` to +select another input file explicitly: ```bash SPONGE -mdin mdin.spg.toml diff --git a/packaging/windows/installer.nsi b/packaging/windows/installer.nsi index 08c8a877..c12b3a44 100644 --- a/packaging/windows/installer.nsi +++ b/packaging/windows/installer.nsi @@ -11,6 +11,7 @@ ; ---------- Build-time defines (set by installer.ps1) ---------- ; !define PRODUCT_VERSION "2.0.0.0" +; !define DISPLAY_VERSION "2.0.0" ; !define VARIANT "CPU" ; !define STAGE_DIR "release-artifacts\nsis\stage" ; !define OUTPUT_PATH "release-artifacts\nsis\SPONGE-CPU-v2.0.0-installer.exe" @@ -23,7 +24,7 @@ !define UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\SPONGE-${VARIANT}" ; ---------- Installer attributes ---------- -Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +Name "${PRODUCT_NAME} ${DISPLAY_VERSION}" OutFile "${OUTPUT_PATH}" InstallDir "$PROGRAMFILES64\${INSTALL_DIR}" InstallDirRegKey HKLM "${REG_KEY}" "InstallDir" @@ -34,7 +35,7 @@ Unicode true VIProductVersion "${PRODUCT_VERSION}" VIAddVersionKey "FileVersion" "${PRODUCT_VERSION}" VIAddVersionKey "ProductName" "${PRODUCT_NAME}" -VIAddVersionKey "ProductVersion" "${PRODUCT_VERSION}" +VIAddVersionKey "ProductVersion" "${DISPLAY_VERSION}" VIAddVersionKey "CompanyName" "SPONGE Development Team" VIAddVersionKey "FileDescription" "${PRODUCT_NAME} Installer" VIAddVersionKey "LegalCopyright" "Copyright (c) 2022-2026 SPONGE Development Team" @@ -99,7 +100,7 @@ Section "$(SEC_MAIN_NAME)" SecMain WriteRegStr HKLM "${UNINSTALL_KEY}" \ "Publisher" "SPONGE Development Team" WriteRegStr HKLM "${UNINSTALL_KEY}" \ - "DisplayVersion" "${PRODUCT_VERSION}" + "DisplayVersion" "${DISPLAY_VERSION}" WriteRegDWORD HKLM "${UNINSTALL_KEY}" \ "NoModify" 1 WriteRegDWORD HKLM "${UNINSTALL_KEY}" \ diff --git a/packaging/windows/installer.ps1 b/packaging/windows/installer.ps1 index d627de61..7dfbf36b 100644 --- a/packaging/windows/installer.ps1 +++ b/packaging/windows/installer.ps1 @@ -13,30 +13,45 @@ $PSNativeCommandUseErrorActionPreference = $true function Get-ProductVersion { param([string]$TagName) - if ($TagName -match '^v(\d+)\.(\d+)\.(\d+)$') { - return "$($Matches[1]).$($Matches[2]).$($Matches[3]).0" + if (-not $TagName) { + return "0.0.0.0" } - if ($TagName -match '^v(\d+)\.(\d+)\.(\d+)(alpha|beta|rc)(\d+)$') { - $major = [int]$Matches[1] - $minor = [int]$Matches[2] - $patch = [int]$Matches[3] + if ($TagName -cnotmatch '\Av(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-(alpha|beta|rc)\.(0|[1-9][0-9]*))?\z') { + throw "Unsupported release tag: $TagName" + } + + $parts = foreach ($index in 1..3) { + $value = 0 + if (-not [int]::TryParse($Matches[$index], [ref]$value) -or $value -gt 65535) { + throw "Release version components must be in 0..65535: $TagName" + } + $value + } + + # Disjoint prerelease ranges preserve alpha < beta < rc < stable. + # The revision is a 16-bit value; reserve its maximum for stable releases. + $revision = 65535 + if ($Matches[4]) { $channel = $Matches[4] - $number = [int]$Matches[5] + $number = 0 + if (-not [int]::TryParse($Matches[5], [ref]$number) -or $number -gt 19999) { + throw "Prerelease number must be in 0..19999: $TagName" + } $offset = switch ($channel) { "alpha" { 0 } - "beta" { 20 } - "rc" { 40 } - default { 0 } + "beta" { 20000 } + "rc" { 40000 } } - - return "$major.$minor.$patch.$($offset + $number)" + $revision = $offset + $number } - return "2.0.0.0" + return "$($parts -join '.').$revision" } +# Validate before staging files or creating output directories. +$productVersion = Get-ProductVersion $Tag $repoRoot = Resolve-Path "." $envPrefix = Join-Path $repoRoot ".pixi\envs\$EnvName" $exeDir = Join-Path $envPrefix "bin" @@ -76,7 +91,7 @@ foreach ($dllDir in @($exeDir, $runtimeBinDir)) { # Resolve paths $tagLabel = if ($Tag) { $Tag } else { "dev" } -$productVersion = Get-ProductVersion $Tag +$displayVersion = if ($Tag) { $Tag.Substring(1) } else { "dev" } $variantUpper = $Variant.ToUpper() $outputPath = Join-Path (Resolve-Path $OutputDir) "SPONGE-$variantUpper-$tagLabel-installer.exe" $nsiFullPath = Join-Path $repoRoot $NsiPath @@ -102,6 +117,7 @@ $utf8Bom = New-Object System.Text.UTF8Encoding $true # Build installer & $makensis ` /DPRODUCT_VERSION="$productVersion" ` + /DDISPLAY_VERSION="$displayVersion" ` /DVARIANT="$variantUpper" ` /DSTAGE_DIR="$stageFullPath" ` /DOUTPUT_PATH="$outputPath" ` diff --git a/packaging/windows/test-version.ps1 b/packaging/windows/test-version.ps1 new file mode 100644 index 00000000..d4493eb2 --- /dev/null +++ b/packaging/windows/test-version.ps1 @@ -0,0 +1,95 @@ +param([switch]$BuildInstaller) + +$ErrorActionPreference = "Stop" +$PSNativeCommandUseErrorActionPreference = $true +$installerPath = Join-Path $PSScriptRoot "installer.ps1" + +# Load only the version function, without running the packaging entry point. +$parseErrors = $null +$ast = [System.Management.Automation.Language.Parser]::ParseFile( + $installerPath, [ref]$null, [ref]$parseErrors +) +if ($parseErrors.Count) { throw "Installer parse errors: $parseErrors" } +$function = $ast.Find({ + param($node) + $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and + $node.Name -eq "Get-ProductVersion" +}, $false) +if (-not $function) { throw "Get-ProductVersion not found" } +. ([scriptblock]::Create($function.Extent.Text)) + +$cases = [ordered]@{ + "" = "0.0.0.0" + "v0.0.0" = "0.0.0.65535" + "v2.0.0-alpha.0" = "2.0.0.0" + "v2.0.0-alpha.21" = "2.0.0.21" + "v2.0.0-alpha.19999" = "2.0.0.19999" + "v2.0.0-beta.0" = "2.0.0.20000" + "v2.0.0-beta.1" = "2.0.0.20001" + "v2.0.0-beta.2" = "2.0.0.20002" + "v2.0.0-beta.19999" = "2.0.0.39999" + "v2.0.0-rc.0" = "2.0.0.40000" + "v2.0.0-rc.19999" = "2.0.0.59999" + "v2.0.0" = "2.0.0.65535" + "v2.0.1-alpha.0" = "2.0.1.0" + "v2.1.0-alpha.0" = "2.1.0.0" + "v3.0.0-alpha.0" = "3.0.0.0" + "v65535.65535.65535" = "65535.65535.65535.65535" +} +$previous = $null +foreach ($case in $cases.GetEnumerator()) { + $actual = Get-ProductVersion $case.Key + if ($actual -cne $case.Value) { + throw "$($case.Key): expected $($case.Value), got $actual" + } + $version = [version]$actual + if ($null -ne $previous -and $version -le $previous) { + throw "Version ordering failed: $previous >= $version" + } + $previous = $version +} + +$invalidTags = @( + "v65536.0.0", "v0.65536.0", "v0.0.65536", + "v65536.0.0-beta.1", "v0.65536.0-rc.1", "v0.0.65536-alpha.1", + "v999999999999999999999.0.0", "v2.0.0-beta.999999999999999999999", + "v2.0.0-alpha.20000", "v2.0.0-beta.20000", "v2.0.0-rc.20000", + "v2.0.0-beta.-1", "v2.0.0-beta.01", "v02.0.0", "v2.00.0", "v2.0.00", + "2.0.0", "v2.0", "v2.0.0beta2", "v2.0.0-preview.1", "v2.0.0+build.1", + "V2.0.0", "v2.0.0-BETA.1", " v2.0.0", "v2.0.0`n" +) +foreach ($tag in $invalidTags) { + $rejected = $false + try { $null = Get-ProductVersion $tag } catch { $rejected = $true } + if (-not $rejected) { throw "Invalid tag was accepted: $tag" } +} +Write-Host "Passed $($cases.Count) version cases, $($cases.Count - 1) ordering checks, and $($invalidTags.Count) invalid tags." + +if ($BuildInstaller) { + # Exercise the real packaging script using an inert payload; no SPONGE build needed. + $tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ([guid]::NewGuid().ToString()) + $payloadDir = Join-Path $tempRoot ".pixi/envs/version-test/bin" + New-Item -ItemType Directory -Path $payloadDir -Force | Out-Null + Set-Content -Path (Join-Path $payloadDir "SPONGE.exe") -Value "version test payload" + Copy-Item (Join-Path $PSScriptRoot "installer.nsi") $tempRoot + Copy-Item (Join-Path $PSScriptRoot "../../LICENSE") $tempRoot + Push-Location $tempRoot + try { + foreach ($tag in @("v2.0.0-alpha.19999", "v2.0.0-beta.2", "v2.0.0-rc.19999", "v2.0.0", "v2.0.1-alpha.0")) { + & $installerPath -EnvName "version-test" -Variant CPU -Tag $tag ` + -NsiPath "installer.nsi" -LicensePath "LICENSE" + $exe = Join-Path $tempRoot "release-artifacts/nsis/SPONGE-CPU-$tag-installer.exe" + $info = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($exe) + $fixedVersion = "$($info.FileMajorPart).$($info.FileMinorPart).$($info.FileBuildPart).$($info.FilePrivatePart)" + $fixedProductVersion = "$($info.ProductMajorPart).$($info.ProductMinorPart).$($info.ProductBuildPart).$($info.ProductPrivatePart)" + if ($fixedVersion -ne $cases[$tag] -or $fixedProductVersion -ne $cases[$tag] -or + $info.FileVersion -ne $cases[$tag] -or $info.ProductVersion -ne $tag.Substring(1)) { + throw "Installer version metadata mismatch for $tag" + } + } + Write-Host "Passed 5 NSIS installer metadata checks." + } finally { + Pop-Location + Remove-Item -Recurse -Force $tempRoot + } +} diff --git a/tests/virtual_atoms/test_virtual_atom_core.cpp b/tests/virtual_atoms/test_virtual_atom_core.cpp index c5217c12..9ea6728a 100644 --- a/tests/virtual_atoms/test_virtual_atom_core.cpp +++ b/tests/virtual_atoms/test_virtual_atom_core.cpp @@ -140,7 +140,7 @@ bool Check_Math() const Boundary periodic = Boundary{BoundaryPolicy::Periodic, cell, rcell}; const VECTOR force_v(1.2f, -0.7f, 0.4f); - ok &= Check_Vector("type 0 compatibility position", + ok &= Check_Vector("type 0 reflection position", Virtual_Atom_Type_0_Position({1.0f, 2.0f, 3.0f}, 4.0f), {1.0f, 2.0f, 5.0f}); ok &= Check_Vector("type 0 source force",