diff --git a/AGENTS.md b/AGENTS.md index a148385fd..e3f99929b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -875,7 +875,10 @@ landed — and **the MCP server and two other repositories brought to the releas answer back out of the nodes. That documentation is as much a deliverable as the operation (the maintainer's words on [#1409](https://github.com/asc-community/AngouriMath/issues/1409)); write it there, or in this repository's `Docs/Usage` and the website, and check at every release that the - MCP still says what the library does. + MCP still says what the library does. An operation the library offers by name is an entry of + `Core/Operations/Catalogue.cs` ([#1684](https://github.com/asc-community/AngouriMath/issues/1684)), + and a new factory of `Transformation` fails `CatalogueTest` until it is an entry or a building + block with its reason. - [CSharpMath](https://github.com/verybadcat/CSharpMath), for the round trip of `Latexize` — see *The standard command wins* above. If a PR there from an earlier release is still unmerged, add to it rather than opening a second. diff --git a/Sources/.editorconfig b/Sources/.editorconfig index be901a7f0..45b05543c 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -24,6 +24,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [AngouriMath/Core/Transformations/*.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 +[AngouriMath/Core/Operations/*.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 + [AngouriMath/Functions/Continuous/Integration/RothsteinTrager.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 @@ -87,6 +90,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Core/Transformations/*.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/Operations/*.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/Algebra/Polynomials/EvaluationIrreducibilityTest.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 diff --git a/Sources/AngouriMath/Core/Operations/Catalogue.cs b/Sources/AngouriMath/Core/Operations/Catalogue.cs new file mode 100644 index 000000000..6dfb2e3a3 --- /dev/null +++ b/Sources/AngouriMath/Core/Operations/Catalogue.cs @@ -0,0 +1,255 @@ +// +// 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 System; +using System.Collections.Generic; +using System.Linq; +using AngouriMath.Core.Transformations; +using static AngouriMath.Entity; +using static AngouriMath.Entity.Number; + +namespace AngouriMath.Core.Operations +{ + /// + /// Every operation the library offers by name, which the terminal and the MCP server take + /// theirs from. + /// + /// + /// + /// The names are the ones + /// #1019 item 16 + /// proposes for amcli's commands in v3: the library's own verbs, so simplify + /// rather than simp and differentiate rather than diff. + /// + /// + /// An operation is in this list when a surface offers it, or could offer it with nothing + /// but text in and text out. The building blocks also names, + /// such as or + /// , are not: a caller composes them, and + /// none answers a question by itself. A test lists every factory of + /// as one or the other, so that a new one fails the build + /// until it is placed. + /// + /// https://github.com/asc-community/AngouriMath/issues/1684 + /// + internal static class Catalogue + { + [ConstantField] + private static readonly Parameter TheVariable = new("variable", "The variable it is over."); + + // ---------------------------------------------------------------- transformations + + /// . + public static TransformationOperation> Simplify { get; } = new( + "simplify", "Simplifies, searching the expression's rewritings for the simplest.", + TransformationRelation.Equivalence, static e => e.Simplify()); + + /// . + public static TransformationOperation> Normalize { get; } = new( + "normalize", "Tidies the expression in one structural pass, without the search simplify makes.", + TransformationRelation.Equivalence, static e => e.InnerSimplified); + + /// . + public static TransformationOperation> Expand { get; } = new( + "expand", "Multiplies products over sums out.", + TransformationRelation.Equivalence, static e => e.Expand()); + + /// . + public static TransformationOperation> Factorize { get; } = new( + "factorize", "Gathers common factors out and factors polynomials.", + TransformationRelation.Equivalence, static e => e.Factorize()); + + /// . + public static TransformationOperation> Differentiate { get; } = new( + "differentiate", "Differentiates over a variable, as many times as the order says.", + TransformationRelation.Derivation, static (e, variable, order) => e.Differentiate(variable, order), + TheVariable, new Parameter("order", "How many times; a negative order integrates instead.")); + + /// . + public static TransformationOperation> Integrate { get; } = new( + "integrate", "Finds an antiderivative over a variable, with a constant of integration, or leaves the integral unevaluated.", + TransformationRelation.Derivation, static (e, variable) => e.Integrate(variable), + TheVariable); + + /// . + public static TransformationOperation> Limit { get; } = new( + "limit", "Takes the limit as a variable approaches a value, or leaves it unevaluated.", + TransformationRelation.Derivation, static (e, variable, destination, side) => e.Limit(variable, destination, side), + TheVariable, new Parameter("destination", "The value it approaches."), new Parameter("side", "From the left, from the right, or from both sides.")); + + /// . + public static TransformationOperation> Substitute { get; } = new( + "substitute", "Replaces every occurrence of a subexpression with another expression.", + TransformationRelation.Derivation, static (e, what, with) => e.Substitute(what, with), + new Parameter("what", "The subexpression to replace."), new Parameter("with", "What to put in its place.")); + + // ---------------------------------------------------------------- queries on an expression + + /// . + public static Query> Solve { get; } = new( + "solve", "Solves an equation, an inequality or a statement for a variable.", + static (e, variable) => e.Solve(variable), + TheVariable); + + /// . + public static Query, IReadOnlyList, Matrix?>> SolveSystem { get; } = new( + "solve_system", "Solves equations jointly for several variables, one row of the answer for each solution.", + static (equations, variables) => MathS.Equations(equations).Solve([.. variables]), + new Parameter("variables", "The variables to solve for, in the order of the answer's columns.")); + + /// . + public static Query> Evaluate { get; } = new( + "evaluate", "Evaluates as far as it goes: to a number, a boolean or a set where it can, and as an expression where it cannot.", + static e => e.Evaled); + + /// . + public static Query> EvaluateNumerically { get; } = new( + "evaluate_numerically", "Evaluates to a complex number, at the working precision.", + static e => e.EvalNumerical()); + + /// . + public static Query>> Variables { get; } = new( + "variables", "Lists the free variables.", + static e => e.Vars); + + /// . + public static Query>> Alternatives { get; } = new( + "alternatives", "Lists forms of the expression that simplify finds, simplest first.", + static (e, level) => e.Alternate(level), + new Parameter("level", "How hard to look, as simplify's level.")); + + /// . + public static Query> Latex { get; } = new( + "latex", "Writes the expression as LaTeX.", + static e => e.Latexize()); + + /// . + public static Query> Sympy { get; } = new( + "sympy", "Writes the expression as Python code for SymPy.", + static e => MathS.ToSympyCode(e)); + + /// . + public static Query, Matrix?>> TruthTable { get; } = new( + "truth_table", "Tabulates a statement over every assignment of true and false to its variables.", + static (e, variables) => MathS.Boolean.BuildTruthTable(e, [.. variables]), + new Parameter("variables", "The variables, in the order of the table's columns.")); + + /// . + public static Query, Matrix?>> SatisfyingAssignments { get; } = new( + "satisfying_assignments", "The assignments of true and false to a statement's variables under which it holds, one row each.", + static (e, variables) => MathS.SolveBooleanTable(e, [.. variables]), + new Parameter("variables", "The variables, in the order of the rows' columns.")); + + /// + /// , or + /// where there is no point. + /// + public static Query> Series { get; } = new( + "series", "Expands as a Taylor series in a variable, up to a degree, around a point or around zero.", + static (e, variable, degree, around) => around is null + ? MathS.Series.Maclaurin(e, degree, variable) + : MathS.Series.Taylor(e, degree, (variable, around)), + TheVariable, new Parameter("degree", "The highest power kept."), new Parameter("around", "The point, or none for zero.")); + + // ---------------------------------------------------------------- queries on a number + + /// . + public static Query> ToBase { get; } = new( + "to_base", "Writes a real number in a base from 2 to 36.", + static (number, numberBase) => MathS.ToBaseN(number, numberBase), + new Parameter("base", "The base, from 2 to 36.")); + + /// . + public static Query> FromBase { get; } = new( + "from_base", "Reads a real number written in a base from 2 to 36.", + static (digits, numberBase) => MathS.FromBaseN(digits, numberBase), + new Parameter("base", "The base, from 2 to 36.")); + + // ---------------------------------------------------------------- queries on a matrix + + /// . + public static Query> Determinant { get; } = new( + "determinant", "The determinant of a square matrix, or none for a matrix that is not square.", + static m => m.Determinant); + + /// . + public static Query> Inverse { get; } = new( + "inverse", "The inverse of a square matrix, or none where it has none.", + static m => m.Inverse); + + /// . + public static Query> Transpose { get; } = new( + "transpose", "The transpose of a matrix.", + static m => m.T); + + /// . + public static Query> Rank { get; } = new( + "rank", "The rank of a matrix.", + static m => m.Rank); + + /// . + public static Query> ReducedRowEchelonForm { get; } = new( + "reduced_row_echelon_form", "A matrix in reduced row echelon form.", + static m => m.ReducedRowEchelonForm); + + /// . + public static Query> Trace { get; } = new( + "trace", "The trace of a square matrix.", + static m => m.Trace); + + /// . + public static Query> TensorProduct { get; } = new( + "tensor_product", "The tensor product of two matrices.", + static (a, b) => Matrix.TensorProduct(a, b), + new Parameter("other", "The matrix on the right.")); + + // ---------------------------------------------------------------- queries on an integer + + /// . + public static Query>> PrimeFactorization { get; } = new( + "prime_factorization", "The primes that divide an integer, each with its power.", + static n => MathS.NumberTheory.Factorize(n)); + + /// , evaluated. + public static Query> Totient { get; } = new( + "totient", "Euler's totient of an integer: how many positive integers up to it are coprime to it.", + static n => MathS.NumberTheory.Phi(n).Evaled); + + /// . + public static Query> GreatestCommonDivisor { get; } = new( + "greatest_common_divisor", "The greatest common divisor of two integers.", + static (a, b) => MathS.NumberTheory.GreatestCommonDivisor(a, b), + new Parameter("other", "The second integer.")); + + /// . + public static Query> DivisorCount { get; } = new( + "divisor_count", "How many positive divisors an integer has.", + static n => MathS.NumberTheory.CountDivisors(n)); + + /// . + public static Query> IsPrime { get; } = new( + "is_prime", "Whether an integer is prime.", + static n => n.IsPrime); + + // ---------------------------------------------------------------- the list + + /// Every entry above, in the order written. + /// Last in the file, because static initialisers run in the order they are written. + public static IReadOnlyList All { get; } = + [ + Simplify, Normalize, Expand, Factorize, Differentiate, Integrate, Limit, Substitute, + Solve, SolveSystem, Evaluate, EvaluateNumerically, Variables, Alternatives, Latex, Sympy, TruthTable, SatisfyingAssignments, Series, + ToBase, FromBase, + Determinant, Inverse, Transpose, Rank, ReducedRowEchelonForm, Trace, TensorProduct, + PrimeFactorization, Totient, GreatestCommonDivisor, DivisorCount, IsPrime, + ]; + + /// The entry named , or where there is none. + /// Its name, such as simplify. + public static Operation? Find(string name) => All.FirstOrDefault(operation => operation.Name == name); + } +} diff --git a/Sources/AngouriMath/Core/Operations/Operation.cs b/Sources/AngouriMath/Core/Operations/Operation.cs new file mode 100644 index 000000000..356ddbcaf --- /dev/null +++ b/Sources/AngouriMath/Core/Operations/Operation.cs @@ -0,0 +1,97 @@ +// +// 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 System; +using System.Collections.Generic; +using AngouriMath.Core.Transformations; + +namespace AngouriMath.Core.Operations +{ + /// + /// One thing the library does, under the name a caller asks for it by. The terminal's + /// commands and the MCP server's tools are each made of these, and + /// lists every one. + /// + /// + /// Internal until v3, while the shape of an entry may still move. The stable surface is the + /// methods on and , and an entry answers exactly + /// what the method it names answers. + /// https://github.com/asc-community/AngouriMath/issues/1684 + /// + internal abstract class Operation + { + private protected Operation(string name, string description, Parameter[] parameters) + { + Name = name; + Description = description; + Parameters = parameters; + } + + /// The name a caller asks for it by, such as simplify. + public string Name { get; } + + /// What it does, in one sentence. + public string Description { get; } + + /// + /// What it takes besides its input, in the order its delegate takes them after the input. + /// + public IReadOnlyList Parameters { get; } + + /// + public override string ToString() => Name; + } + + /// One argument an takes besides its input. + /// Its name, such as variable. + /// What it is, in one sentence. + internal sealed record Parameter(string Name, string Description); + + /// + /// An operation whose answer is an expression that stands in a stated relation to its + /// input: one of the s, applied as the method on + /// applies it. + /// + /// + /// Its signature: the input expression, then each of , + /// and an expression back. + /// + internal sealed class TransformationOperation : Operation where TApply : Delegate + { + internal TransformationOperation(string name, string description, + TransformationRelation relation, TApply apply, params Parameter[] parameters) + : base(name, description, parameters) + { + Relation = relation; + Apply = apply; + } + + /// What the answer claims about the input. + public TransformationRelation Relation { get; } + + /// Answers. + public TApply Apply { get; } + } + + /// + /// An operation whose answer is not an expression standing for the input: a set of + /// solutions, a number, text, a table. + /// + /// + /// Its signature: the input, then each of , and the + /// answer back. + /// + internal sealed class Query : Operation where TAsk : Delegate + { + internal Query(string name, string description, TAsk ask, params Parameter[] parameters) + : base(name, description, parameters) + => Ask = ask; + + /// Answers. + public TAsk Ask { get; } + } +} diff --git a/Sources/Tests/UnitTests/Core/Operations/CatalogueTest.cs b/Sources/Tests/UnitTests/Core/Operations/CatalogueTest.cs new file mode 100644 index 000000000..288aa6743 --- /dev/null +++ b/Sources/Tests/UnitTests/Core/Operations/CatalogueTest.cs @@ -0,0 +1,231 @@ +// +// 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 System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using AngouriMath; +using AngouriMath.Core; +using AngouriMath.Core.Operations; +using AngouriMath.Core.Transformations; +using Xunit; +using static AngouriMath.Entity; + +namespace AngouriMath.Tests.Core.Operations +{ + /// + /// The catalogue of operations: that it lists every entry it declares, that each entry + /// describes the delegate it holds, and that every factory of + /// is either an entry or a building block that says why it is not. + /// https://github.com/asc-community/AngouriMath/issues/1684 + /// + [Trait("Area", "Transformations")] + public sealed class CatalogueTest + { + /// + /// Each factory of that an entry applies, by the entry's name. + /// + private static readonly Dictionary EntryOfFactory = new() + { + [nameof(Transformation.Simplification)] = "simplify", + [nameof(Transformation.SimplificationAtLevel)] = "simplify", + [nameof(Transformation.InnerSimplification)] = "normalize", + [nameof(Transformation.Expansion)] = "expand", + [nameof(Transformation.ExpansionAtLevel)] = "expand", + [nameof(Transformation.Factorization)] = "factorize", + [nameof(Transformation.FactorizationAtLevel)] = "factorize", + [nameof(Transformation.Differentiation)] = "differentiate", + [nameof(Transformation.Integration)] = "integrate", + [nameof(Transformation.LimitAt)] = "limit", + [nameof(Transformation.Substitution)] = "substitute", + }; + + /// + /// The factories that are parts for a caller to compose rather than operations to ask + /// for by name, each with the reason. + /// + private static readonly Dictionary BuildingBlocks = new() + { + [nameof(Transformation.Rewriting)] = "applies a rule set the caller passes, which no text names", + [nameof(Transformation.NumericContentExtraction)] = "the last step of factorize", + [nameof(Transformation.AsSingleFraction)] = "a step of the rational canonical form", + [nameof(Transformation.RuleBasedFactorizationAtLevel)] = "the rule-based half of factorize", + [nameof(Transformation.PolynomialFactorization)] = "the polynomial half of factorize", + [nameof(Transformation.Normalization)] = "orders commutative operands, so that expressions can be compared rather than simplified", + [nameof(Transformation.RationalCanonicalization)] = "a canonical form for comparing rational functions, which nothing runs by default", + [nameof(Transformation.Canonicalization)] = "a canonical form for comparing expressions, which nothing runs by default", + [nameof(Transformation.Rationalization)] = "a step simplify takes", + [nameof(Transformation.EqualitySaturation)] = "an experimental search, which takes a budget and a cost model", + [nameof(Transformation.CanonicalizationOverGraph)] = "an experimental search, which takes a budget", + }; + + private static readonly Variable x = MathS.Var("x"); + + private static string DelegateName(Operation operation) => operation.GetType().GetGenericArguments().Single().Name; + + [Fact] + public void TheListHoldsEveryEntryOnce() + { + var declared = typeof(Catalogue) + .GetProperties(BindingFlags.Public | BindingFlags.Static) + .Where(property => typeof(Operation).IsAssignableFrom(property.PropertyType)) + .Select(property => (Operation)property.GetValue(null)!) + .ToList(); + Assert.Equal(declared.Count, Catalogue.All.Count); + Assert.Equal(declared.OrderBy(o => o.Name), Catalogue.All.OrderBy(o => o.Name)); + Assert.Equal(Catalogue.All.Count, Catalogue.All.Select(o => o.Name).Distinct().Count()); + } + + [Fact] + public void NamesAreLowercaseWordsJoinedByUnderscores() + { + foreach (var operation in Catalogue.All) + Assert.Matches("^[a-z]+(_[a-z]+)*$", operation.Name); + } + + [Fact] + public void FindAnswersByName() + { + foreach (var operation in Catalogue.All) + Assert.Same(operation, Catalogue.Find(operation.Name)); + Assert.Null(Catalogue.Find("simp")); + } + + [Fact] + public void EachDescriptionIsOneSentence() + { + foreach (var operation in Catalogue.All) + foreach (var description in operation.Parameters.Select(p => p.Description).Prepend(operation.Description)) + { + Assert.False(string.IsNullOrWhiteSpace(description), operation.Name); + Assert.DoesNotContain('\n', description); + Assert.EndsWith(".", description); + } + } + + /// + /// The parameters are the delegate's arguments after its input, so a parameter added to + /// one and not the other is caught here. + /// + [Fact] + public void TheParametersAreTheDelegatesArgumentsAfterTheInput() + { + foreach (var operation in Catalogue.All) + { + var invoke = operation.GetType().GetGenericArguments().Single().GetMethod("Invoke")!; + Assert.True(invoke.GetParameters().Length == operation.Parameters.Count + 1, + $"{operation.Name}: {DelegateName(operation)} takes {invoke.GetParameters().Length} arguments, and it names {operation.Parameters.Count} parameters"); + Assert.Equal(operation.Parameters.Count, operation.Parameters.Select(p => p.Name).Distinct().Count()); + } + } + + /// + /// A factory added to fails here until it is either an + /// entry or a building block, and a name that leaves it fails here too. + /// + [Fact] + public void EveryFactoryOfTransformationIsPlaced() + { + var factories = typeof(Transformation) + .GetMembers(BindingFlags.Public | BindingFlags.Static) + .Where(member => member switch + { + PropertyInfo property => property.PropertyType == typeof(Transformation), + MethodInfo method => method.ReturnType == typeof(Transformation) && !method.IsSpecialName, + _ => false, + }) + .Select(member => member.Name) + .ToHashSet(); + + var unplaced = factories.Where(name => !EntryOfFactory.ContainsKey(name) && !BuildingBlocks.ContainsKey(name)).ToList(); + Assert.True(unplaced.Count == 0, $"Not an entry and not a building block: {string.Join(", ", unplaced)}"); + var stale = EntryOfFactory.Keys.Concat(BuildingBlocks.Keys).Where(name => !factories.Contains(name)).ToList(); + Assert.True(stale.Count == 0, $"Listed here and no longer a factory: {string.Join(", ", stale)}"); + Assert.Empty(EntryOfFactory.Keys.Intersect(BuildingBlocks.Keys)); + foreach (var entry in EntryOfFactory.Values) + Assert.NotNull(Catalogue.Find(entry)); + } + + public static readonly IEnumerable TransformationsAndTheirEntries = new (Transformation, string)[] + { + (Transformation.Simplification, "simplify"), + (Transformation.InnerSimplification, "normalize"), + (Transformation.Expansion, "expand"), + (Transformation.Factorization, "factorize"), + (Transformation.Differentiation(x), "differentiate"), + (Transformation.Integration(x), "integrate"), + (Transformation.LimitAt(x, 0, ApproachFrom.BothSides), "limit"), + (Transformation.Substitution(x, 1), "substitute"), + }.Select(pair => new object[] { pair.Item1, pair.Item2 }); + + [Theory] + [MemberData(nameof(TransformationsAndTheirEntries))] + public void AnEntryClaimsWhatItsTransformationClaims(Transformation transformation, string entry) + { + var relation = Catalogue.Find(entry)!.GetType().GetProperty("Relation")!.GetValue(Catalogue.Find(entry)); + Assert.Equal(transformation.Relation, relation); + } + + /// + /// One answer from each entry, against the method the entry names, on an input where + /// that method and its neighbours answer differently -- so that an entry holding the + /// wrong method is caught. + /// + [Fact] + public void EachEntryAnswersAsTheMethodItNames() + { + Entity trigonometric = "sin(x)^2 + cos(x)^2", square = "(x + 1)^2"; + var y = MathS.Var("y"); + Assert.Equal(trigonometric.Simplify(), Catalogue.Simplify.Apply(trigonometric)); + Assert.Equal(trigonometric.InnerSimplified, Catalogue.Normalize.Apply(trigonometric)); + Assert.NotEqual(Catalogue.Simplify.Apply(trigonometric), Catalogue.Normalize.Apply(trigonometric)); + Assert.Equal(square.Expand(), Catalogue.Expand.Apply(square)); + Assert.Equal(square.Expand().Factorize(), Catalogue.Factorize.Apply(square.Expand())); + Assert.Equal(square.Differentiate(x, 2), Catalogue.Differentiate.Apply(square, x, 2)); + Assert.Equal(square.Integrate(x), Catalogue.Integrate.Apply(square, x)); + Assert.Equal(MathS.FromString("sin(x) / x").Limit(x, 0, ApproachFrom.Left), Catalogue.Limit.Apply("sin(x) / x", x, 0, ApproachFrom.Left)); + Assert.Equal(square.Substitute(x, y), Catalogue.Substitute.Apply(square, x, y)); + + Assert.Equal(MathS.FromString("x^2 = 4").Solve(x), Catalogue.Solve.Ask("x^2 = 4", x)); + Entity[] system = ["x + y - 3", "x - y - 1"]; + Assert.Equal(MathS.Equations(system).Solve(x, y), Catalogue.SolveSystem.Ask(system, [x, y])); + Assert.Equal(MathS.FromString("sqrt(2) + 1").Evaled, Catalogue.Evaluate.Ask("sqrt(2) + 1")); + Assert.Equal(MathS.FromString("sqrt(2) + 1").EvalNumerical(), Catalogue.EvaluateNumerically.Ask("sqrt(2) + 1")); + Assert.Equal(MathS.FromString("x + y").Vars, Catalogue.Variables.Ask("x + y")); + Assert.Equal(trigonometric.Alternate(2), Catalogue.Alternatives.Ask(trigonometric, 2)); + Assert.Equal(square.Latexize(), Catalogue.Latex.Ask(square)); + Assert.Equal(MathS.ToSympyCode(square), Catalogue.Sympy.Ask(square)); + Assert.Equal(MathS.Boolean.BuildTruthTable("a and not b", "a", "b"), Catalogue.TruthTable.Ask("a and not b", [MathS.Var("a"), MathS.Var("b")])); + Assert.Equal(MathS.SolveBooleanTable("a and not b", "a", "b"), Catalogue.SatisfyingAssignments.Ask("a and not b", [MathS.Var("a"), MathS.Var("b")])); + Assert.NotEqual(Catalogue.TruthTable.Ask("a and not b", [MathS.Var("a"), MathS.Var("b")]), Catalogue.SatisfyingAssignments.Ask("a and not b", [MathS.Var("a"), MathS.Var("b")])); + Assert.Equal(MathS.Series.Maclaurin("e^x", 3, x), Catalogue.Series.Ask("e^x", x, 3, null)); + Assert.Equal(MathS.Series.Taylor("e^x", 3, (x, 1)), Catalogue.Series.Ask("e^x", x, 3, 1)); + Assert.NotEqual(Catalogue.Series.Ask("e^x", x, 3, null), Catalogue.Series.Ask("e^x", x, 3, 1)); + + Assert.Equal(MathS.ToBaseN(10.5m, 2), Catalogue.ToBase.Ask(10.5m, 2)); + Assert.Equal(MathS.FromBaseN("1010.1", 2), Catalogue.FromBase.Ask("1010.1", 2)); + + var matrix = MathS.Matrix(new Entity[,] { { 1, 2 }, { 3, 4 } }); + Assert.Equal(matrix.Determinant, Catalogue.Determinant.Ask(matrix)); + Assert.Equal(matrix.Inverse, Catalogue.Inverse.Ask(matrix)); + Assert.Equal(matrix.T, Catalogue.Transpose.Ask(matrix)); + Assert.Equal(matrix.Rank, Catalogue.Rank.Ask(matrix)); + Assert.Equal(matrix.ReducedRowEchelonForm, Catalogue.ReducedRowEchelonForm.Ask(matrix)); + Assert.Equal(matrix.Trace, Catalogue.Trace.Ask(matrix)); + Assert.Equal(Matrix.TensorProduct(matrix, matrix.T), Catalogue.TensorProduct.Ask(matrix, matrix.T)); + + Number.Integer twelve = 12, eighteen = 18; + Assert.Equal(MathS.NumberTheory.Factorize(twelve), Catalogue.PrimeFactorization.Ask(twelve)); + Assert.Equal(MathS.NumberTheory.Phi(twelve).Evaled, Catalogue.Totient.Ask(twelve)); + Assert.Equal(MathS.NumberTheory.GreatestCommonDivisor(twelve, eighteen), Catalogue.GreatestCommonDivisor.Ask(twelve, eighteen)); + Assert.Equal(MathS.NumberTheory.CountDivisors(twelve), Catalogue.DivisorCount.Ask(twelve)); + Assert.Equal(twelve.IsPrime, Catalogue.IsPrime.Ask(twelve)); + Assert.True(Catalogue.IsPrime.Ask(7)); + } + } +}