mathnet-symbolics icon indicating copy to clipboard operation
mathnet-symbolics copied to clipboard

How to extend with a custom function in c#?

Open omnilogix opened this issue 1 year ago • 2 comments

I would like to add a ccustom function that gets evaluated when calling SymbolicExpression.Evaluate();

for instance SqFt(x, y) where I would calculate square footage from the parameters

public double SqFt(double x, double y) { return x * y; }

SymbolicExpression.Parse("SqFt(23, 15)");

When using NCalc I was able to attach a delegate that would receive the function name and arguments so I could return a result.

Is there a way to do this with MathNet?

Thanks

omnilogix avatar Oct 17 '24 00:10 omnilogix

Hi @omnilogix ,

You can try my fork:

https://github.com/ingted/symbolic.net

I supported several function customization ways. (Check the following pages)

  1. https://github.com/ingted/symbolic.net/blob/main/SymbolicNet6/scripts/test.20250618.fsx

Add your function into Definition.funDict

Definition.funDict.TryRemove "iif"
Definition.funDict.TryAdd ("iif", (DTProc ([
    [symX;symY;symZ], (DBFun ((fun parentScopeIdOpt procEnv symbolValues exprs ->
        let g = procEnv.gCtx
        let s = procEnv.sCtx
        let prevO = procEnv.prevOutput
        let stx = procEnv.stx
        let ifTop = procEnv.depth = 0
        let _, X = stx.Value.TryGetValue "x"
        let _, Y_ = stx.Value.TryGetValue "y"
        let _, Z_ = stx.Value.TryGetValue "z"
        printfn "iif => %A %A %A %A" X Y_ Z_ exprs

        let procNestedExp f =
            match f with
            | NestedExpr l ->
                //match l with
                //| [FunInvocation (Symbol "expr", ll)] ->
                    let evaluated = evaluate2 (Evaluate.IF_PRECISE, parentScopeIdOpt, symbolValues, procEnv) (FunInvocation (Symbol "eval", [FunInvocation (Symbol "expr", l)]))
                    evaluated.eRst
                //| _ ->
                //    printfn "procNestedExp l: %A" l
                //    f
            | _ ->
                f

        let Y = procNestedExp Y_
        let Z = procNestedExp Z_

        match X with
        | FB b -> 
            {
                procEnv
                    with
                        prevOutput = Some (if b then Y else Z)
            }
        | NestedExpr [FunInvocation (Symbol b, [])] when b = "true" || b = "false" ->
            {
                procEnv
                    with
                        prevOutput = Some (if b = "true" then Y else Z)
            }
        | NestedExpr l ->
            printfn "l: %A" l
            let evaluated = evaluate2 (Evaluate.IF_PRECISE, parentScopeIdOpt, symbolValues, procEnv) (FunInvocation (Symbol "eval", [FunInvocation (Symbol "expr",l)]))
            let rst =
                match evaluated.eRst with
                | FB b -> b
                | v -> failwithf "Invalid return type, FB <bool> expected, but %A evaluated" v
            {
                evaluated.eEnv
                    with
                        prevOutput = Some (if rst then Y else Z)
            }
        | v ->
            failwithf "Invalid logical expr, FB <bool> or NestedExpr <Expression list> expected, but %A provided" v
        
    ), OutFP))
], 0, None)))
  1. https://github.com/ingted/symbolic.net/blob/main/FalueTests/Program.fs

using define to define function with expression string.

This fork also support variable define functionality, that means it supports symbolic scripts!!


Definition.funDict.TryRemove "main"
Definition.funDict.TryAdd ("main", DTProc ([
    [], (DBExp ([
        Infix.parseOrThrow "let(ttc1, 789)"
        //Infix.parseOrThrow "let(y, 10000000)"
        Infix.parseOrThrow "print(ttc)"
        Infix.parseOrThrow "print(ttc1)"
        Infix.parseOrThrow "print(ttc1)"
        Infix.parseOrThrow "def(ttc, 2, 3, x, y, def(t1,1,1,x,x+100000+y), print(x*2), t1(x + y/3))"
        Infix.parseOrThrow "print(ttc(ttc1, 12))"
        Infix.parseOrThrow "let(gg,ttc(ttc1, 12))"
        Infix.parseOrThrow "print(gg)"
    ], OutVar [Symbol "gg"]))
], 0, None))
(SymbolicExpression.Parse "main()").Evaluate(dict ["ttc", FloatingPoint.Real 9487.0]) |> chk (NestedList [BR 100805N]) "failed 0010"

ingted avatar Jun 23 '25 01:06 ingted

By the way, it also supports Tensor/Deedle data frame/ConcurrentPersistedDictionary, if you don't need them, you could just comment them out in fsproj.

<IfTensorSupport>true</IfTensorSupport>
<TargetFrameworks>net9.0<!--;netstandard2.1--></TargetFrameworks>

ingted avatar Jun 23 '25 01:06 ingted