Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/InteractiveTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
run: |
dotnet build Sources/Tests/InteractiveWrapperUnitTests -c Release
dotnet build Sources/Tests/TerminalUnitTests -c Release
dotnet build Sources/Tests/McpUnitTests -c Release

- name: Test Interactive Wrapper
if: ${{ matrix.os != 'ubuntu-latest' }}
Expand All @@ -42,11 +43,20 @@ jobs:
run: |
dotnet test "Sources/Tests/TerminalUnitTests" -c Release

# The server amcli mcp runs. It builds against the library in this repository, so a change
# that moves an answer its documents describe, or a function the grammar gains or loses,
# fails here rather than after a release.
- name: Test MCP server
if: ${{ matrix.os != 'ubuntu-latest' }}
run: |
dotnet test "Sources/Tests/McpUnitTests" -c Release

- name: Test with coverage
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
dotnet test "Sources/Tests/InteractiveWrapperUnitTests" -c Release --collect:"XPlat Code Coverage" --results-directory ./coverage -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
dotnet test "Sources/Tests/TerminalUnitTests" -c Release --collect:"XPlat Code Coverage" --results-directory ./coverage -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
dotnet test "Sources/Tests/McpUnitTests" -c Release --collect:"XPlat Code Coverage" --results-directory ./coverage -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover

- name: 'Send to codecov'
if: ${{ matrix.os == 'ubuntu-latest' }}
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,7 @@ are short, and a stale one is worse than none — if you change what a file desc
| [`Contributing/Packaging.md`](Sources/AngouriMath/Docs/Contributing/Packaging.md) | which capabilities ship in the kernel package and which ship separately, and the four checkable clauses that decide. Read it before anything large lands in the kernel |
| [`WhatsNew/version_performance_control.md`](Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md) | the inter-version performance table, how to add a column, and when the CI performance baseline may be updated |
| `Sources/Analyzers/` | the custom analyzers, including the static-field one behind `[ConstantField]` |
| [`Sources/MCP/README.md`](Sources/MCP/README.md) | the MCP server `amcli mcp` runs: what it offers an agent, and the invariants that look arbitrary in it and are not |

Anything added for the library's own purposes is not `public` — see
[`Contributing/coding_rules.md`](Sources/AngouriMath/Docs/Contributing/coding_rules.md). Nothing
Expand Down
6 changes: 6 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,12 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[AngouriMath/Numerics/*.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[MCP/**.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/McpUnitTests/**.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Core/IntervalEvaluationTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

Expand Down
16 changes: 16 additions & 0 deletions Sources/AngouriMath.sln
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "AngouriMath.Terminal", "Ter
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "AngouriMath.Terminal.Lib", "Terminal\AngouriMath.Terminal.Lib\AngouriMath.Terminal.Lib.fsproj", "{405E214B-17A6-83E0-90A1-4F8B7213954F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MCP", "MCP", "{A3D7217B-225C-4020-980F-EBB8EAECADBA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AngouriMath.Mcp", "MCP\AngouriMath.Mcp\AngouriMath.Mcp.csproj", "{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "McpUnitTests", "Tests\McpUnitTests\McpUnitTests.csproj", "{0C73C9BD-C99C-486D-A058-EE269F02DB77}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utils", "Utils", "{DC3A3E46-854E-403D-8FA5-561DF66277D4}"
ProjectSection(SolutionItems) = preProject
Utils\antlr_rerun.bat = Utils\antlr_rerun.bat
Expand Down Expand Up @@ -153,6 +159,14 @@ Global
{405E214B-17A6-83E0-90A1-4F8B7213954F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{405E214B-17A6-83E0-90A1-4F8B7213954F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{405E214B-17A6-83E0-90A1-4F8B7213954F}.Release|Any CPU.Build.0 = Release|Any CPU
{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A}.Release|Any CPU.Build.0 = Release|Any CPU
{0C73C9BD-C99C-486D-A058-EE269F02DB77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C73C9BD-C99C-486D-A058-EE269F02DB77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C73C9BD-C99C-486D-A058-EE269F02DB77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C73C9BD-C99C-486D-A058-EE269F02DB77}.Release|Any CPU.Build.0 = Release|Any CPU
{9E733914-18F8-CD3A-EDB8-F32FFEF4A3DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E733914-18F8-CD3A-EDB8-F32FFEF4A3DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E733914-18F8-CD3A-EDB8-F32FFEF4A3DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -197,6 +211,8 @@ Global
{13409571-0ADC-B9C6-2EE7-44DF0B4AD43C} = {822E64E5-9BB9-4702-B704-18129D0BC9C7}
{E34FC29D-1046-0B02-E647-858434BFD9E0} = {7FCB69C7-EC31-4F61-BDD9-43B9DEED8750}
{405E214B-17A6-83E0-90A1-4F8B7213954F} = {7FCB69C7-EC31-4F61-BDD9-43B9DEED8750}
{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A} = {A3D7217B-225C-4020-980F-EBB8EAECADBA}
{0C73C9BD-C99C-486D-A058-EE269F02DB77} = {9869C55D-A753-4DE2-9C22-CCF768C6EC53}
{9E733914-18F8-CD3A-EDB8-F32FFEF4A3DA} = {DC3A3E46-854E-403D-8FA5-561DF66277D4}
{FD30A551-F0D8-071A-9356-0BF0CDFB2FCC} = {D8022D2F-1DCE-4CE1-98DF-70B00D719D59}
{ABAD0619-30C7-242D-670F-2ECC908074A2} = {D8022D2F-1DCE-4CE1-98DF-70B00D719D59}
Expand Down
197 changes: 197 additions & 0 deletions Sources/MCP/AngouriMath.Mcp/Analysis.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
//
// Copyright (c) 2019-2026 Angouri.
// AngouriMath is licensed under MIT.
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
// Website: https://am.angouri.org.
//

using AngouriMath;
using AngouriMath.Core;
using static AngouriMath.Entity;

namespace AngouriMath.Mcp;

/// <summary>
/// Answers two questions the raw library does not: "what should I watch out for in this
/// expression?" and "is this answer real, or merely correct?".
///
/// AngouriMath has no step engine — there is no derivation output anywhere in the library —
/// so the honest substitute is to check a chain of working the caller supplies, and say
/// which step broke. See <see cref="CheckSteps"/>.
/// </summary>
internal static class Analysis
{
// Spread across the real line, including negatives and a point near zero, because the
// whole point here is to FIND the places an expression stops being real or defined.
private static readonly Entity[] Probes =
["-2.7", "-1.3", "-0.4", "0.6", "1.9", "3.2"];

public sealed record Caution(string Where, string Risk);

/// <summary>
/// Structural hazards, found by walking the tree. These are the classic ways an
/// expression means less than it appears to.
/// </summary>
public static List<Caution> Hazards(Entity e)
{
var found = new List<Caution>();

foreach (var node in e.Nodes)
{
switch (node)
{
case Divf:
var divisor = node.DirectChildren.Count > 1
? node.DirectChildren[1].Stringize()
: "?";
found.Add(new Caution(node.Stringize(),
$"division: undefined where {divisor} = 0. Any cancellation of this " +
"term silently widens the domain unless a `provided` guard survives."));
break;

case Logf:
found.Add(new Caution(node.Stringize(),
"logarithm: real only for a positive argument. Combining " +
"ln(a)+ln(b) into ln(a*b) widens the domain and is a documented " +
"source of extraneous roots."));
break;

case Powf pow when pow.Exponent is not Number.Integer:
found.Add(new Caution(node.Stringize(),
"fractional or symbolic power: the principal branch is taken, so " +
"(-8)^(1/3) is a complex number, not -2. sqrt(x^2) is abs(x), not x."));
break;

case Arcsinf or Arccosf:
found.Add(new Caution(node.Stringize(),
"inverse trig: real only for an argument in [-1, 1], and the " +
"principal branch is returned — the full solution family is periodic."));
break;

case Factorialf:
found.Add(new Caution(node.Stringize(),
"factorial: defined via the gamma function, so it returns values for " +
"non-integers and poles at the negative integers."));
break;

case Absf:
found.Add(new Caution(node.Stringize(),
"absolute value: not differentiable at 0, and it hides a sign change " +
"that matters when solving."));
break;
}
}

return found;
}

/// <summary>Domain guards AngouriMath itself worked out, pulled out of the tree.</summary>
public static List<string> Conditions(Entity simplified) =>
simplified.Nodes
.OfType<Providedf>()
.Select(p => p.Predicate.Stringize())
.Distinct()
.ToList();

public sealed record RealityReport(
List<string> ComplexAt, List<string> UndefinedAt, int Sampled);

/// <summary>
/// Where does this expression stop being a real number? A result can be perfectly
/// correct and still be useless as a length, a resistance or a time.
/// </summary>
public static RealityReport Reality(Entity e)
{
var complexAt = new List<string>();
var undefinedAt = new List<string>();
var variables = e.Vars.ToArray();
var sampled = 0;

// With no free variables there is a single value to inspect.
if (variables.Length == 0)
{
Inspect(e, "(constant)");
return new RealityReport(complexAt, undefinedAt, 1);
}

foreach (var probe in Probes)
{
var substituted = e;
foreach (var variable in variables)
substituted = substituted.Substitute(variable, probe);

sampled++;
Inspect(substituted, string.Join(" = ", variables.Select(v => v.Stringize())) +
" = " + probe.Stringize());
}

return new RealityReport(complexAt, undefinedAt, sampled);

void Inspect(Entity at, string label)
{
try
{
if (!at.EvaluableNumerical) { undefinedAt.Add(label); return; }

var value = at.EvalNumerical();
var real = value.RealPart.EDecimal.ToDouble();
var imaginary = value.ImaginaryPart.EDecimal.ToDouble();

if (double.IsNaN(real) || double.IsNaN(imaginary)) undefinedAt.Add(label);
else if (Math.Abs(imaginary) > 1e-12) complexAt.Add(label);
}
catch
{
undefinedAt.Add(label);
}
}
}

/// <summary>Force an expression to be real-valued: anything complex becomes NaN.</summary>
public static Entity AsReal(Entity e) =>
e.Replace(node => node.WithCodomain(Domain.Real));

public sealed record StepCheck(
int Index, string From, string To, bool? Equal, string Method, string? Difference);

/// <summary>
/// Check a chain of working, one step at a time, and report the first step that changes
/// the value.
///
/// This is the useful form of "show me the steps". The library cannot produce a
/// derivation — but a model is perfectly good at PROPOSING one and unreliable at
/// EXECUTING it, so having it write the steps and having this check each transition
/// puts each side on the task it is actually good at. A wrong step is located, not just
/// detected.
/// </summary>
public static List<StepCheck> CheckSteps(List<Entity> steps)
{
var checks = new List<StepCheck>();

for (var i = 1; i < steps.Count; i++)
{
var from = steps[i - 1];
var to = steps[i];

Entity difference;
try { difference = (from - to).Simplify(); }
catch { difference = from - to; }

if (Numeric.IsZero(difference))
{
checks.Add(new StepCheck(i, from.Stringize(), to.Stringize(),
true, "exact", null));
continue;
}

var numerically = Numeric.Equal(difference, 0);
checks.Add(new StepCheck(
i, from.Stringize(), to.Stringize(),
numerically,
numerically is null ? "inconclusive" : "numeric",
numerically == true ? null : difference.Stringize()));
}

return checks;
}
}
31 changes: 31 additions & 0 deletions Sources/MCP/AngouriMath.Mcp/AngouriMath.Mcp.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<!--
The Model Context Protocol server that `amcli mcp` runs: newline-delimited JSON-RPC over stdio,
exposing the library to an agent as tools, prompts and resources. A library rather than a
program so that the terminal's tool is the one thing a user installs; Server.Run is the entry.
It builds against the library in this repository, so a change to the parser or to an answer
the server's selftest or its tests rely on fails here rather than after a release.
https://github.com/asc-community/AngouriMath/issues/1649
-->

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>AngouriMath.Mcp</RootNamespace>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- Shipped inside the terminal's tool package, not as a package of its own. -->
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="McpUnitTests" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../../AngouriMath/AngouriMath.csproj" />
</ItemGroup>

</Project>
Loading
Loading