I worked with GiNaC https://www.ginac.de/ and noticed that the bottleneck was variable substitutions, e.g., replace a variable with a constant and simplify the expression. I'm still dreaming of an efficient CAS.
in case of single variable for example var result = SymbolicVariable.Parse("sin(3*x)").Execute(2);
in case of multi variables you can send the values based on their alphabet order
var d = SymbolicVariable.Parse("a^b^c"); Assert.AreEqual("a^(b^c)", d.ToString());
var r = d.Execute(3, 2, 4); Assert.AreEqual(43046721.0, r);
or by assigning them to a dictionary of Dictionary<string, double>