diff --git a/.github/workflows/Nuget.yml b/.github/workflows/Nuget.yml index 95ec524fa..57962370e 100644 --- a/.github/workflows/Nuget.yml +++ b/.github/workflows/Nuget.yml @@ -1,11 +1,6 @@ -name: 'Upload to MyGet and NuGet' - -run-name: ${{ github.event_name == 'push' && 'Upload to MyGet' || 'Upload to NuGet' }} +name: 'Upload to NuGet' on: - push: - branches: - - master release: types: [published] @@ -23,49 +18,31 @@ jobs: - uses: actions/checkout@v6 - - name: 'Determine version and target' + - name: 'Determine version' id: version run: | - if [[ "${{ github.event_name }}" == "push" ]]; then - # MyGet versioning - commithash=$(git rev-parse --short HEAD) - currtime=$(date +%s) - echo "commit hash is $commithash" - echo "time is $currtime" - version=10.0.0-master-$currtime-$commithash - echo "version is $version" - echo "version=$version" >> $GITHUB_OUTPUT - echo "apikey=${{ secrets.MYGET_KEY }}" >> $GITHUB_OUTPUT - echo "source=myget" >> $GITHUB_OUTPUT - echo "target=MyGet" >> $GITHUB_OUTPUT - else - # NuGet versioning from release tag - tagname="${{ github.event.release.tag_name }}" - - # Validate tag name exists - if [[ -z "$tagname" ]]; then - echo "Error: Release tag name is empty" - exit 1 - fi - - # Remove 'v' prefix if present (e.g., v1.0.0 -> 1.0.0) - version="${tagname#v}" - echo "Release tag is $tagname" - echo "Package version is $version" - - # Validate version format (SemVer 2.0.0: X.Y.Z[-prerelease][+build]) - if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then - echo "Error: Invalid version format '$version'." - echo "Expected semantic version (e.g., 1.0.0, 1.0.0-beta.1, 1.0.0+build.123)" - exit 1 - fi - - echo "version=$version" >> $GITHUB_OUTPUT - echo "apikey=${{ secrets.NUGET_KEY }}" >> $GITHUB_OUTPUT - echo "source=nuget.org" >> $GITHUB_OUTPUT - echo "target=NuGet" >> $GITHUB_OUTPUT + tagname="${{ github.event.release.tag_name }}" + + # Validate tag name exists + if [[ -z "$tagname" ]]; then + echo "Error: Release tag name is empty" + exit 1 fi - + + # Remove 'v' prefix if present (e.g., v1.0.0 -> 1.0.0) + version="${tagname#v}" + echo "Release tag is $tagname" + echo "Package version is $version" + + # Validate version format (SemVer 2.0.0: X.Y.Z[-prerelease][+build]) + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then + echo "Error: Invalid version format '$version'." + echo "Expected semantic version (e.g., 1.0.0, 1.0.0-beta.1, 1.0.0+build.123)" + exit 1 + fi + + echo "version=$version" >> $GITHUB_OUTPUT + - name: 'Pack and publish AngouriMath' run: | cd Sources/AngouriMath @@ -73,7 +50,7 @@ jobs: dotnet build AngouriMath.csproj -c release dotnet pack AngouriMath.csproj -c release -p:PackageVersion=${{ steps.version.outputs.version }} cd bin/release - dotnet nuget push AngouriMath.${{ steps.version.outputs.version }}.nupkg --api-key ${{ steps.version.outputs.apikey }} --source "${{ steps.version.outputs.source }}" + dotnet nuget push AngouriMath.${{ steps.version.outputs.version }}.nupkg --api-key ${{ secrets.NUGET_KEY }} --source nuget.org - name: 'Pack and publish AngouriMath.FSharp' run: | @@ -82,7 +59,7 @@ jobs: dotnet build -c Release dotnet pack -c Release -p:PackageVersion=${{ steps.version.outputs.version }} cd bin/Release - dotnet nuget push AngouriMath.FSharp.${{ steps.version.outputs.version }}.nupkg --api-key ${{ steps.version.outputs.apikey }} --source "${{ steps.version.outputs.source }}" + dotnet nuget push AngouriMath.FSharp.${{ steps.version.outputs.version }}.nupkg --api-key ${{ secrets.NUGET_KEY }} --source nuget.org - name: 'Pack and publish AngouriMath.Interactive' run: | @@ -91,7 +68,7 @@ jobs: dotnet build -c Release dotnet pack -c Release -p:PackageVersion=${{ steps.version.outputs.version }} cd bin/Release - dotnet nuget push AngouriMath.Interactive.${{ steps.version.outputs.version }}.nupkg --api-key ${{ steps.version.outputs.apikey }} --source "${{ steps.version.outputs.source }}" + dotnet nuget push AngouriMath.Interactive.${{ steps.version.outputs.version }}.nupkg --api-key ${{ secrets.NUGET_KEY }} --source nuget.org - name: 'Pack and publish AngouriMath.Terminal' run: | @@ -100,4 +77,4 @@ jobs: dotnet build -c Release dotnet pack -c Release -p:PackageVersion=${{ steps.version.outputs.version }} cd bin/Release - dotnet nuget push AngouriMath.Terminal.${{ steps.version.outputs.version }}.nupkg --api-key ${{ steps.version.outputs.apikey }} --source "${{ steps.version.outputs.source }}" + dotnet nuget push AngouriMath.Terminal.${{ steps.version.outputs.version }}.nupkg --api-key ${{ secrets.NUGET_KEY }} --source nuget.org diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..d085ada98 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,158 @@ +# AGENTS.md + +For AI agents working on AngouriMath. Humans: [CONTRIBUTING.md](CONTRIBUTING.md) is yours, and +everything below applies to you too. + +AngouriMath is a computer algebra system. The thing being built is *mathematics*, and the code is +how it is expressed. Read this as instructions for doing mathematics well, using C# and F#. + +## The one rule everything else follows from + +**Be a mathematician first.** When mathematical correctness and backward compatibility disagree, +correctness wins. A published API that returns the wrong answer is not an asset to preserve; it is +a bug with users. Say so in the changelog, and change it. + +The same goes for convention. If mathematicians write it one way and the library writes it another, +the library is wrong — even where the library's way is defensible in isolation. `arcsinh` is not a +thing; the inverse of `sinh` is an *area*, not an arc, so it is `arsinh` (#687). Follow the notation +of the people who use the subject, not the notation that was easiest to parse. + +**Consistency is the point.** [#497](https://github.com/asc-community/AngouriMath/issues/497), the +2.0 paper, names inconsistency as the central defect: *"one may find it inconsistent in a lot of +places in API, behaviour, and internal structure of code."* A rule that fires for `sin` but not +`tan`, a limit that works from both sides but not from one, an evaluation that holds precision for +large numbers and drops it for small — each is a bug even when every individual case is defensible. +When you fix something, ask what else is the same shape, and fix that too, or write down why not. + +## Not answering is a legitimate answer. Answering wrongly is not. + +The most important distinction in this codebase: + +| result | means | +|---|---| +| unevaluated (`Limitf`, the original expression back) | "I could not settle this" | +| `NaN` | "**this does not exist**" | +| a value | "this is the value" | + +These are three different claims and they are not interchangeable. Returning `NaN` for a limit you +merely failed to compute is a *wrong answer*, not a graceful failure — it tells the caller the limit +does not exist. Returning an unevaluated node is honest. + +So, in order of preference: right answer > no answer > slow answer > wrong answer. A wrong answer is +worse than a hang, because a hang is visible. + +## Verify the mathematics, not the string + +Never assert on printed form unless the printed form *is* the bug. Assert the property: + +- an integral: differentiate it back and compare to the integrand +- a root: substitute it into the equation +- an identity: subtract the two sides and simplify to zero +- a limit: check it against the value at nearby points, or a series + +A string comparison passes for `2*x` and fails for `x*2`, which tells you nothing about whether the +answer is right. `Sources/Tests/UnitTests/Common/IssueRegressionTest.cs` is where issue regressions +go, named for the issue. + +**Reproduce on a stock `master` build before claiming anything is broken.** More than one confident +report here has turned out to describe behaviour that was never broken. + +## Aim high + +The ambition is to answer the hard problems: olympiad and competition algebra, calculus and number +theory; the integrals and limits that need real machinery rather than pattern tables; the questions +where the honest current answer is "unevaluated". Concretely, that means things like Risch for +integration, a proper polynomial layer (multivariate GCD, resultants, factorisation) which most of +the open simplification issues sit behind, and quantifier elimination for inequalities. + +Measure against a corpus of problems with known answers, and report **wrong / error / timeout** +counts alongside the solved count. A change that solves one more problem and introduces one wrong +answer is a regression. Compare against SymPy, Mathematica, or a textbook — being different from +SymPy is not automatically being wrong, but it is always worth explaining. + +## Keep up with the mathematics + +Algorithms here are decades of literature deep, and the good ones are written down. Before inventing +a procedure, find out whether it has a name: + +- **SymPy** (`sympy/`) — readable reference implementations of Risch, Gruntz, Gröbner, and much else, + with the papers cited in the docstrings. The single most useful cross-check. +- **Mathematica / Wolfram Alpha, Maxima, SageMath** — for deciding what the right answer *is*. +- **DLMF** (dlmf.nist.gov) — the authority on special-function identities and branch cuts. +- **OEIS** — for recognising an integer sequence. +- **arXiv math.AC / cs.SC**, the *Journal of Symbolic Computation*, and the ISSAC proceedings — where + new symbolic-computation algorithms appear. +- **Gruntz's thesis** for limits, **Bronstein's _Symbolic Integration I_** for integration, + **_Modern Computer Algebra_** (von zur Gathen & Gerhard) for the polynomial layer. + +Branch cuts deserve a specific warning: `arcsin`, `log`, and fractional powers disagree between +conventions, and C99, .NET, Python and Mathematica do not all agree. Decide deliberately, cite the +convention, and test the disagreeing points. + +## Working practice + +**Branch first, always.** Never commit a feature or a fix straight to `master`, even with write +access. One branch per change, each independently mergeable, cut from `master`. + +``` +fix/ fix/vanishing-denominator +feat/ feat/gruntz +perf/ perf/limit-memoisation +docs/ docs/agents-md +chore/ chore/drop-myget +``` + +Then: + +1. Write the failing test first, and check it fails for the reason you think. +2. Fix it. Prefer the smaller change with the smaller blast radius. +3. Run everything: `dotnet test Sources/Tests/UnitTests`, + `dotnet test Sources/Tests/FSharpWrapperUnitTests`. +4. If an existing test now fails, decide honestly which it is — the answer got better, the test was + pinning a fudge, or you broke something — and say which in the commit message. Never loosen an + assertion without writing down why. +5. Open a PR. State what was wrong, why the fix is right, and what you measured. + +`TreatWarningsAsErrors` is on and there are custom analyzers; a static field needs +`[ConstantField]`, `[ThreadStatic]` or `[ConcurrentField]`. + +## Write for the reader, briefly + +Comments explain **why**, not what — the code says what. The reader is a mathematician six months +from now wondering whether a line can be deleted. Tell them what breaks if it is. + +```csharp +// (x - a)^k is positive on the right whatever k is, and on the left takes the sign of +// (-1)^k, so approaching from the left at an odd order turns the sign around. +``` + +Be concise. Do not narrate your own process, and **do not record your mistakes, wrong turns or +retracted diagnoses in code, commit messages or documentation.** Those belong in the issue tracker, +where they are searchable and where someone hitting the same wall will find them. What belongs in +the code is the conclusion and the reason it holds. A measurement that justifies a constant is a +reason; a story about how you arrived at it is not. + +Cite issues by full URL in code comments (`https://github.com/asc-community/AngouriMath/issues/557`), +since a bare `#557` means nothing outside GitHub. `#557` is fine in PR titles and bodies. + +## Where the work is + +Good entry points, roughly by depth: + +- **Simplification consistency** — [#531](https://github.com/asc-community/AngouriMath/issues/531), + [#557](https://github.com/asc-community/AngouriMath/issues/557), + [#415](https://github.com/asc-community/AngouriMath/issues/415). Rules that fire in one arrangement + and not another. +- **Limits** — [#596](https://github.com/asc-community/AngouriMath/issues/596), + [#536](https://github.com/asc-community/AngouriMath/issues/536). `0^0` and `oo^0` have no rule + where `1^oo` has one; piecewise has none at all. +- **Numerics** — [#602](https://github.com/asc-community/AngouriMath/issues/602). Precision that + holds at one scale and not another. +- **Solving** — [#629](https://github.com/asc-community/AngouriMath/issues/629), + [#475](https://github.com/asc-community/AngouriMath/issues/475), + [#381](https://github.com/asc-community/AngouriMath/issues/381). Diophantine equations and + characteristic polynomials both want the polynomial layer. +- **The polynomial layer itself** — multivariate GCD, resultants, factorisation. Large, and most of + the above sits behind it. +- **[#497](https://github.com/asc-community/AngouriMath/issues/497), AngouriMath 2.0** — the design + paper. If you are proposing something structural, propose it there. diff --git a/README.md b/README.md index d4199141e..8efeec145 100644 --- a/README.md +++ b/README.md @@ -58,18 +58,6 @@ Note, that all tests and builds are tested for the following three operating sys | Terminal | Nuget (with prereleases) | Nuget | Nuget | | C++ | GitHub release (latest SemVer including pre-releases) | WIP | WIP | -There are also latest-master versions (updated on every push to master) on [MyGet](https://www.myget.org/feed/Packages/angourimath): -| MyGet | Downloads | -|-------|-----------| -| [![MyGet (with prereleases)](https://img.shields.io/myget/angourimath/vpre/AngouriMath?label=AngouriMath)](https://www.myget.org/feed/angourimath/package/nuget/AngouriMath) | ![MyGet](https://img.shields.io/myget/angourimath/dt/AngouriMath?label=Downloads) | -| [![MyGet (with prereleases)](https://img.shields.io/myget/angourimath/vpre/AngouriMath.FSharp?label=AngouriMath.FSharp)](https://www.myget.org/feed/angourimath/package/nuget/AngouriMath.FSharp) | ![MyGet](https://img.shields.io/myget/angourimath/dt/AngouriMath.FSharp?label=Downloads) | -| [![MyGet (with prereleases)](https://img.shields.io/myget/angourimath/vpre/AngouriMath.Interactive?label=AngouriMath.Interactive)](https://www.myget.org/feed/angourimath/package/nuget/AngouriMath.Interactive) | ![MyGet](https://img.shields.io/myget/angourimath/dt/AngouriMath.Interactive?label=Downloads) | -| [![MyGet (with prereleases)](https://img.shields.io/myget/angourimath/vpre/AngouriMath.Terminal?label=AngouriMath.Terminal)](https://www.myget.org/feed/angourimath/package/nuget/AngouriMath.Terminal) | ![MyGet](https://img.shields.io/myget/angourimath/dt/AngouriMath.Terminal?label=Downloads) | - -Source to install from MyGet: -``` -https://www.myget.org/F/angourimath/api/v3/index.json -``` #### Other info | Website | Stars | License | diff --git a/Sources/NuGet.Config b/Sources/NuGet.Config index 55b308200..f115653fa 100644 --- a/Sources/NuGet.Config +++ b/Sources/NuGet.Config @@ -6,6 +6,5 @@ - \ No newline at end of file