Closed mrxrsd closed 4 years ago
Hi Pieter,
What do u think about adding a local constant registry in AstBuilder? Something like this:
CalculationEngine.cs
private Operation BuildAbstractSyntaxTree(string formulaText, IDictionary<string, double> constants = null)
{
TokenReader tokenReader = new TokenReader(cultureInfo);
List<Token> tokens = tokenReader.Read(formulaText);
var localConstantsRegistry = new ConstantRegistry(false);
if (constants != null)
{
foreach (var localConst in constants)
localConstantsRegistry.RegisterConstant(localConst.Key, localConst.Value);
}
AstBuilder astBuilder = new AstBuilder(FunctionRegistry, adjustVariableCaseEnabled, localConstantsRegistry);
Operation operation = astBuilder.Build(tokens);
if (optimizerEnabled)
return optimizer.Optimize(operation, this.FunctionRegistry, this.ConstantRegistry);
else
return operation;
}
AstBuilder.cs (piece of public Operation Build(IList
case TokenType.Text:
if (functionRegistry.IsFunctionName((string)token.Value))
{
operatorStack.Push(token);
parameterCount.Push(1);
}
else
{
string tokenValue = (string)token.Value;
if (adjustVariableCaseEnabled)
{
tokenValue = tokenValue.ToLowerInvariant();
}
if (constantRegistry.IsConstantName(tokenValue)
{
resultStack.Push(new FloatingPointConstant(constantRegistry.GetConstantInfo(tokenValue).Value));
}
else
{
resultStack.Push(new Variable(tokenValue));
}
}
break;
CalculationEngineTest.cs
[TestMethod]
public void TestCalculationFormulaWithConstant()
{
CalculationEngine engine = new CalculationEngine(CultureInfo.InvariantCulture, ExecutionMode.Compiled);
var fn = engine.Build("a+b+c",new Dictionary<string,double> { { "a", 1 }});
double result = fn(new Dictionary<string, double> {{"b", 2}, {"c", 2}} );
Assert.AreEqual(5.0, result);
}
I am open for the idea to add support for it, but just for my understanding: for which use cases would you require this feature?
In my case it's almost a linear regression model. It has two types of variables: generated by the system and by the input. System variables almost never change but I need to keep track all changes. So it's easier to think of them as variables instead of constants in the expression.
Another point it is I'm caching by my side, because I have other types of expressions that are not math and Jace can not process.
Another example would be a tax engine. The way of calculating is always the same, but each State has its aliquot. Then we can read the aliquots from a database and apply in the formula for each State.
I think always we have variables that change little and need to be tracked, this functionality will be applied.
And just to challenge you: why could you not build a function and then provide the variables for calculation? (It would really like to understand your use case in depth)
Yes, it is possible but I will lose performance or I need to do as I showed in the first post by string replacement and dealing with all complexity to identify variables boundaries.
var sw = new Stopwatch();
var calcEngine = new CalculationEngine();
var fn = calcEngine.Build("a*x+b");
sw.Start();
for (int i = 0; i < 10000000; i++)
{
fn(new Dictionary<string, double> {{"a", 2}, {"x", 3}, {"b", 4}});
}
sw.Stop();
sw.Reset(); // 4774ms
var fn1 = calcEngine.Build("2*x+4");
sw.Start();
for (int i = 0; i < 10000000; i++)
{
fn1(new Dictionary<string, double> { { "x", 3 } });
}
sw.Stop();
sw.Reset(); //2939ms
Thinking as Functional programming is like curry function. I've already had my "function body" defined or in this case my math expressions. But I want to create sub-formulas with some variables pre-defined and caching this new functions.
About my user case is a compliance engine. There are a many of predefined rules created by user and we have sensors generating input for that "acceptance tree". This predefined rules rarely, or never change. But the tolerance values may change slightly depending on the quality of the raw material.
Example: Weight Acceptance Rule = "input - 1000 >= tolerance".
In this case my expression will never change but the tolerance value can change, or not, in a week for example.
It's like I said at the beginning, I can create the expression with all the values, except for the input of the sensor, but in this case would have to deal with the substitution by myself or I will always treating all variables as input losing performance.
Do you see any other way?
You have convinced me 🙂 I agree with your use case. I will review your pull request this evening and add it as feature for Jace 1.0
uow great news! looking forward to this new release! 😄
I have merged pull request #44 into the dev branch
I will also add support to define constants in formulas declarative:
CalculationEngine engine = new CalculationEngine(CultureInfo.InvariantCulture, ExecutionMode.Compiled);
Func<double, double, double> formula = (Func<double, double, double>)engine.Formula("a+b+c")
.Parameter("b", DataType.FloatingPoint)
.Parameter("c", DataType.FloatingPoint)
.Constant("a", 1)
.Result(DataType.FloatingPoint)
.Build();
double result = formula(2.0, 2.0);
Assert.AreEqual(5.0, result);
I have updated the wiki: https://github.com/pieterderycke/Jace/wiki/Compile-Time-constants
This was released as part of Jace 1.0
Hi,
Is it possible to add constant vars when build a function? something like that:
engine.build("a+b+c").Parameter("a",1").Parameter("b",2").Build();
Or I need to replace constant variable by myself before create a function? engine.build("1+2+c").Build();
tks