Hi. The last 7 months I have been working on the greatest release of AngouriMath. There is something I want to tell you.

Briefly about the project

In November of 2019 I realized that it would be nice to have a symbolic algebra library for .NET, which could simplify expressions, solve equations, build latex code, and many more. That is how I decided to create one.

But this already exists...

As long as I tell people about what I am working on, they suggest different solutions, be those to rewrite SymPy, make a wrapper over SageMath for .NET, pirate Wolfram|Alpha, use primitive mathnet.symbolics (about which they mention themselves).

All of those have limitations or difficulties. In contrast, what I am working on is a very lightweight library, made and optimized for .NET specifically. It is open-source (under MIT).

Release 1.2

In August one of the main contributors, @HappyPig375, helped to rewrite a significant part of the library into a type hierarchy. Every operator/function has its own node (type). Thanks to it, the library now has a more obvious API, improved performance and security. Now, let us go over what has been done within these 7 months.

An expression is a record

For example, that is what Sumf looks like

public sealed partial record Sumf(Entity Augend, Entity Addend) : NumericNode

Thanks to it, now we can apply pattern matching:

internal static Entity CommonRules(Entity x) => x switch
{
    // (a * f(x)) * g(x) = a * (f(x) * g(x))
    Mulf(Mulf(Number const1, Function func1), Function func2) => func1 * func2 * const1,

    // (a/b) * (c/d) = (a*c)/(b*d)
    Mulf(Divf(var any1, var any2), Divf(var any3, var any4)) => any1 * any3 / (any2 * any4),

    // a / (b / c) = a * c / b
    Divf(var any1, Divf(var any2, var any3)) => any1 * any3 / any2,

(it is a few examples of patterns used in the simplification algorithm)

Math

Here I am going to over over features related to math itself.

New functions

Secant, cosecant, arcsecant, arccosecant were added as separate nodes.

12 hyperbolic functions were added. They do not have their own nodes and return their symbolic expression (sinh(x)as(e.Pow(x) - e.Pow(-x)) / 2).

Abs and Signum. The syntax of Abs is the following: (|x|). It looks similar to how we write in on a paper while at the same time allows to avoid ambiguouty (because | is symmetric, there might be problems with it).

Euler's totient function was added.

WriteLine(@"phi(8)".EvalNumerical());
WriteLine(@"(|-3 + 4i|)".EvalNumerical());
WriteLine(@"sinh(3)".Simplify());
WriteLine(@"sec(0.5)".Simplify());

Prints

4
5
(e ^ 3 - 1 / e ^ 3) / 2
sec(1/2)

Domains

They allow to limit every node's range. If a node is out of its domain, it turns into a NaN, which makes the entire expression undefined. SpecialSets are used as values of domains.

Booleans and logic

Logic operators were finally added. At the beginning, there was an idea to make those similar to what we use in programming; instead, it was decided to make them literal. That is why the syntax is: not, or, xor, and, implies.

To check whether an expression is evaluable into a Boolean, you need to use EvaluableBoolean. Same way, we use EvaluableNumerical to check whether an expression is evaluable into a Number.

WriteLine(@"(true or b) implies c".Simplify());

(printsc)

Equality and inequality signs

They might carry the Booleantype, if their operands are computed. Of course, the following were added: =, <, >, <=, >=.

You can also combine inequalities. For example, a > b > c is interpreted asa > b and b > c.

WriteLine(@"a < b >= c".Simplify());

(Printsa < b and b >= c)

Sets

Sets were added. They are parsable and have their own nodes.

The most naive type of set is FiniteSet. Its syntax: { 1, 2, 3 }.

Intervals also have familiar syntax: [1; 2] for both points included, (1; 2) for both excluded, [1; 2) for the right one excluded, and (1; 2] for the left one excluded. Complex intervals were removed because of their clumsiness.

SpecialSet are preset sets. Now we have CC, RR, QQ, ZZ, BB for complex, real, rational, integer and boolean values.

ConditionalSet is written in set-builder notation, for example: { x : x > 0 and x^2 = y } (any x such that greater than 0 and equalsy).

WriteLine(@"({ 1, 2 } \/ { 5 }) /\ { x : x in [2; 3] and x > 0 } ".Simplify());

(Prints{ 2 })

Limits improved

First remarkable, second remarkable limits transformation rules were added, as well as the l'Hopital's rule.

WriteLine("tan(a x) / (b x)".Limit("x", 0));
WriteLine("(sin(t) - t) / t3".Limit("t", 0));

(Printsa / b and -1/6 respectively)

"Provided" node

Allows to set constraints on an expression. For example, square root on real numbers we can define as sqrt(x) provided x >= 0. It will turn into NaN if you substitute negative x.

If an expression being turned into NaN is an element of a finite set, it will be excluded, instead of turning the whole expression into NaN. It is the only exception for NaN.

Piecewise

Piecewise is a sequence ofProvideds. Unlike the classical piecewise-defined function, here the order of elements matters, and when computingPiecewise, the firstProvided is returned such that its predicate is true.

That is an example of how we can define the absolute value function for real numbers via Piecewise:

Entity abs = "piecewise(x provided x > 0, -x provided x <= 0)";
WriteLine(abs.Substitute("x", 3).EvalNumerical());
WriteLine(abs.Substitute("x", -3).EvalNumerical());

(Prints 3 in both cases)

Ease and convenience of use

This part of the article about features making the use of AngouriMath more comfortable.

Exceptions were reconsidered and refactored

Now all exceptions thrown by the library are somehow inherited from AngouriMathBaseException. Because there is nop/invoke or IO, you can be assured that any exception assignable to AngouriMathBaseException does not bring any direct harm to the system or user's data. In other words, when catching exceptions, now you can catch this exception in the last instance of Catch.

Performance improved

To be precise, the performance has been fluctuating throughout this all time. Nonetheless, it is still by far better than that of 1.1.0.5. Here you can find a performance report by every key commit.

F# is now supported

With this release, the API for AngouriMath is now supported natively for wonderful language F#. It might be not as functional as the main API, so in especially complicated cases, you still can call methods from the core library itself.

Interactive

I wrote an article some time ago about AngouriMath in Jupyter. AngouriMath.Interactive itself simplify converts ILatexiseable into a LaTeX-code and renders it with MathJax (read more about it in the mentioned article).

A simple example of using AngouriMath.Interactive in Jupyter
A simple example of using AngouriMath.Interactive in Jupyter

Multithreading

All computations happen strictly in one thread. The library is not responsible for your threading problems. Moreover, all methods are thread-safe. The settings are local for every thread([ThreadStatic]).

The main feature of this update is that you can interrupt computations of Solve orSimplify.

New settings

It is their third version. Even though I am not a big fan of the new solution, it is the best I could achieve. To set a new value of a setting, let us write

using var _ = MaxExpansionTermCount.Set(10);
// some code

Then this setting will be automatically rolled back to the previous value once the end of the scope is reached (the return type of Set isIDisposable).

That is about it

Thank you for your attention. I will be pleased to answer your questions.

Anybody willing to contribute is welcome (contact me or fork and make your changes directly).

References

  1. GitHub page of the project.

  2. Website of the project.

  3. Detailed What's new.

  4. Plans for the future updates.

  5. My GitHub profile.

  6. SymPy - those who inspire me.