From 5ba0843500062cef9a831279c26bd3ba016b1c3c Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 8 Aug 2026 11:27:31 +0000 Subject: [PATCH 1/2] Build the C++ bindings against the library beside them AngouriMath.CPP.Exporting referenced AngouriMath as a NuGet package pinned to 1.4.0-preview.2 -- a preview older than the 1.4.0 release itself. So the C++ bindings, and the CPPBuild and CPPTest workflows that exercise them, were built and tested against a published package rather than against the source in the same repository. Nothing changed here since that preview reached them, and shipping 2.0 would have left the C++ layer bound to a 1.4 preview. Three things were in the way, and only the first was obvious. **The library could not be AOT compiled.** It targeted net7.0, and ahead-of- time compilation is not supported for net7.0 on osx-arm64. Fixed by the retarget in the commit below this one, which is why that has to land first. **PublishAot was passed as a global property.** `-p:PublishAot=true` on the command line reaches every project in the graph, so the AOT support check ran against netstandard2.0 -- both AngouriMath's own netstandard2.0 target and the Analyzers project, which is netstandard2.0 because Roslyn requires it. Neither can be AOT compiled, and neither needs to be. Declaring PublishAot in the project that actually publishes keeps it off the rest, so it moves into AngouriMath.CPP.Exporting.csproj and out of the two workflow commands. **The reference pulled in every target framework.** Pinned to net10.0, as UnitTests already does. Measured, from a clean bin and obj: dotnet publish -p:NativeLib=Shared -p:SelfContained=true -r linux-x64 -c release -> AngouriMath.CPP.Exporting.so, 4393256 bytes, no errors and no IL warnings AngouriMath.Terminal.Lib had the same kind of pin on AngouriMath.Interactive, but it is already commented out there and a project reference is what it uses. This was the last live one. Co-Authored-By: Claude Opus 5 --- .github/workflows/CPPBuild.yml | 4 ++-- .github/workflows/CPPTest.yml | 4 ++-- .../AngouriMath.CPP.Exporting.csproj | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/CPPBuild.yml b/.github/workflows/CPPBuild.yml index cf26d23e9..9d570ae91 100644 --- a/.github/workflows/CPPBuild.yml +++ b/.github/workflows/CPPBuild.yml @@ -55,14 +55,14 @@ jobs: cd Sources/Wrappers/AngouriMath.CPP.Exporting mkdir ../AngouriMath.CPP.Importing/out-x64 call "C:\Program Files\Microsoft Visual Studio\18\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - dotnet publish -p:NativeLib=Shared -p:SelfContained=true -p:PublishAot=true -r ${{ matrix.flag }} -c release + dotnet publish -p:NativeLib=Shared -p:SelfContained=true -r ${{ matrix.flag }} -c release - name: 'Building the library into native for Linux & MacOS' if: ${{ matrix.os != 'windows-latest' }} run: | cd Sources/Wrappers/AngouriMath.CPP.Exporting mkdir ../AngouriMath.CPP.Importing/out-x64 - dotnet publish -p:NativeLib=Shared -p:SelfContained=true -p:PublishAot=true -r ${{ matrix.flag }} -c release + dotnet publish -p:NativeLib=Shared -p:SelfContained=true -r ${{ matrix.flag }} -c release - name: 'Renaming the library for Linux' if: ${{ matrix.os == 'ubuntu-latest' }} diff --git a/.github/workflows/CPPTest.yml b/.github/workflows/CPPTest.yml index 351f8e7d6..47820ae58 100644 --- a/.github/workflows/CPPTest.yml +++ b/.github/workflows/CPPTest.yml @@ -47,14 +47,14 @@ jobs: cd Sources/Wrappers/AngouriMath.CPP.Exporting mkdir ../AngouriMath.CPP.Importing/out-x64 call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - dotnet publish -p:NativeLib=Shared -p:SelfContained=true -p:PublishAot=true -r ${{ matrix.flag }} -c release + dotnet publish -p:NativeLib=Shared -p:SelfContained=true -r ${{ matrix.flag }} -c release - name: 'Building the library into native for Linux & MacOS' if: ${{ matrix.os != 'windows-latest' }} run: | cd Sources/Wrappers/AngouriMath.CPP.Exporting mkdir ../AngouriMath.CPP.Importing/out-x64 - dotnet publish -p:NativeLib=Shared -p:SelfContained=true -p:PublishAot=true -r ${{ matrix.flag }} -c release + dotnet publish -p:NativeLib=Shared -p:SelfContained=true -r ${{ matrix.flag }} -c release - name: 'Preparing tests' run: | diff --git a/Sources/Wrappers/AngouriMath.CPP.Exporting/AngouriMath.CPP.Exporting.csproj b/Sources/Wrappers/AngouriMath.CPP.Exporting/AngouriMath.CPP.Exporting.csproj index 8c8cbdd96..3288a2805 100644 --- a/Sources/Wrappers/AngouriMath.CPP.Exporting/AngouriMath.CPP.Exporting.csproj +++ b/Sources/Wrappers/AngouriMath.CPP.Exporting/AngouriMath.CPP.Exporting.csproj @@ -6,6 +6,7 @@ true true + true @@ -19,6 +20,8 @@ - + + TargetFramework=net10.0 + \ No newline at end of file From 427a4336e092725fd6f7f6fddb10700207298927 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 8 Aug 2026 12:10:47 +0000 Subject: [PATCH 2/2] Check the C++ integral tests by differentiating, not by string IntTest1 and IntTest2 compared Integrate's printed form against the literals "x2 / 2" and "x2 / 2 + 2x". Neither says what it looks like: "x2" parses as a variable of that name, not as a square. And neither survives Integrate gaining its constant of integration, which is why these two were the only failures of the twenty-four once the bindings were built against the current source instead of a 1.4.0-preview.2 package. The printed form was never the property under test. Both now differentiate the antiderivative and check the difference from the integrand simplifies to zero, which is what the rest of the suite does and what AGENTS.md asks for. That the form was the problem rather than the mathematics is visible in the second case: d/dx of the antiderivative of x + 2 prints as 2 + x, so even a corrected string would have been comparing arrangements rather than values. Measured: d/dx(x ^ 2 / 2 + C) is x, d/dx(x ^ 2 / 2 + 2 * x + C) is 2 + x, and both differences simplify to 0. cmake is not available here, so CTest on CI is the check. Co-Authored-By: Claude Opus 5 --- .../CPPWrapperUnitTests/tests/RunTests.cpp | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Sources/Tests/CPPWrapperUnitTests/tests/RunTests.cpp b/Sources/Tests/CPPWrapperUnitTests/tests/RunTests.cpp index ad0300592..61ea0e60d 100644 --- a/Sources/Tests/CPPWrapperUnitTests/tests/RunTests.cpp +++ b/Sources/Tests/CPPWrapperUnitTests/tests/RunTests.cpp @@ -1,6 +1,7 @@ #include #include #include +#include TEST(RunTests, ParsingTest1) { auto src = "x / 2 + 3"; @@ -54,20 +55,25 @@ TEST(RunTests, DiffTest2) { EXPECT_EQ(expected.ToString(), actual.ToString()); } +// An antiderivative is checked by differentiating it back, not by comparing its printed +// form. These two compared against the literals "x2 / 2" and "x2 / 2 + 2x", which do not +// say what they look like -- "x2" parses as a variable of that name rather than as a +// square -- and which could not survive Integrate gaining its constant of integration. +// The printed form is not the property under test: d/dx of the answer is. +static void ExpectAntiderivative(const char* integrand) { + AngouriMath::Entity entity = integrand; + auto back = entity.Integrate("x").Differentiate("x"); + auto difference = AngouriMath::Entity( + "(" + back.ToString() + ") - (" + std::string(integrand) + ")"); + EXPECT_EQ("0", difference.Simplify().ToString()); +} + TEST(RunTests, IntTest1) { - auto src = "x"; - AngouriMath::Entity entity = src; - auto actual = entity.Integrate("x"); - auto expected = AngouriMath::Entity("x2 / 2"); - EXPECT_EQ(expected.ToString(), actual.ToString()); + ExpectAntiderivative("x"); } TEST(RunTests, IntTest2) { - auto src = "x + 2"; - AngouriMath::Entity entity = src; - auto actual = entity.Integrate("x"); - auto expected = AngouriMath::Entity("x2 / 2 + 2x"); - EXPECT_EQ(expected.ToString(), actual.ToString()); + ExpectAntiderivative("x + 2"); } TEST(RunTests, LimTest1) {