From 808c484f5b3ed36f5b51dbbaef2b950fe26a6ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A2=E4=BA=AC=E6=B3=BD?= Date: Fri, 11 Sep 2026 08:48:56 +0000 Subject: [PATCH] =?UTF-8?q?C=E5=86=85=E6=A0=B8=E6=B5=81=E9=87=8F=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E6=96=B9=E6=B3=95=E4=BC=98=E5=8C=96=EF=BC=8C=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E6=96=87=E4=BB=B6=E5=90=8D=E7=A7=B0=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 11 +- .github/workflows/solver-regression.yml | 2 +- app/simulation/backends.py | 2 +- app/simulation/native_codegen/__main__.py | 2 +- app/simulation/native_codegen/build.py | 29 +- app/simulation/paths.py | 2 +- app/simulation/warmup.py | 2 - bat/setup-native-linux.sh | 31 + ...test-mql-4模型复核与输入修正-2026-09-11.md | 78 + .../test-mql-8模型对照与修正-2026-09-11.md | 110 + .../牛顿管流求根与四路网页验证-2026-09-11.md | 141 + docs/standard/native-evaluation-schedule.md | 11 +- docs/standard/system-xml-v3.md | 2 + docs/update-log/更新日志-2026-09-11.md | 4 + frontend/src/App.tsx | 68 +- frontend/src/ContactAwareEdge.tsx | 77 +- frontend/src/SimulationResultsView.tsx | 15 +- frontend/src/componentSymbols/pneumatic.tsx | 5 +- frontend/src/edgeRouting.ts | 91 - frontend/src/styles.css | 17 - frontend/tests/e2e/component-symbols.spec.ts | 60 +- frontend/tests/e2e/edge-interactions.spec.ts | 332 +- frontend/tests/e2e/fit-view.spec.ts | 11 +- frontend/tests/e2e/parameter-table.spec.ts | 4 +- frontend/tests/e2e/pressure-units.spec.ts | 198 + .../tests/e2e/project-persistence.spec.ts | 139 + .../tests/e2e/results-chart-windows.spec.ts | 108 + .../tests/e2e/simulation-live-mql8.spec.ts | 4 +- frontend/tests/e2e/vertical-mirror.spec.ts | 235 + native/README.md | 23 +- native/components/kernels.c | 82 +- .../native}/native-python-reference.json | 0 .../test-mql-4-amesim-reference.json | 0 .../test_mql_full_branches/manifest.json | 2 +- .../sources}/test_mql-full-branches-01-04.xml | 0 .../data/AmesimModels}/help/P4NODE2.pdf | Bin .../data/AmesimModels}/help/PN3NODE2.pdf | Bin .../data/AmesimModels}/help/PNCH012.pdf | Bin .../data/AmesimModels}/help/PNL0001.pdf | Bin .../data/AmesimModels}/help/PNL0002.pdf | Bin .../data/AmesimModels}/help/PNL0003.pdf | Bin .../data/AmesimModels}/help/PNL00R.pdf | Bin .../data/AmesimModels}/help/PNOR001.pdf | Bin .../data/AmesimModels}/help/PNVO001.pdf | Bin .../03_gas_properties.pdf | Bin .../06_important_rules.pdf | Bin .../07_review_pneumatic.pdf | Bin .../pneumatic_library_manual/08_pipes.pdf | Bin .../lib_pneumatic.pdf | Bin .../data/AmesimModels}/help/source/PNCH012.c | 0 .../AmesimModels}/help/source/PNCH012.spe | 0 .../data/AmesimModels}/help/source/PNL0001.c | 0 .../AmesimModels}/help/source/PNL0001.spe | 0 .../data/AmesimModels}/help/source/PNL0002.c | 0 .../AmesimModels}/help/source/PNL0002.spe | 0 .../data/AmesimModels}/help/source/PNL0003.c | 0 .../AmesimModels}/help/source/PNL0003.spe | 0 .../data/AmesimModels}/help/source/PNL00R.c | 0 .../data/AmesimModels}/help/source/PNL00R.spe | 0 .../data/AmesimModels}/help/source/PNOR001.c | 0 .../AmesimModels}/help/source/PNOR001.spe | 0 .../data/AmesimModels}/help/source/PNVO001.c | 0 .../AmesimModels}/help/source/PNVO001.spe | 0 .../AmesimModels}/help/source/pn_public.h | 0 .../help/utilities/pn2pipefr_.pdf | Bin .../help/utilities/pn2rcqfix_.pdf | Bin .../AmesimModels}/help/utilities/pn2vol.pdf | Bin .../AmesimModels}/help/utilities/pn2vol1.pdf | Bin .../AmesimModels}/help/utilities/pn2vol2_.pdf | Bin .../data/AmesimModels}/test_mql.ame | Bin .../data/AmesimModels}/test_mql/README.md | 0 tests/data/README.md | 24 + .../fixtures/high_stiffness_explicit_rk45.xml | 325 - tests/data/native-skill-test.xml | 325 - tests/data/test-mql-4-corrected.json | 15649 ++++++++++++++++ tests/data/test-mql-4-corrected.xml | 1088 -- ...t-mql-8.json => test-mql-8-corrected.json} | 256 +- tests/data/test-mql-8.xml | 2078 -- tests/manual/benchmark_native_pipe_solver.py | 91 + tests/manual/browser_native_simulation.mjs | 89 + tests/manual/compare_native_amesim.py | 92 + tests/model_audit/audit_mql4_model.py | 403 + .../independent_mql4_parameter_check.py | 101 + tests/model_audit/p4node2-ame-geometry.json | 98 + tests/model_audit/test-mql-4-audit.json | 1396 ++ tests/native_reference.py | 2 +- tests/test_high_stiffness_explicit_rk45.py | 1 - tests/test_native_codegen.py | 5 +- tests/test_native_only_backend.py | 2 +- tests/test_native_pipe_physics.py | 6 +- tests/test_native_pipe_solver.py | 237 + tests/test_pressure_units.py | 84 + tests/test_regression_fixture_line_endings.py | 6 +- tools/audit_test_mql8_model.py | 325 + 94 files changed, 20163 insertions(+), 4418 deletions(-) create mode 100755 bat/setup-native-linux.sh create mode 100644 docs/other/test-mql-4模型复核与输入修正-2026-09-11.md create mode 100644 docs/other/test-mql-8模型对照与修正-2026-09-11.md create mode 100644 docs/other/牛顿管流求根与四路网页验证-2026-09-11.md create mode 100644 frontend/tests/e2e/pressure-units.spec.ts create mode 100644 frontend/tests/e2e/project-persistence.spec.ts create mode 100644 frontend/tests/e2e/vertical-mirror.spec.ts rename tests/{data => baselines/native}/native-python-reference.json (100%) rename tests/{data => baselines/simulation/test_mql_4}/test-mql-4-amesim-reference.json (100%) rename tests/{data => baselines/simulation/test_mql_full_branches/sources}/test_mql-full-branches-01-04.xml (100%) rename {AmesimModels => tests/data/AmesimModels}/help/P4NODE2.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/PN3NODE2.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/PNCH012.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/PNL0001.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/PNL0002.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/PNL0003.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/PNL00R.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/PNOR001.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/PNVO001.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/pneumatic_library_manual/03_gas_properties.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/pneumatic_library_manual/06_important_rules.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/pneumatic_library_manual/07_review_pneumatic.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/pneumatic_library_manual/08_pipes.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/pneumatic_library_manual/lib_pneumatic.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNCH012.c (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNCH012.spe (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNL0001.c (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNL0001.spe (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNL0002.c (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNL0002.spe (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNL0003.c (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNL0003.spe (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNL00R.c (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNL00R.spe (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNOR001.c (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNOR001.spe (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNVO001.c (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/PNVO001.spe (100%) rename {AmesimModels => tests/data/AmesimModels}/help/source/pn_public.h (100%) rename {AmesimModels => tests/data/AmesimModels}/help/utilities/pn2pipefr_.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/utilities/pn2rcqfix_.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/utilities/pn2vol.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/utilities/pn2vol1.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/help/utilities/pn2vol2_.pdf (100%) rename {AmesimModels => tests/data/AmesimModels}/test_mql.ame (100%) rename {AmesimModels => tests/data/AmesimModels}/test_mql/README.md (100%) create mode 100644 tests/data/README.md delete mode 100644 tests/data/fixtures/high_stiffness_explicit_rk45.xml delete mode 100644 tests/data/native-skill-test.xml create mode 100644 tests/data/test-mql-4-corrected.json delete mode 100644 tests/data/test-mql-4-corrected.xml rename tests/data/{test-mql-8.json => test-mql-8-corrected.json} (99%) delete mode 100644 tests/data/test-mql-8.xml create mode 100644 tests/manual/benchmark_native_pipe_solver.py create mode 100644 tests/manual/browser_native_simulation.mjs create mode 100644 tests/manual/compare_native_amesim.py create mode 100644 tests/model_audit/audit_mql4_model.py create mode 100644 tests/model_audit/independent_mql4_parameter_check.py create mode 100644 tests/model_audit/p4node2-ame-geometry.json create mode 100644 tests/model_audit/test-mql-4-audit.json create mode 100644 tests/test_native_pipe_solver.py create mode 100644 tests/test_pressure_units.py create mode 100644 tools/audit_test_mql8_model.py diff --git a/.gitattributes b/.gitattributes index 07d1f09..bb0bedb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,9 +4,14 @@ # Regression manifests hash these files as raw bytes. Keep their checkout # representation identical on Windows and Linux so hashes remain portable. -tests/data/test-mql-8.xml text eol=lf -tests/data/test-mql-8.json text eol=lf -tests/data/test_mql-full-branches-01-04.xml text eol=lf +tests/fixtures/legacy/test-mql-8.xml text eol=lf +tests/fixtures/legacy/test-mql-8.json text eol=lf +tests/baselines/simulation/test_mql_full_branches/sources/test_mql-full-branches-01-04.xml text eol=lf tests/baselines/simulation/**/*.json text eol=lf tests/baselines/simulation/**/sources/* text eol=lf + +# Browser projects and relocated native references. +tests/data/*.json text eol=lf +tests/baselines/native/*.json text eol=lf +tests/fixtures/amesim/*.xml text eol=lf diff --git a/.github/workflows/solver-regression.yml b/.github/workflows/solver-regression.yml index e076c90..52c74df 100644 --- a/.github/workflows/solver-regression.yml +++ b/.github/workflows/solver-regression.yml @@ -93,7 +93,7 @@ jobs: python -W error::ResourceWarning -m unittest discover -s tests - name: Complete the 10 second skill fixture in native RK45 run: | - python -m app.simulation.native_codegen tests/data/native-skill-test.xml --output-dir test/ci-skill --method RK45 --max-step 0.001 --rtol 1e-7 --runs 1 --solve-only + python -m app.simulation.native_codegen tests/fixtures/native-skill-test.xml --output-dir test/ci-skill --method RK45 --max-step 0.001 --rtol 1e-7 --runs 1 --solve-only - if: always() uses: actions/upload-artifact@v4 with: diff --git a/app/simulation/backends.py b/app/simulation/backends.py index 9b70382..6a20d8f 100644 --- a/app/simulation/backends.py +++ b/app/simulation/backends.py @@ -26,7 +26,7 @@ def simulation_config(simulation) -> SolveIVPConfig: # floors are emitted by native_codegen.tolerances; XML tolerance fields # remain a separate protocol change. return SolveIVPConfig(t_start=simulation.t_start, t_stop=simulation.t_stop, - method=simulation.method, rtol=1e-7, max_step=simulation.max_step) + method=simulation.method, rtol=1e-8, max_step=simulation.max_step) def simulate_network(network, simulation, *, progress_callback=None, diff --git a/app/simulation/native_codegen/__main__.py b/app/simulation/native_codegen/__main__.py index 2fd03ef..a85e62b 100644 --- a/app/simulation/native_codegen/__main__.py +++ b/app/simulation/native_codegen/__main__.py @@ -64,7 +64,7 @@ def main(): measured = rows[1:] summary = { "source": str(args.input.resolve()), "sourceSha256": sha256(args.input.read_bytes()).hexdigest(), - "xmlSha256": sha256(xml).hexdigest(), "executable": str(package / "model.exe"), + "xmlSha256": sha256(xml).hexdigest(), "executable": str(package / build.executable.name), "cachedExecutable": str(build.executable), "buildKey": build.manifest["buildKey"], "buildSeconds": build.seconds, "preparationSeconds": preparation, "cacheHit": build.cache_hit, diff --git a/app/simulation/native_codegen/build.py b/app/simulation/native_codegen/build.py index 3c01092..8788541 100644 --- a/app/simulation/native_codegen/build.py +++ b/app/simulation/native_codegen/build.py @@ -33,10 +33,16 @@ def _hash(path: Path) -> str: def toolchain() -> tuple[str, Path, str]: + if os.name != "nt" and not sys.platform.startswith("linux"): + raise RuntimeError("Native builds support Windows and Linux; this platform is not supported.") compiler = os.environ.get("SIMULATION_NATIVE_CC") or shutil.which("gcc") if not compiler: raise RuntimeError("C compiler not found; set SIMULATION_NATIVE_CC to gcc.") - base = Path(os.environ.get("SUNDIALS_ROOT", str(Path(sys.base_prefix) / "Library"))) + configured = os.environ.get("SUNDIALS_ROOT") + candidates = ([Path(configured)] if configured else + [Path(sys.base_prefix) / "Library"] if os.name == "nt" else + [Path(sys.prefix) / "native/sundials-7.4.0", Path("/usr/local"), Path("/usr")]) + base = next((path for path in candidates if (path / "include/cvode/cvode.h").is_file()), candidates[0]) if not (base / "include/cvode/cvode.h").is_file(): raise RuntimeError("SUNDIALS C development files not found; set SUNDIALS_ROOT.") version = subprocess.run([compiler, "--version"], capture_output=True, text=True, check=True, timeout=15).stdout.splitlines()[0] @@ -48,6 +54,7 @@ def build_native(program: NativeProgram, *, cache_dir: Path | None = None) -> Na compiler, sundials, compiler_version = toolchain() runtime = sorted(NATIVE.rglob("*.c")) + sorted((NATIVE / "include").glob("*.h")) flags = ["-std=c11", "-O3", "-Wall", "-Wextra", "-Werror", "-ffp-contract=off", "-fno-fast-math"] + executable_name = "model.exe" if os.name == "nt" else "model" if os.name == "nt": flags += ["-D__USE_MINGW_ANSI_STDIO=1", "-static-libgcc"] libraries = [sundials / "lib" / f"sundials_{name}.lib" for name in LIBRARIES] @@ -55,8 +62,20 @@ def build_native(program: NativeProgram, *, cache_dir: Path | None = None) -> Na vc_runtime = sundials / "bin/vcruntime140.dll" if vc_runtime.is_file(): dlls.append(vc_runtime) + elif sys.platform.startswith("linux"): + flags += ["-D_POSIX_C_SOURCE=200809L"] + library_dirs = [sundials / "lib", sundials / "lib64", sundials / "lib/x86_64-linux-gnu"] + library_dir = next((path for path in library_dirs if all( + (path / f"libsundials_{name}.a").is_file() for name in LIBRARIES)), None) + if library_dir is None: + raise RuntimeError("SUNDIALS static libraries not found; build with BUILD_STATIC_LIBS=ON and set SUNDIALS_ROOT.") + libraries = [library_dir / f"libsundials_{name}.a" for name in LIBRARIES] + dlls = [] else: - raise RuntimeError("Native v1 build packaging currently supports Windows x64; Linux packaging is pending.") + raise RuntimeError("Native builds support Windows and Linux; this platform is not supported.") + link_libraries = list(map(str, libraries)) + if sys.platform.startswith("linux"): + link_libraries = ["-Wl,--start-group", *link_libraries, "-Wl,--end-group"] sources = {str(p.relative_to(ROOT)): _hash(p) for p in runtime} sources["native/THIRD_PARTY_NOTICES.txt"] = _hash(NATIVE / "THIRD_PARTY_NOTICES.txt") dependencies = {str(p.name): _hash(p) for p in libraries + dlls} @@ -75,7 +94,7 @@ def build_native(program: NativeProgram, *, cache_dir: Path | None = None) -> Na manifest = json.loads(manifest_path.read_text(encoding="utf-8")) if all((target / name).is_file() and _hash(target / name) == digest for name, digest in manifest["artifacts"].items()): - return NativeBuild(target / "model.exe", manifest, True, time.perf_counter()-start) + return NativeBuild(target / executable_name, manifest, True, time.perf_counter()-start) raise RuntimeError(f"Native cache integrity check failed: {target}") cache.mkdir(parents=True, exist_ok=True) stage = Path(tempfile.mkdtemp(prefix="building-", dir=cache)) @@ -84,7 +103,7 @@ def build_native(program: NativeProgram, *, cache_dir: Path | None = None) -> Na command = [compiler, *flags, "-I", str(stage), "-I", str(NATIVE / "include"), "-I", str(sundials / "include"), str(stage / "model.c"), *[str(p) for p in runtime if p.suffix == ".c"], - *map(str, libraries), "-lm", "-o", str(stage / "model.exe")] + *link_libraries, "-lm", "-o", str(stage / executable_name)] result = subprocess.run(command, capture_output=True, text=True, timeout=120) (stage / "build.log").write_text(result.stdout + result.stderr, encoding="utf-8") if result.returncode: @@ -116,4 +135,4 @@ def build_native(program: NativeProgram, *, cache_dir: Path | None = None) -> Na if stage.resolve().parent != cache: raise RuntimeError("Unexpected native build staging directory.") shutil.rmtree(stage) - return NativeBuild(target / "model.exe", manifest, False, time.perf_counter()-start) + return NativeBuild(target / executable_name, manifest, False, time.perf_counter()-start) diff --git a/app/simulation/paths.py b/app/simulation/paths.py index 9fc0866..ba8d444 100644 --- a/app/simulation/paths.py +++ b/app/simulation/paths.py @@ -15,7 +15,7 @@ DATA_DIR = ( ) SIMULATION_RUNS_DIR = DATA_DIR / "simulation-runs" -SIMULATION_BASELINES_DIR = PROJECT_ROOT / "tests" / "data" +SIMULATION_BASELINES_DIR = PROJECT_ROOT / "tests" / "baselines" AMESIM_TEST_MQL_ARCHIVE_PATH = PROJECT_ROOT / "AmesimModels" / "test_mql.ame" MODELICA_TESTMODEL_RESULT_PATH = ( PROJECT_ROOT / "ModelicaModels" / "Simulation" / "Testmodel_res.csv" diff --git a/app/simulation/warmup.py b/app/simulation/warmup.py index 5b7cf0d..c5b520f 100644 --- a/app/simulation/warmup.py +++ b/app/simulation/warmup.py @@ -49,8 +49,6 @@ def _run_native_warmup() -> None: from app.simulation.native_codegen.build import toolchain from app.system_xml import validate_system_xml_document - if os.name != "nt": - raise RuntimeError("Native v1 currently supports Windows x64 build packaging only.") toolchain() validate_system_xml_document(b"") diff --git a/bat/setup-native-linux.sh b/bat/setup-native-linux.sh new file mode 100755 index 0000000..d662a6f --- /dev/null +++ b/bat/setup-native-linux.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Build the pinned native dependency beside the Python virtual environment. +set -euo pipefail +REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)" +NATIVE_ENV="${SYSTEM_SIMULATION_NATIVE_ENV:-$REPO_ROOT/.venv/native}" +VERSION=7.4.0 +ARCHIVE_SHA256=679ddacdd77610110e613164e8297d6d0cd35bae8e9c3afc8e8ff6f99a1c2a7b +SOURCE_ROOT="$NATIVE_ENV/build/sundials-$VERSION" +PREFIX="$NATIVE_ENV/sundials-$VERSION" +for tool in curl cmake gcc tar sha256sum; do + command -v "$tool" >/dev/null || { echo "Missing build tool: $tool" >&2; exit 1; } +done +mkdir -p "$SOURCE_ROOT" +ARCHIVE="$SOURCE_ROOT/sundials-$VERSION.tar.gz" +if [[ ! -f "$ARCHIVE" ]]; then + curl --fail --location "https://github.com/LLNL/sundials/releases/download/v$VERSION/sundials-$VERSION.tar.gz" --output "$ARCHIVE" +fi +printf '%s %s\n' "$ARCHIVE_SHA256" "$ARCHIVE" | sha256sum --check - +if [[ ! -d "$SOURCE_ROOT/sundials-$VERSION" ]]; then + tar -xzf "$ARCHIVE" -C "$SOURCE_ROOT" +fi +cmake -S "$SOURCE_ROOT/sundials-$VERSION" -B "$SOURCE_ROOT/build" \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_ARKODE=OFF -DBUILD_CVODES=OFF -DBUILD_IDA=OFF \ + -DBUILD_IDAS=OFF -DBUILD_KINSOL=OFF \ + -DEXAMPLES_ENABLE_C=OFF -DEXAMPLES_INSTALL=OFF +cmake --build "$SOURCE_ROOT/build" --parallel "${CMAKE_BUILD_PARALLEL_LEVEL:-4}" +cmake --install "$SOURCE_ROOT/build" +echo "Native dependency installed in $PREFIX" +echo "The repository .venv Python discovers this default location automatically." diff --git a/docs/other/test-mql-4模型复核与输入修正-2026-09-11.md b/docs/other/test-mql-4模型复核与输入修正-2026-09-11.md new file mode 100644 index 0000000..30cd050 --- /dev/null +++ b/docs/other/test-mql-4模型复核与输入修正-2026-09-11.md @@ -0,0 +1,78 @@ +# test-mql-4 模型复核与输入修正(2026-09-11) + +目录整理补充:`tests/data` 的 JSON/XML 仅保留浏览器可导入的四路、八路 corrected JSON;参考曲线、旧工程和后端 XML 已迁到 `tests/baselines` / `tests/fixtures`,本文当前路径同步更新,文件内容及来源哈希不变。详见 [数据目录说明](../../tests/data/README.md)。 + +本次从 `tests/data/test_mql_4.ame` 的原始 `.cir` 图纸重新核对元件、参数和端口连接,生成了可导入网页的 `tests/data/test-mql-4-corrected.json`,并同步 `tests/fixtures/amesim/test-mql-4-corrected.xml`。两份输入均有 81 个元件、90 条连接、582 个公开参数,对应 64 个动态状态;生成结果的每个公开参数和连接端口均已按同一元件映射与 AME 图纸逐项比较。生成的 XML 通过后端语法和语义校验,无错误、无警告。 + +`native-python-reference.json` 是 50 个网络的冻结数值参考,`test-mql-4-amesim-reference.json` 是 72 条曲线、57 个时刻的 Amesim 结果参考,两者都不是可导入的工程 JSON。本次未覆盖或改写这两份参考数据。 + +## 新发现及修正 + +此前名为 corrected 的 XML 仍有一处参数不一致:`amesim_pnl00r_4` 对应 AME 的 `pneumatic`(图纸 `LINE` 索引 38、PNL00R)。AME 的相对粗糙度 `rr` 为 `1e-5`,旧 XML 为 `0.0032142857142857142`;本次改为 `1e-5`。该管径在前轮已改为 `0.01 m`,本次确认管长 `1 m` 也一致。此前报告列出的五处参数修正未覆盖这项遗留差异。 + +旧 XML 和 AME 的元件连接图同构,但有 25 条连线涉及等价端口编号或方向差异。本次 JSON/XML 直接采用 AME 的确切端口编号: + +- 11 条连接涉及 P4NODE2 的非参考分支口重排,固定参考口 `port_2` 原本已匹配。 +- 8 条连接涉及 PNCH012 的同一容腔端口重排。 +- 4 条连接涉及 LMECHN1 的支路编号重排,公共机械端保持原对应关系。 +- 2 条连接涉及 PNL00R 两端方向反转。它不增加容腔状态,但其带符号端口输出应按新端口对应关系解释。 + +PNL0001 的状态端未颠倒:AME 建模连线的 `OUTPUT_TYPE=1` 使用起点→模型端口 1、终点→端口 2;`OUTPUT_TYPE=2` 使用相反对应。组件端口索引从 0 起,PNVO001/FORC 的索引 0 映射为公开 `res`,STEP0/UD00 的索引 0 映射为 `out`,其他组件端口映射为 `port_(索引+1)`。 + +工程保留先前结果变量使用的组件 ID;显示名改为 AME 图纸别名。已有画布布局来自八路 JSON 中同 ID 元件的展示元数据,缺少的闭合管另行放置。变化端口的连接改用可见连线,避免把不再重合的端口继续标记为接触连接。布局、名称和绘图连线路径不作为物理一致性的依据。 + +后续真实浏览器导入发现,组件目录中的信号端口带有 `positiveFlowDirection: null`,而前端工程合同只接受省略此可选字段或值为 `intoComponent`。已修正交付 JSON 及生成器:没有方向值时省略该键。该修复仅涉及展示元数据,物理参数、连接端点及数值 XML 不变;审计 JSON 中的工程文件 SHA256 已同步更新。仅通过 XML 校验不足以证明工程 JSON 可被前端导入。 + +## 用户复核新增:P4NODE2 图形编号与朝向 + +用户直接打开 AME 后指出参考口看起来不同。这是实际的显示缺陷,前面的物理连接审计没有覆盖图形编号与朝向:导出工程的 80/81 个节点位置沿用了八路 JSON 中同 ID 的位置,闭合管 `amesim_pnl0002_10` 另放在 `(80, 900)`;工程布局并没有恢复为 AME 原图的坐标和连线路径。 + +重新直接读取 `.cir` 的 4 个 P4NODE2,均为 `COMP_GEOMETRY=1`,原始四个 `COMP_PORT/PORT_POS` 依次为 `(10,19)`、`(19,10)`、`(10,1)`、`(1,10)`。因此原图端口 1 朝下、参考口 2 朝右、端口 3 朝上、端口 4 朝左。前端原图标的 3/4 号锚点编号与该对应关系不符,而且四路 JSON 的第 2~4 节点未旋转,令参考粗线朝左。仅旋转原图标不能修正全部四口,因为对顶端口的编号也不同。 + +已交换通用 P4NODE2 图标的端口 3/4 锚点,并把四路工程的 4 个 P4NODE2 统一为旋转 180°、不镜像,使四口完整方位符合 AME;清除了 1 条相关连线的旧路由点。参考粗线与实际 `port_2` Handle 使用同一镜像/旋转变换,二者本身没有脱离。通用锚点修正也影响其他工程(包括八路 corrected)的 P4NODE2 显示方位,不改变其连接所引用的端口名称。 + +这次修复没有改变任何元件 ID、模型类型/版本、参数、连线 ID/端点或仿真设置,数值 XML 完全未改。审计 JSON 保留此前参数和连接修正记录,另记本次展示变化及最新工程哈希。 + +AME 自带预览图以及重新直接读取的原始图纸均显示四支路:4 个 PNRP17 活塞、4 个 PNCH012 气室、4 个 PNVO001 阀、4 个 STEP0 输入、4 个 LSTP00A 接触元件;4 个支路质量块另与 2 个公共质量块连接,因此 MECMAS21 总数为 6。图中气动管线有 18 条作为子模型的 LINE,在网页中显示为独立管路元件,这也是两种画法的计数观感不同之处。 + +独立图形证据保存在 `tests/model_audit/p4node2-ame-geometry.json`,直接记录原始 AME 端口坐标和归档哈希。新增前端回归以这组独立坐标检查实际 DOM Handle 的相对方位,并检查镜像后粗线仍指向 `port_2`;它不直接复制前端锚点常量作为判断依据。两项定向浏览器测试(既有 PN3/P4 图形检查及新增 AME 四口/镜像检查)均通过,共 9.4 s;测试没有请求仿真。 + +## 独立原始参数复核 + +为避免共享参数转换代码造成共同漏检,另用 `tests/model_audit/independent_mql4_parameter_check.py` 直接解析 AME 原始字段和表达式,独立求值及换算,没有调用原审计的 `_expected_parameters` 或其字段转换辅助函数。582 项分为 543 个 AME 原生字段、32 个公开容积/容积变化输入的零默认值,以及 7 个适配字段(4 个阀开度回退值、2 个由图形方向映射的力方向、1 个氦气物性选择);公开额外字段不冒充 AME 原生参数。 + +当前 JSON 的 582 项均与各自来源或明确的适配规则相符。独立核对包括 26 个表压转绝压、58 个 mm→m、8 个 mm²→m²、6 个 L→m³,以及各 12 个刚度和阻尼从每毫米转为每米的换算。脚本已从可提交路径实际运行通过;逐字段表达式、原值、原单位、目标单位与比较结果输出到 `test/solver-newton-20260911/mql4/visual-recheck/independent-parameters.json`。 + +## 仿真设置及参考边界 + +生成输入为 BDF、0~10 s、输出间隔 0.01 s、最大步长 `1e30 s`;本轮初始核查阶段网页后端默认 `rtol=1e-7`,后续精度核查后已调整为 `1e-8`;JSON 不保存该后端默认值,实际运行值应以结果诊断为准。原 AME `.sim` 的原始两行数值保存在审计 JSON 中。前轮官方 API 核查记录该归档保存的是固定步长设置,而用于既有参考数据的是官方重新编译后改用 Standard 变步长、误差限 `1e-7`、最大步长 `1e30` 的四路运行。本次没有重新运行 Amesim,也不把 native BDF 与 Amesim Standard 自动算法称为同一积分器。 + +原 AME 归档内的图纸和编译/结果缓存不一致,不能直接把包内 `.results` 当作本次四路参考: + +- `.cir` 有 63 个组件、18 条建模管线、26 条直接连线和 28 对接触连接,得到 81 个公开元件、90 条连接、64 个动态状态。 +- `.modelinfo` 仍记载 132 个状态,`.ameperf` 包含 LSTP00A 第 1~8 实例,属于旧八路运行。 +- `.results` 声明 1116 个保存变量,而当前 `.var` 只有 640 行;这两份缓存不能作为相互一致的结果集合使用。 + +既有 `test-mql-4-amesim-reference.json` 的来源声明为 2026-09-11 官方重编译四路模型,包含 72 条状态物理量及阀流量曲线、57 个选定时刻。它的原始 AME SHA256 与当前归档一致;其 `xmlSha256` 绑定的是修改前 XML,故本次修正后不再与该字段相等。这个历史字段和曲线数据保持不变,不能为通过校验而改写来源哈希。 + +该参考可用于本次曲线差异评估,但不表示本次重新运行了 Amesim,也不覆盖接触事件力峰或全部 1001 个常规采样点。新修改的粗糙度应作为本次输入差异明确记录,不能继续沿用“此前 corrected XML 已逐参数完全对齐”的结论。 + +## 复现与文件 + +在仓库根目录执行只读核查,不生成或改写工程与审计文件: + +```sh +.venv/bin/python tests/model_audit/audit_mql4_model.py --check +``` + +该模式检查生成端口可选字段的规则,并逐组件比较 JSON/XML 的模型类型、版本、参数、全部连接端点与仿真设置;不启动求解器。当前交付通过,内存中人为加入 `positiveFlowDirection: null`、修改参数或连接端点均被拒绝。它是针对交付输入的轻量检查,不替代完整前端解析与真实浏览器导入。 + +需要重新生成 corrected JSON/XML 及审计文件时执行: + +```sh +.venv/bin/python tests/model_audit/audit_mql4_model.py +``` + +脚本只静态读取 AME 图纸和注册组件合同,生成/核对 corrected JSON/XML,并输出 `tests/model_audit/test-mql-4-audit.json`。参数和单位转换复用仓库 `tests/test_test_mql_ame_contract.py` 的明确映射,脚本不依赖 `test/` 中的程序。首次运行时将其输入 XML 备份到 `test/solver-newton-20260911/mql4/legacy-corrected-input.xml`,另在该目录保存展开后的源模型和过程审计;修改前 XML 的 SHA256 为 `7b67e61f791f33bf2a4a0045d151d7fd6a418e63a5ea7f3472d474fd4eff9c86`。 + +原 AME SHA256:`99a071896671d634d651a7b21f487662dd7c122af8fd3d60ead404a5afa53ed4`。完整成员哈希、元件映射、25 条连接变化和生成文件哈希保存在审计 JSON 中。本报告记录输入及图形复核,不记录求解性能或本轮数值曲线结论。 diff --git a/docs/other/test-mql-8模型对照与修正-2026-09-11.md b/docs/other/test-mql-8模型对照与修正-2026-09-11.md new file mode 100644 index 0000000..8f1bef0 --- /dev/null +++ b/docs/other/test-mql-8模型对照与修正-2026-09-11.md @@ -0,0 +1,110 @@ +# test-mql-8 模型对照与修正(2026-09-11) + +目录整理补充:`tests/data` 的 JSON/XML 仅保留浏览器可导入的四路、八路 corrected JSON;参考曲线、旧工程和后端 XML 已迁到 `tests/baselines` / `tests/fixtures`,本文当前路径同步更新,文件内容及来源哈希不变。详见 [数据目录说明](../../tests/data/README.md)。 + +已依据 `tests/data/test_mql.ame` 的真实图纸生成 `tests/data/test-mql-8-corrected.json`,保留原 `test-mql-8.json` 供审计。修正版包含参数、逐端口连接和仿真最大步长的对齐。静态校验通过。rtol=1e-7 的首次求解在 120 s 限额内推进到 5.144858167 s 后超时;后续按用户确认的全局 rtol=1e-8,以同一执行模型和内核完成了完整 10 s 单次运行。本轮未运行 Amesim 官方程序,也未进行八路 Amesim 曲线对比。 + +## 来源与核查方法 + +AME 是可读取的 tar 归档。以 `test_mql_.cir` 的组件、接触连接、DIRECT 连接和带子模型的管线为模型来源,解析全局表达式、SI 单位和初始状态。压力字段按图纸保存的表压加 `101300 Pa` 转为公开模型的绝对压力。原始输入指纹: + +- `tests/data/test_mql.ame`:21698560 字节,SHA256 `1ff0ea4284b9248260eeceb8b27cd0bc14dbccb43554ab8c0b49900d0b944c3d`。 +- `tests/fixtures/legacy/test-mql-8.json`:272523 字节,SHA256 `4f1bf698519ba21429a11a83653adc7e52191f2d2b51fca943ee0b84f62feebd`。 + +图纸含 117 个组件、40 条带模型管线,展开后共 157 个元件(含介质)、178 条连接,与 JSON 的类型拓扑完全同构。映射以类型和完整邻接约束建立,再选端口修改数最少的对应关系,不使用组件显示名称猜测。类型拓扑的剩余对称候选具有相同 AME 参数;完整逐元件映射保存在审计文件中。 + +组件图纸端口索引从 0 开始;信号端按公开合同映射为 `res/out`。管线 `OUTPUT_TYPE=1` 时起点对应计算 port_1、终点对应 port_2;`OUTPUT_TYPE=2` 时相反。这个字段决定 PNL0001 的储气端,不能仅根据线段绘制方向推断。 + +1092 个公开参数全部核对,其中 1015 个可直接对应参数又与归档 `.param/.data` 逐值交叉核对,全部一致;这两份表均有 1170 行。两个 LMECHN1 的结构参数 `v1=8` 只在图纸中声明,其余公开补充参数按已有合同映射。归档结果未用于声称本轮已经重新运行 Amesim 或完成曲线一致性验证。 + +## 参数修正 + +以下四项均属于拓扑唯一对应的 `amesim_mecmas21_9`,AME 实体为 `component:35 / mass_friction_endstops_19`。 + +| 参数 | 原 JSON | AME 图纸与修正版 | `.param/.data` 行号 | +| --- | ---: | ---: | ---: | +| mass | 90000 kg | 100 kg | 364 | +| xmin | -0.72 m | -1 m | 373 | +| xmax | 0 m | 1 m | 377 | +| stoptype | 1 | 4 | 383 | + +这不是只交换参考口的同数值优化;质量与限位配置的修正确实会改变系统行为。其余 1088 个参数已一致,保留原值及编辑器单位、表达式和显示设置。比较允许正常的浮点表达式舍入差异,不把 `15299999.999999998` 与 `15300000` 当作参数错误。 + +## 连接和仿真设置 + +所有 178 条边逐端口与 AME 对齐,共 62 条边各修改一个端点,元件之间的邻接关系保持一致: + +| 类型 | 修改端点数 | 原因 | +| --- | ---: | --- | +| 8 个 P4NODE2 | 30 | 修正 7 个参考口的实际来源,并对齐其余支路的精确编号 | +| 8 个 PNCH012 | 16 | 对齐共享容腔的 port_2/port_3 编号 | +| 2 个 LMECHN1 | 16 | 对齐八条机械汇总支路的编号顺序 | + +后两项属于现有共享状态/汇总端口的编号对齐,不应解释成 32 处独立物理机制错误。15 条受影响边原带有几何接触标记,端点变化后清除该标记,使连接以普通边显示;不删除任何物理连接,也不移动组件。 + +AME `.sim` 首行保存 `0 10 0.01 1e30 1e-7 ...`。修正版保持 0~10 s、输出间隔 0.01 s,最大积分步长由原 JSON 的 0.02 s 对齐为 1e30 s。公开模型仍采用 BDF;没有将 AME 的内部求解器编号等同于 CVODE BDF。AME 归档相对误差限为 1e-7,首次原生核验采用同值。后续经用户确认,全局网页/API 默认值调整为 1e-8;JSON 协议本身不新增误差限字段,后续执行按实际后端设置记录。 + +## 验证与复现 + +修正版通过工程载荷解析、XML 导出、端口供需与网络校验,以及正式 C 代码生成:132 个动态状态、1784 个输出、484 条气动计算关系、0 个系统代数循环块。编辑器保存的参数表达式在单独的执行副本中解析为数值,与浏览器导出行为一致。此项静态验证之后进行了下述一次真实数值执行;管流求根修改的其他模型性能对照由独立验证记录说明。 + +复现:在仓库根目录执行 `.venv/bin/python tools/audit_test_mql8_model.py`。脚本复用现有 `tests/test_test_mql_ame_contract.py` 中的 AME 表达式、单位和公开参数映射辅助函数,不执行该历史测试类。 + +脚本写入修正版 JSON,以及 Git 忽略目录 `test/solver-newton-20260911/mql8/` 中的 `audit.json`(157 个实体映射、1092 项参数、178 条连接、1015 项归档参数交叉证据和来源指纹)与 `corrected.xml`。它不修改原 JSON、AME 或冻结基准。 + +本轮实际求解与手工网页验收均显式使用修正版 `tests/data/test-mql-8-corrected.json`,作为后续用户运行的交付文件。原 JSON 仅保留为审计来源;旧 `simulation-live-mql8.spec.ts` 记录 2026-08-18 的 Python 后端活动心跳流程并写入该日基准文件,不作为本轮原生验收入口,未覆盖其历史证据。`fit-view.spec.ts` 的旧模型视图检查和 `tests/baselines/` 也保持原始输入。 + +## 单次真实 C 执行 + +使用修正版 JSON,经正式输入加载、C 生成和本机静态 SUNDIALS 7.4.0 构建后,直接调用一次 `execute_native`;未预热、未重复计时,也未更改物理参数、精度、积分器或库。请求 BDF/CVODE、0~10 s、rtol=1e-7、最大步长 1e30 s、采样间隔 0.01 s,执行限额 120 s。质量等状态的实际绝对误差限仍由当前生成器按物理量生成。 + +| 指标 | 单次运行结果 | +| --- | ---: | +| 完成状态 | failed,执行超时 | +| 实际推进终点 | 5.144858167417297 s | +| 纯求解墙钟 | 120.000105 s | +| 模型求值次数 | 1530472 | +| 接受步数 / 拒绝步数 | 77649 / 2590 | +| 输出采样数 | 517(含事件和退出时刻) | +| 气体质量状态数 | 56 | +| 初始气体总质量 | 5.5668930151375235 kg | +| 已返回采样的总质量最大漂移 | 1.62537e-13 kg | + +所有已返回采样数值均有限。结果明确报告 `Native solve exceeded its time limit.`;超时后 RHS 拒绝求值产生的 CVODE Jacobian/setup 错误日志不能单独作为另一个雅可比故障的证据。这次运行证明修正模型能生成、构建并实际推进,不能表述为完整 10 s 回归通过,也不支持性能加速结论。未为取得完成标记放宽设置或追加运行。 + +材料保存在 `test/solver-newton-20260911/mql8/native-run/`:`input.xml`、`model-manifest.json`、`summary.json`、`execution/result.json` 和 `execution/worker.log`。构建键为 `403eb3539384555d3d2eb48951d2416f4754a7b1466f5c001a12879b4928eb7a`,该次执行时修正版 JSON 的 SHA256 为 `d034938422cc35e4e49183c76e900ad09ebbca250624fe3706fed76064e25038`,对应下述 P4 显示修复之前的工程。单次执行脚本是同目录上级的 `run_native_once.py`;复现必须改用新的运行目录,不能覆盖现有结果。 + +## P4 端口显示对齐 + +后续逐口复核发现,原通用 P4 符号的 port_3/port_4 方位与 AME 编号不一致。通用符号修正为默认 port_1 上、port_2 左、port_3 下、port_4 右后,对八路图纸逐个读取 `COMP_GEOMETRY` 和 `COMP_PORTS`:8 个 P4 均为 geometry 1,口位依次为下、右、上、左。因此修正版统一设为 `rotation=180`、`mirrored=false`,同时清理关联边上 6 组过期 `routePoints`;生成脚本保存了这项处理与每个原图端口的证据。 + +修复前后通过正式 `load_input` 导出的 XML **逐字节完全一致**,SHA256 为 `2804b33f04cabb3c10fcc3cc0843c35de17efbe1b6ee5e5e821edd9a5515a23d`;这次仅改变显示方位和旧路径缓存,没有改变物理端点、参数或执行设置。当前修正版 JSON 的 SHA256 为 `670977bef67e62d9c66e8af497bada208bd72a7301be45128d185d47282cf288`。验证记录在 `test/solver-newton-20260911/mql8/p4-display-verification.json`,此前的运行输入和结果保持原样。 + +## 全局 rtol=1e-8 的单次完整运行 + +用户确认全局网页/API 默认相对误差限改为 1e-8 后,对当前显示修正后的 JSON 再执行一次。脚本从正式 `simulation_config` 读取设置,并在运行前断言其确为 1e-8,未通过命令行或局部配置覆盖默认值。使用 BDF/CVODE 7.4.0、0~10 s、最大步长 1e30 s、采样间隔 0.01 s;执行限额仍为 120 s,无预热或重复运行。 + +| 指标 | 单次运行结果 | +| --- | ---: | +| 完成状态 | success=true,completed | +| 实际推进终点 | 10 s | +| 纯求解墙钟 / CPU | 5.907054 s / 5.906099 s | +| 模型求值次数 | 74265 | +| 接受步数 / 拒绝步数 | 6974 / 454 | +| 雅可比求值 / LU 次数 | 475 / 1656 | +| 状态转换 / 求解器启动次数 | 1 / 4 | +| 返回采样数 / 范围 | 1002 / 0~10 s | +| 气体质量状态数 | 56 | +| 气体总质量最大漂移 | 1.15463e-14 kg | + +全部返回采样均为有限数值,132 个动态状态、1784 个输出和 0 个系统代数循环块保持一致。此次命中与 rtol=1e-7 运行相同的构建键 `403eb3539384555d3d2eb48951d2416f4754a7b1466f5c001a12879b4928eb7a`;P4 显示修复不改变执行 XML,相对误差限是运行时设置。两次运行精度条件不同,而且本轮与轻量前端检查并行,不能根据这两次时间记录宣称管流优化的加速倍数。 + +本轮证明当前修正版在全局 rtol=1e-8 下可以完整运行 10 s;不以此替代八路模型与 Amesim 曲线的独立一致性核验。此前 rtol=1e-7 超时记录保留,不改写为通过。 + +材料位于 `test/solver-newton-20260911/mql8/native-run-rtol1e8/`,包含 `input.xml`、`model-manifest.json`、`summary.json`、`execution/result.json` 和 `execution/worker.log`。执行脚本为其上级 `run_native_rtol1e8.py`,只有传入 `--execute` 才启动求解,且要求新的运行目录。输入 JSON SHA256 为 `670977bef67e62d9c66e8af497bada208bd72a7301be45128d185d47282cf288`。 + +## 八路真实网页补验 + +通过本次 `127.0.0.1:8011` 正式网页,直接导入最终 `test-mql-8-corrected.json`,以全局 `rtol=1e-8` 完整运行到10 s。曲线显示、CSV和结果文件下载、刷新后恢复均成功,无浏览器脚本异常。网页原始曲线和最终输出与独立原生八路运行逐值相同;CSV的1785列、1002行逐单元与原始数据相同。最后一轮为功能补验,不计入四路速度基准。 + +完整证据在 `test/solver-newton-20260911/browser-mql8-final/`:`summary.json`、`verification.json`、输入快照、原始/恢复后的`.simresult`、CSV和网页截图。初轮自动化曾因写死四路独有组件ID而在选择曲线时失败,应用求解已完成且无pageerror;脚本改为从当前工程选择真实存在的PNL0002后,上述流程全部通过。 diff --git a/docs/other/牛顿管流求根与四路网页验证-2026-09-11.md b/docs/other/牛顿管流求根与四路网页验证-2026-09-11.md new file mode 100644 index 0000000..633fc12 --- /dev/null +++ b/docs/other/牛顿管流求根与四路网页验证-2026-09-11.md @@ -0,0 +1,141 @@ +# 牛顿管流求根、AME 输入复核与网页验证(2026-09-11) + +目录整理补充:`tests/data` 的 JSON/XML 仅保留浏览器可导入的四路、八路 corrected JSON;参考曲线、旧工程和后端 XML 已迁到 `tests/baselines` / `tests/fixtures`,本文当前路径同步更新,文件内容及来源哈希不变。详见 [数据目录说明](../../tests/data/README.md)。 + +本轮在 `5d5a2e1843bc10cf7764c77cc2c1182c21a68df5` 的物性复用实现上,补全局部管流牛顿求根的区间保护和二分回退;按用户确认,将网页/API 与 Python CLI 默认相对误差限改为 `1e-8`。四路输入先按 AME 图纸核对,再做实际原生计算和浏览器验证。环境依赖、生成程序和运行数据均留在 Git 忽略目录,没有提交或推送 Git。 + +## 实现与适用范围 + +每条阻力支路在本次 RHS 所给定的两端压力、上游温度和物性下,局部求解一个标量方程: + +\[ +F(Re)=Re^2 f(Re,\varepsilon/D)-K=0,\qquad +Re=\frac{4|\dot m|}{\pi D\mu}. +\] + +`K` 由当前可压缩流动系数、上游状态和管径、管长确定。流向由压差决定;摩擦系数仍使用现有的层流、过渡和湍流连续混合公式。PNL0001/2/3 的容腔质量、能量状态由系统积分器推进,机械运动及接触事件也在系统层处理。局部求根没有把全系统变成一个单独的牛顿方程组。 + +本次保护包括: + +- 检查输入有限性及有效夹根区间;扩展区间和迭代均有 128 次上限。 +- 牛顿导数无效、候选点越界,或上一步牛顿未使残差至少减半时,回到保留区间的中点。 +- 对实际返回值重新检查归一化方程残差 `≤1e-9`,并检查流量修正量满足绝对 `1e-13 kg/s` 或相对 `1e-10` 的要求。 +- 处理小根下溢、大流量溢出、相邻浮点数造成的区间停滞;未满足标准就报告失败,不把最后一次迭代当作成功。 + +PNL00R 原有直接层流分支继续保留。物性状态复用、物理方程、整体雅各比构造和积分器类型没有因局部保护而更换。夹根连续性、有效输入和浮点可表示范围仍是条件;这套方法不保证任意系统必然完成,也不能消除接触刚性或所有积分误差。 + +## 先核对模型,再比较数值 + +交付输入为 [四路 JSON](../../tests/data/test-mql-4-corrected.json)、[同步 XML](../../tests/fixtures/amesim/test-mql-4-corrected.xml) 和 [八路修正版 JSON](../../tests/data/test-mql-8-corrected.json)。完整差异分别见 [四路复核](test-mql-4模型复核与输入修正-2026-09-11.md) 与 [八路修正](test-mql-8模型对照与修正-2026-09-11.md)。 + +四路 AME 为 `tests/data/test_mql_4.ame`。其 `.cir` 包含 63 个组件和 18 条建模管线,展开为 81 个公开元件、90 条连接、582 项公开参数、64 个动态状态。修正遗留管阻 `amesim_pnl00r_4.rr` 为 `1e-5`,并将 25 条涉及等价端口编号/阻力方向的连线与 AME 编号逐项对齐。 + +用户指出 `pnnode4` 看起来不同后,又直接解析了 `.cir` 的端口变量和连接记录,使用原始唯一别名逐个匹配,独立于原有同构映射。四个 P4NODE2 的真实压力、温度参考口均为 `port_2`,下列连接原本就是一致的: + +| AME 节点别名 | JSON 节点 | 参考口在两侧模型的连接对象 | +|---|---|---| +| pnnode4_16 | amesim_p4node2_1 | pneumatic_69 / amesim_pnl0001_13 的 port_2 | +| pnnode4_17 | amesim_p4node2_2 | pneumatic_68 / amesim_pnl0001_14 的 port_2 | +| pnnode4_18 | amesim_p4node2_3 | pneumatic_66 / amesim_pnl0001_15 的 port_2 | +| pnnode4_19 | amesim_p4node2_4 | pneumatic_65 / amesim_pnl0001_16 的 port_2 | + +发现的实际显示问题是:三个节点沿用旧工程旋转方向,且前端 P4NODE2 的 3、4 号端口锚点与 AME 几何定义互换。修复图标锚点及修正版工程的旋转/连线路由,物理连接编号和参数保持审计结果。另一次独立参数解析核对了表压加 `101300 Pa`、毫米/面积/容积/刚度/阻尼单位转换和状态初值,未发现新数值差异。公开适配项与 AME 直接字段在审计中单列。 + +`native-python-reference.json` 是 50 网络冻结数值基准;`test-mql-4-amesim-reference.json` 是 72 曲线、57 时刻的结果基准,两者都不是工程文件,未改写。AME 四路归档仍包含旧八路编译/结果缓存(132 状态);本轮没有可调用的 Amesim 安装,不能把该缓存当作四路运行。本次比较使用仓库中已有的官方重编译四路参考,原 AME SHA256 与当前文件相符。它不能证明全部时间点、全部输出或接触力都与 Amesim 一致。 + +## 相同精度下的求解速度 + +使用同一四路输入、同一物性复用实现、同一 SUNDIALS 7.4.0/GCC 13.3.0 环境。BDF,0~10 s,输出步长 0.01 s,最大步长 `1e30`,各算法均使用 `rtol=1e-8`。每个版本预热一次、正式运行三次,交错运行次序;下表为中位数。 + +| 局部管流算法 | 纯求解时间 | 进程全程 | +|---|---:|---:| +| 旧固定点迭代(16/64 次上限、半步松弛) | 1.3309 s | 2.3479 s | +| 前次提交的牛顿实现 | 0.9171 s | 1.9009 s | +| 本次补全保护的牛顿+二分回退 | 0.9097 s | 1.8962 s | + +本次相对固定点版本纯求解耗时减少 **31.6%**,约为其 **1.46 倍速度**。相对已经采用牛顿的前次提交,约 0.8% 差异不足以认定另有显著加速;本轮新增保护的主要收益是失败处理和收敛条件完整性。前次牛顿与本次版本的全部采样序列、最终输出和最终状态逐值完全相同。 + +固定点版本是在明确基线提交的 C 源码中,仅替换回原来的局部半步松弛迭代,不是运行旧版整个应用。因此这张表隔离了管流算法差异,不混入物性复用收益。基准脚本检查替换位置,并保存实际源码、输入快照、哈希、构建键和原始结果。 + +此前 `rtol=1e-7` 的同机基准:固定点 1.8731 s、前次牛顿 1.3149 s、本次 1.3074 s。收紧精度后,本模型本次纯求解反而降为 0.9097 s,RHS 次数从 32029 降至 21499;自适应积分路径改变,不能据此推断所有系统收紧误差限都会加速。 + +## Amesim 曲线验收 + +网页使用的默认误差限由 `1e-7` 改为 `1e-8`,绝对误差限继续按状态类型生成。原误差门槛不放宽。默认 `1e-7` 在确切端口编号对齐后最大温差为 `0.0168374 K`,超过 `0.015 K` 门槛;收紧后为 `0.00882331 K`。 + +| 物理量 | 最大绝对误差 | 既有门槛 | +|---|---:|---:| +| 压力 | 199.671 Pa | 250 Pa | +| 温度 | 0.00882331 K | 0.015 K | +| 位移 | 0.807942 μm | 2 μm | +| 速度 | 2.73964 μm/s | 10 μm/s | +| 质量流量 | 0.00707919 g/s | 0.03 g/s | + +72 条曲线在 57 个保存的 Amesim 时刻全部通过。四路运行到 10 s,输出 1003 点(含额外事件点),全量数值有限;闭合气路总质量最大漂移 `2.4425e-14 kg`。误差用原始 native/网页导出数据线性插值到参考时刻计算,没有平滑数据或删去不利比较点。 + +等价端口重排改变了浮点加法次序,从而影响自适应积分轨迹。旧/新端口布置在 10 个相同状态上的 RHS 探测,最大缩放差约 `3.95e-15`;新保护与前次牛顿的整条结果也相同。因此收紧系统精度处理的是全系统积分敏感性,不能归因于局部二分保护本身。 + +既有 LSTP00A 碰撞事件力峰仍约 `1.50e11 N`,不在这 72 曲线参考范围内。局部求根和本次 `rtol` 调整没有解决该已知问题;原始结果仍保留这些峰值。详见 [此前接口力报告](test-mql-4网页仿真与LSTP00A接口力对比-2026-09-11.md)。 + +## 最终真实浏览器验证 + +在本次独立服务 `http://127.0.0.1:8011` 运行正式 Vite 构建,Playwright 驱动 Chromium 151.0.7922.34,未模拟 API 或注入预制仿真结果。使用图标修正后的交付 JSON(SHA256 `542494eed8e235cb36eae5f80a0776b812e547d9215fa370949b0882834ebc8b`)。预热一次、正式运行三次: + +| 指标 | 三次正式运行中位数 | +|---|---:| +| C 内部纯求解 | 0.9249 s | +| 原生进程全程 | 1.8465 s | +| 点击运行至 HTTP 流结束 | 2.7666 s | +| 点击运行至结果可查看 | 3.5504 s | + +“结果可查看”计时包含实际响应、浏览器结果处理、IndexedDB 保存完成,以及切换到结果页并出现导出控件;没有把它称为纯计算时间。各次实际诊断均为 `rtol=1e-8`、10 s 完成、21499 次 RHS、4970 个接受步、243 个拒绝步、1003 个采样点。 + +验证了 JSON 导入、真实仿真、图形选择和曲线绘制、结果文件下载、CSV 下载、刷新恢复,以及将下载的结果文件重新导入并显示温度曲线。无 `pageerror`。刷新前后完整结果逐值相等;网页原始结果与独立原生基准的全部曲线和最终输出逐值相同;CSV 的 883 列、1003 行逐单元与网页原始结果一致。结果参考比较也直接针对最终网页导出的 `.simresult` 重做,全部通过。 + +证据为 `test/solver-newton-20260911/browser-mql4-final-rtol1e8/` 中的输入快照、4 次 `.simresult`、`result.csv`、`restored.simresult`、`summary.json`、`native-equality.json`、`csv-equality.json`、`temperature-view-proof.json` 和页面截图。`result.png` 为首次选择的质量曲线;`result-temperature.png` 为精确选择温度后的截图。网页显示检查中发现脚本的宽泛 K 文本匹配也会命中 kg,已将可复现脚本改为精确匹配单位 K;该选择问题不影响任何原始数值或计时。 + +## 八路修正版的完成性检查 + +`test-mql-8-corrected.json` 对齐 157 个元件、178 条连接、1092 个公开参数。修正公共机械质量的 4 项参数(质量 90000→100 kg、限位 -0.72/0→-1/1 m、stoptype 1→4);修改 62 条边的端口,其中 7 个 P4NODE2 的参考口来源原先确实接错,其余包含共享容腔/汇总支路编号对齐。最大积分步长改为 AME 保存的 `1e30`,8 个 P4 显示方向也与 AME 对齐。原 JSON 保留为审计来源,新的实际运行使用 corrected 文件。 + +同一修正后执行 XML 和同一内核,在旧 `rtol=1e-7` 下 120 s 超时,推进至 5.144858167 s。按用户确认的新全局 `rtol=1e-8`,单次原生运行完整到 10 s:纯求解 5.9071 s,74265 次 RHS,6974 个接受步、454 个拒绝步,1002 个返回采样,全部有限;56 个气体质量状态的总质量最大漂移 `1.15463e-14 kg`。两次精度不同且只做单次完成性检查,不将其时间比值当作管流优化加速比。 + +这证明修正版在当前默认设置下可以完整运行,不等同于八路全部曲线已经与 Amesim 一致。本轮八路没有重跑 Amesim 或制作曲线参考。原始数据位于 `test/solver-newton-20260911/mql8/native-run-rtol1e8/`,旧超时记录仍在同级 `native-run/`。输入 JSON SHA256 为 `670977bef67e62d9c66e8af497bada208bd72a7301be45128d185d47282cf288`。 + +## 回归检查 + +- 9 个相关后端模块共 **38 项测试全部通过**,无跳过:局部求根、管路物理及四路 Amesim 门槛、物性复用、管流缓存、原生代码生成/打包/API、依赖顺序、50 网络目录基准、原生唯一后端及预热。 +- 正式前端 TypeScript/Vite 构建通过。**2 项真实 DOM 几何测试通过**:用 AME `COMP_PORT/PORT_POS` 原始坐标验证旋转后的四个实际 Handle,并验证镜像后粗线仍指向 `port_2`;既有 PN3/P4 图形测试同时通过。 +- 将原生程序复制到新目录,清空 `PATH/PYTHONPATH` 并去除 `LD_LIBRARY_PATH`,初始化得到 64 个状态,独立 BDF 0~10 s 运行成功。二进制哈希与打包清单一致。 + +日志分别为 `final-backend-tests.log`、`frontend-final-build.log`、P4 几何测试证据及 `standalone-package-verification.json`。求根单元测试含独立摩擦公式与二分参考、过渡区邻接浮点数、坏导数注入、无夹根/无可接受根及极端流量尺度,不以运行速度作为正确性断言。 + +## 环境与复现 + +Python 包使用现有 `.venv`。新增原生依赖放在 `.venv/native/sundials-7.4.0/`,浏览器缺失共享库放在 `.venv/native/browser-libs/`;没有安装到系统目录。`.venv/`、`app/data/`、`test/` 均已由现有 Git 规则忽略。只交付可读的环境安装脚本 `bat/setup-native-linux.sh`,不交付环境二进制。Linux 原生构建和预热已补齐,保留 Windows 路径和 DLL 打包分支。 + +```sh +# 无需 Amesim 应用的输入核查 +.venv/bin/python tests/model_audit/audit_mql4_model.py --check + +# 三版本真实原生基准,输出必须选新目录 +.venv/bin/python tests/manual/benchmark_native_pipe_solver.py \ + tests/data/test-mql-4-corrected.json --output-dir test/new-pipe-benchmark --runs 3 + +# 结果比较;绘图使用已有系统 Python 的 NumPy/Matplotlib +python3.12 tests/manual/compare_native_amesim.py \ + test/solver-newton-20260911/benchmark-mql4-rtol1e8/guarded-newton/run-1/result.json \ + --output-dir test/new-amesim-comparison --plot +``` + +运行证据根目录为 `test/solver-newton-20260911/`。`benchmark-mql4-rtol1e8/summary.json` 包含公平计时、实际精度和哈希;`comparison-mql4-rtol1e8/` 包含逐曲线 CSV/JSON 及 SVG/PNG 对照图;`temperature-diagnostics/` 保留选择新默认精度的诊断。全部原始结果与可执行程序保留在本机忽略目录,未加入 Git。 + +## 八路真实网页补验 + +通过本次 `127.0.0.1:8011` 正式网页,直接导入最终 `test-mql-8-corrected.json`,以全局 `rtol=1e-8` 完整运行到10 s。曲线显示、CSV和结果文件下载、刷新后恢复均成功,无浏览器脚本异常。网页原始曲线和最终输出与独立原生八路运行逐值相同;CSV的1785列、1002行逐单元与原始数据相同。最后一轮为功能补验,不计入四路速度基准。 + +完整证据在 `test/solver-newton-20260911/browser-mql8-final/`:`summary.json`、`verification.json`、输入快照、原始/恢复后的`.simresult`、CSV和网页截图。初轮自动化曾因写死四路独有组件ID而在选择曲线时失败,应用求解已完成且无pageerror;脚本改为从当前工程选择真实存在的PNL0002后,上述流程全部通过。 + +## 验证快照与并行工作 + +本轮数值基准保留实际 C 源码、构建清单、程序与输入快照;网页证据记录了具体输入 SHA256 和实际结果。收尾检查时,工作区出现了并行修改的 `App.tsx`、`SimulationResultsView.tsx`、边线路由、压力单位和相关测试文件。本轮没有覆盖这些改动,也不将此前38项后端测试和网页构建结论扩大为后续并行改动的验证。当前任务核心文件哈希另存于 `test/solver-newton-20260911/final-task-source-hashes.json`。本轮实际执行环境为Linux,Windows构建分支保留,但本轮未在Windows重新运行。 diff --git a/docs/standard/native-evaluation-schedule.md b/docs/standard/native-evaluation-schedule.md index debb54f..7e77a9c 100644 --- a/docs/standard/native-evaluation-schedule.md +++ b/docs/standard/native-evaluation-schedule.md @@ -34,7 +34,14 @@ - 压力与焓同时互相依赖时,在这个局部块内保留嵌套求解。迭代到上限仍未满足条件,或出现非有限数值,返回求值失败;不把未完成的局部闭合当作成功。 - 条件表达式记录所有可能分支的依赖,覆盖反向与零流量。此做法较为保守:某个特定工况下能进一步简化的关系仍可能留在小循环内。本版没有实现运行时分支特化。 -管路自身的摩擦/流量求根属于元件内部计算。2026-09-11起,PNL管流使用带区间保护的局部Newton求根,保留既有物理分支,并在实际返回点要求方程相对残差不超过 `1e-9`;不再把达到16/64次上限的迭代值直接当成解。上面的系统压力、焓闭合策略,以及积分器和系统雅可比策略仍然保留。系统循环的来源检查和局部划分不代表任意新非线性方程都已得到数值求解支持。 +管路自身的摩擦/流量求根属于元件内部计算。PNL管流将既有阻力关系写成 `Re² × f(Re) = K`,使用带区间保护的局部Newton求根,保留可压缩流动、摩擦系数混合、流向和近平衡平滑公式。求根器遵守: + +- 先检查有限输入,并通过最多128次上界试算显式确认夹根;无法得到有限且满足上界符号的值时失败,不进入未受保护的迭代。 +- Newton试点越界、斜率非正或非有限时二分回退。一次Newton试点若未将方程绝对残差至少减半,下一步强制二分,使区间内的无效小步也能触发保护。 +- 在实际返回候选点检查方程相对残差不超过 `1e-9`,同时保留流量变化的绝对 `1e-13 kg/s` 或相对 `1e-10` 判据;比较过程避免流量尺度溢出造成误判。解析低雷诺数分支也核对残差及返回流量的有限性。 +- 求根最多128轮。区间已缩至相邻浮点值时核验两端候选,仍不满足上述条件则失败;非有限方程、无法夹根、迭代耗尽均返回失败值,不将最后一次试算当成解。 + +PNL00R原有的直接层流分支(对应 `Re ≤ 1000`)继续按其解析式计算;该保留分支不经过上述混合摩擦关系的求根检查。区间法的收敛仍以区间内方程连续等条件为前提,不代表任意经验公式、任意有限输入或整套耦合系统都能成功求解。上面的系统压力、焓闭合策略,以及积分器和系统雅可比策略均保留。系统循环的来源检查和局部划分不代表任意新非线性方程都已得到数值求解支持。 每次 `model_eval` 还建立调用者持有的 `NativePropertyCache`,并显式传给局部辅助函数。气体状态准备后登记已有温度、焓和密度;阀口、管路及诊断计算共享相同状态中的有效物性。压力、焓或介质参数改变时按完整输入重新查询,缓存不跨系统试算复用。记录容量满时使用正常计算回退。 @@ -57,4 +64,4 @@ operations.append(Computation.assignment( 对应测试:`tests/test_native_schedule.py`、`tests/test_native_catalog.py`。本轮模型对照记录见 [计算排序验证记录](../other/C计算依赖排序验证-2026-09-10.md)。 -物性复用和管流求根还由 `tests/test_native_properties.py`、`tests/test_native_pipe_physics.py` 覆盖。实现边界与实测结果见 [物性复用与管流求根实现及验证](../other/物性复用与管流求根实现及验证-2026-09-11.md)。 +物性复用和管流求根还由 `tests/test_native_properties.py`、`tests/test_native_pipe_physics.py` 覆盖。`tests/test_native_pipe_solver.py` 仅依赖Python标准库和C编译器,通过独立二分、切换邻点及临时测试副本中的斜率/方程故障注入,验证区间保护、残差、回退与明确失败。实现边界与实测结果见 [物性复用与管流求根实现及验证](../other/物性复用与管流求根实现及验证-2026-09-11.md)。 diff --git a/docs/standard/system-xml-v3.md b/docs/standard/system-xml-v3.md index df08175..5244c66 100644 --- a/docs/standard/system-xml-v3.md +++ b/docs/standard/system-xml-v3.md @@ -138,6 +138,8 @@ v3 不保存 `name/componentType/x/y/rotation/mirrored`。其中: ### 5.2 `Parameter` +平台压力统一采用绝压,默认显示单位为 `Pa`,可切换为 `kPa`、`MPa` 或 `bar`。这些显示单位只作比例换算,不增加或减去大气压偏移,`1 bar = 100000 Pa`。工程 JSON 中的数值参数保存 SI 值,`parameterUnits` 保存显示单位;表达式按保存的显示单位求值后换算到 SI。例如输入 `2.5 bar` 时,XML 的压力参数值为 `250000`。后端仿真输入与压力结果均使用绝压 Pa。 + ```xml ``` diff --git a/docs/update-log/更新日志-2026-09-11.md b/docs/update-log/更新日志-2026-09-11.md index 7fb85e4..f8395b2 100644 --- a/docs/update-log/更新日志-2026-09-11.md +++ b/docs/update-log/更新日志-2026-09-11.md @@ -1,5 +1,9 @@ # 2026-09-11:C 管路验证版合入网页后端 +数据目录整理:按用户要求,`tests/data` 的 JSON/XML 仅保留可在浏览器导入、且已与对应 AME 参数和端口对齐的 `test-mql-4-corrected.json` 与 `test-mql-8-corrected.json`。其余8份资料逐字节迁到 `tests/fixtures` / `tests/baselines`;活动测试、审计脚本、CI、基准清单路径和当前报告已同步。两个工程真实浏览器导入及XML下载通过,导出的物理参数、连接和仿真设置与工程输入一致;8项路径/清单相关回归通过。具体去向见 [目录说明](../../tests/data/README.md)。 + +最新补充:本轮完成管流牛顿迭代的有限夹根、残差进展检查与二分回退;按用户确认将网页/API 默认 `rtol` 改为 `1e-8`。重新核对四路/八路 AME 图纸并交付 `tests/data/test-mql-4-corrected.json` 与 `tests/data/test-mql-8-corrected.json`;用户复核发现的 P4NODE2 显示方向和 3/4 号端口锚点错误一并修正。相同新精度下,相比局部半步松弛迭代,四路纯求解中位耗时 1.3309→0.9097 s(减少31.6%);72条已有Amesim参考曲线均通过原门槛,最大温差0.00882331 K。38项后端测试与2项P4真实端口几何测试通过;四路最终网页从点击到结果可查看中位3.55 s。八路修正版在新精度下单次完整完成10 s,纯求解5.91 s。最新验收范围、网页证据和八路运行限制以 [本轮完整报告](../other/牛顿管流求根与四路网页验证-2026-09-11.md) 为准;下文旧默认精度和旧输入路径为此前历史记录。 + 当天后续更新:已将物性状态复用、等熵计算中的密度复用、焓直接计算,以及检查残差的管流求根接入正式C内核和两条生成路径。最终25项回归通过;MQL4纯求解中位耗时3.138→1.391 s,skill-test 0.255→0.139 s。已启动更新后的本机后台并以真实HTTP请求验证。详见 [实现与验证报告](../other/物性复用与管流求根实现及验证-2026-09-11.md)。下文为当天较早一次合入的历史记录。 后续网页检查补充:LSTP00A 接口力在新增碰撞事件点存在极短高峰,原有共同采样点回归没有覆盖该现象。详情及对原曲线一致性结论的限定见 [接口力对比报告](../other/test-mql-4网页仿真与LSTP00A接口力对比-2026-09-11.md)。 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c822b0d..7927720 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -29,6 +29,7 @@ import { FileDown, FileUp, FlipHorizontal2, + FlipVertical2, FolderOpen, Grip, ListChecks, @@ -5950,6 +5951,50 @@ function FlowWorkbench() { ); }, [appendConsoleEntry, recordHistory, selectedNodeId, setEdges, setNodes]); + const mirrorSelectionVertically = useCallback(() => { + const selectedIds = new Set( + nodesRef.current + .filter((node) => node.selected || node.id === selectedNodeId) + .map((node) => node.id), + ); + if (selectedIds.size === 0) { + appendConsoleEntry("warning", "请先选择需要镜像的组件"); + return; + } + recordHistory(); + const previousNodes = nodesRef.current; + const mirroredNodes = previousNodes.map((node) => + selectedIds.has(node.id) + ? { + ...node, + data: { + ...node.data, + // Reflect in canvas coordinates: Sy R(theta) Sx(m) = R(180-theta) Sx(!m). + rotation: normalizeNodeRotation((540 - normalizeNodeRotation(node.data.rotation)) % 360), + mirrored: !node.data.mirrored, + }, + } + : node, + ); + const { remainingEdges, removedEdges } = disconnectInvalidatedContactEdges( + previousNodes, + mirroredNodes, + edgesRef.current, + selectedIds, + ); + nodesRef.current = mirroredNodes; + edgesRef.current = remainingEdges; + setNodes(mirroredNodes); + if (removedEdges.length > 0) { + setEdges(remainingEdges); + appendConsoleEntry( + "info", + `镜像使接口分离,已断开 ${removedEdges.length} 个接触连接`, + ); + } + appendConsoleEntry("info", `已竖直镜像 ${selectedIds.size} 个组件`); + }, [appendConsoleEntry, recordHistory, selectedNodeId, setEdges, setNodes]); + const handleNodesChange = useCallback( (changes: NodeChange[]) => { const removesNodes = changes.some((change) => change.type === "remove"); @@ -7391,7 +7436,7 @@ function FlowWorkbench() { const loadedEdges = edgeNormalization.edges; setNodes(loadedNodes); setEdges(loadedEdges); - setProjectName(project.name || "untitled"); + setProjectName(project.name.trim() || "untitled"); setSimulationConfig({ ...defaultSimulationConfig, ...project.simulation }); setSelectedNodeId(null); setSelectedEdgeId(null); @@ -7472,7 +7517,8 @@ function FlowWorkbench() { return; } applyProject( - project, + // Older entries may still contain a name that differs from their storage key. + { ...project, name }, `工程已加载:${project.nodes.length} 个组件,${project.edges.length} 条连接`, ); } catch (error) { @@ -7505,7 +7551,11 @@ function FlowWorkbench() { if (!project) { throw new Error("文件不是有效的 ReactFlow 工程 JSON"); } - applyProject(project, `已导入工程:${file.name}`); + const importedName = file.name.trim().replace(/\.json$/i, "").trim(); + applyProject( + { ...project, name: importedName || project.name.trim() || "untitled" }, + `已导入工程:${file.name}`, + ); } catch (error) { appendConsoleEntry("error", `导入失败:${formatError(error)}`); } @@ -8058,7 +8108,7 @@ function FlowWorkbench() { } if ( pendingPasteRef.current && - ((event.shiftKey && !commandKey && !event.altKey && key === "h") || + ((event.shiftKey && !commandKey && !event.altKey && ["h", "v"].includes(key)) || (commandKey && ["a", "c", "v", "y", "z"].includes(key))) ) { event.preventDefault(); @@ -8067,6 +8117,9 @@ function FlowWorkbench() { if (event.shiftKey && !commandKey && !event.altKey && key === "h") { event.preventDefault(); mirrorSelection(); + } else if (event.shiftKey && !commandKey && !event.altKey && key === "v") { + event.preventDefault(); + mirrorSelectionVertically(); } else if (commandKey && key === "z") { event.preventDefault(); event.shiftKey ? redo() : undo(); @@ -8093,6 +8146,7 @@ function FlowWorkbench() { cancelPortConnection, copySelection, mirrorSelection, + mirrorSelectionVertically, pasteSelection, redo, rotatePendingPaste, @@ -8753,6 +8807,12 @@ function FlowWorkbench() { onClick={mirrorSelection} pressed={allSelectedNodesMirrored} /> +