// Define a function for running the simulation of a population system S with timestep // dt. The number of timesteps and the data buffer are parameters of the defined function. static Func <int, double[, ], int> DefineSimulate(double dt, PopulationSystem S) { CodeGen code = new CodeGen(); // Define a parameter for the current population x, and define mappings to the // expressions defined above. LinqExpr N = code.Decl <int>(Scope.Parameter, "N"); LinqExpr Data = code.Decl <double[, ]>(Scope.Parameter, "Data"); // Loop over the sample range requested. Note that this loop is a 'runtime' loop, // while the rest of the loops nested in the body of this loop are 'compile time' loops. LinqExpr n = code.DeclInit <int>("n", 1); code.For( () => { }, LinqExpr.LessThan(n, N), () => code.Add(LinqExpr.PostIncrementAssign(n)), () => { // Define expressions representing the population of each species. List <Expression> x = new List <Expression>(); for (int i = 0; i < S.N; ++i) { // Define a variable xi. Expression xi = "x" + i.ToString(); x.Add(xi); // xi = Data[n, i]. code.DeclInit(xi, LinqExpr.ArrayAccess(Data, LinqExpr.Subtract(n, LinqExpr.Constant(1)), LinqExpr.Constant(i))); } for (int i = 0; i < S.N; ++i) { // This list is the elements of the sum representing the i'th // row of f, i.e. r_i + (A*x)_i. Expression dx_dt = 1; for (int j = 0; j < S.N; ++j) { dx_dt -= S.A[i, j] * x[j]; } // Define dx_i/dt = x_i * f_i(x), as per the Lotka-Volterra equations. dx_dt *= x[i] * S.r[i]; // Euler's method for x(t) is: x(t) = x(t - h) + h * x'(t - h). Expression integral = x[i] + dt * dx_dt; // Data[n, i] = Data[n - 1, i] + dt * dx_dt; code.Add(LinqExpr.Assign( LinqExpr.ArrayAccess(Data, n, LinqExpr.Constant(i)), code.Compile(integral))); } }); code.Return(N); // Compile the generated code. LinqExprs.Expression <Func <int, double[, ], int> > expr = code.Build <Func <int, double[, ], int> >(); return(expr.Compile()); }
// Solve a system of linear equations private static void Solve(CodeGen code, LinqExpr Ab, IEnumerable <LinearCombination> Equations, IEnumerable <Expression> Unknowns) { LinearCombination[] eqs = Equations.ToArray(); Expression[] deltas = Unknowns.ToArray(); int M = eqs.Length; int N = deltas.Length; // Initialize the matrix. for (int i = 0; i < M; ++i) { LinqExpr Abi = code.ReDeclInit <double[]>("Abi", LinqExpr.ArrayAccess(Ab, LinqExpr.Constant(i))); for (int x = 0; x < N; ++x) { code.Add(LinqExpr.Assign( LinqExpr.ArrayAccess(Abi, LinqExpr.Constant(x)), code.Compile(eqs[i][deltas[x]]))); } code.Add(LinqExpr.Assign( LinqExpr.ArrayAccess(Abi, LinqExpr.Constant(N)), code.Compile(eqs[i][1]))); } // Gaussian elimination on this turd. //RowReduce(code, Ab, M, N); code.Add(LinqExpr.Call( GetMethod <Simulation>("RowReduce", Ab.Type, typeof(int), typeof(int)), Ab, LinqExpr.Constant(M), LinqExpr.Constant(N))); // Ab is now upper triangular, solve it. for (int j = N - 1; j >= 0; --j) { LinqExpr _j = LinqExpr.Constant(j); LinqExpr Abj = code.ReDeclInit <double[]>("Abj", LinqExpr.ArrayAccess(Ab, _j)); LinqExpr r = LinqExpr.ArrayAccess(Abj, LinqExpr.Constant(N)); for (int ji = j + 1; ji < N; ++ji) { r = LinqExpr.Add(r, LinqExpr.Multiply(LinqExpr.ArrayAccess(Abj, LinqExpr.Constant(ji)), code[deltas[ji]])); } code.DeclInit(deltas[j], LinqExpr.Divide(LinqExpr.Negate(r), LinqExpr.ArrayAccess(Abj, _j))); } }
// Use homotopy method with newton's method to find a solution for F(x) = 0. private static List <Arrow> NSolve(List <Expression> F, List <Arrow> x0, double Epsilon, int MaxIterations) { int M = F.Count; int N = x0.Count; // Compute JxF, the Jacobian of F. List <Dictionary <Expression, Expression> > JxF = Jacobian(F, x0.Select(i => i.Left)).ToList(); // Define a function to evaluate JxH(x), where H = F(x) - s*F(x0). CodeGen code = new CodeGen(); ParamExpr _JxH = code.Decl <double[, ]>(Scope.Parameter, "JxH"); ParamExpr _x0 = code.Decl <double[]>(Scope.Parameter, "x0"); ParamExpr _s = code.Decl <double>(Scope.Parameter, "s"); // Load x_j from the input array and add them to the map. for (int j = 0; j < N; ++j) { code.DeclInit(x0[j].Left, LinqExpr.ArrayAccess(_x0, LinqExpr.Constant(j))); } LinqExpr error = code.Decl <double>("error"); // Compile the expressions to assign JxH for (int i = 0; i < M; ++i) { LinqExpr _i = LinqExpr.Constant(i); for (int j = 0; j < N; ++j) { code.Add(LinqExpr.Assign( LinqExpr.ArrayAccess(_JxH, _i, LinqExpr.Constant(j)), code.Compile(JxF[i][x0[j].Left]))); } // e = F(x) - s*F(x0) LinqExpr e = code.DeclInit <double>("e", LinqExpr.Subtract(code.Compile(F[i]), LinqExpr.Multiply(LinqExpr.Constant((double)F[i].Evaluate(x0)), _s))); code.Add(LinqExpr.Assign(LinqExpr.ArrayAccess(_JxH, _i, LinqExpr.Constant(N)), e)); // error += e * e code.Add(LinqExpr.AddAssign(error, LinqExpr.Multiply(e, e))); } // return error code.Return(error); Func <double[, ], double[], double, double> JxH = code.Build <Func <double[, ], double[], double, double> >().Compile(); double[] x = new double[N]; // Remember where we last succeeded/failed. double s0 = 0.0; double s1 = 1.0; do { try { // H(F, s) = F - s*F0 NewtonsMethod(M, N, JxH, s0, x, Epsilon, MaxIterations); // Success at this s! s1 = s0; for (int i = 0; i < N; ++i) { x0[i] = Arrow.New(x0[i].Left, x[i]); } // Go near the goal. s0 = Lerp(s0, 0.0, 0.9); } catch (FailedToConvergeException) { // Go near the last success. s0 = Lerp(s0, s1, 0.9); for (int i = 0; i < N; ++i) { x[i] = (double)x0[i].Right; } } } while (s0 > 0.0 && s1 >= s0 + 1e-6); // Make sure the last solution is at F itself. if (s0 != 0.0) { NewtonsMethod(M, N, JxH, 0.0, x, Epsilon, MaxIterations); for (int i = 0; i < N; ++i) { x0[i] = Arrow.New(x0[i].Left, x[i]); } } return(x0); }
// Generate code to perform row reduction. private static void RowReduce(CodeGen code, LinqExpr Ab, int M, int N) { // For each variable in the system... for (int j = 0; j + 1 < N; ++j) { LinqExpr _j = LinqExpr.Constant(j); LinqExpr Abj = code.ReDeclInit <double[]>("Abj", LinqExpr.ArrayAccess(Ab, _j)); // int pi = j LinqExpr pi = code.ReDeclInit <int>("pi", _j); // double max = |Ab[j][j]| LinqExpr max = code.ReDeclInit <double>("max", Abs(LinqExpr.ArrayAccess(Abj, _j))); // Find a pivot row for this variable. //code.For(j + 1, M, _i => //{ for (int i = j + 1; i < M; ++i) { LinqExpr _i = LinqExpr.Constant(i); // if(|Ab[i][j]| > max) { pi = i, max = |Ab[i][j]| } LinqExpr maxj = code.ReDeclInit <double>("maxj", Abs(LinqExpr.ArrayAccess(LinqExpr.ArrayAccess(Ab, _i), _j))); code.Add(LinqExpr.IfThen( LinqExpr.GreaterThan(maxj, max), LinqExpr.Block( LinqExpr.Assign(pi, _i), LinqExpr.Assign(max, maxj)))); } // (Maybe) swap the pivot row with the current row. LinqExpr Abpi = code.ReDecl <double[]>("Abpi"); code.Add(LinqExpr.IfThen( LinqExpr.NotEqual(_j, pi), LinqExpr.Block( new[] { LinqExpr.Assign(Abpi, LinqExpr.ArrayAccess(Ab, pi)) }.Concat( Enumerable.Range(j, N + 1 - j).Select(x => Swap( LinqExpr.ArrayAccess(Abj, LinqExpr.Constant(x)), LinqExpr.ArrayAccess(Abpi, LinqExpr.Constant(x)), code.ReDecl <double>("swap"))))))); //// It's hard to believe this swap isn't faster than the above... //code.Add(LinqExpr.IfThen(LinqExpr.NotEqual(_j, pi), LinqExpr.Block( // Swap(LinqExpr.ArrayAccess(Ab, _j), LinqExpr.ArrayAccess(Ab, pi), Redeclare<double[]>(code, "temp")), // LinqExpr.Assign(Abj, LinqExpr.ArrayAccess(Ab, _j))))); // Eliminate the rows after the pivot. LinqExpr p = code.ReDeclInit <double>("p", LinqExpr.ArrayAccess(Abj, _j)); //code.For(j + 1, M, _i => //{ for (int i = j + 1; i < M; ++i) { LinqExpr _i = LinqExpr.Constant(i); LinqExpr Abi = code.ReDeclInit <double[]>("Abi", LinqExpr.ArrayAccess(Ab, _i)); // s = Ab[i][j] / p LinqExpr s = code.ReDeclInit <double>("scale", LinqExpr.Divide(LinqExpr.ArrayAccess(Abi, _j), p)); // Ab[i] -= Ab[j] * s for (int ji = j + 1; ji < N + 1; ++ji) { code.Add(LinqExpr.SubtractAssign( LinqExpr.ArrayAccess(Abi, LinqExpr.Constant(ji)), LinqExpr.Multiply(LinqExpr.ArrayAccess(Abj, LinqExpr.Constant(ji)), s))); } } } }
// The resulting lambda processes N samples, using buffers provided for Input and Output: // void Process(int N, double t0, double T, double[] Input0 ..., double[] Output0 ...) // { ... } private Delegate DefineProcess() { // Map expressions to identifiers in the syntax tree. List <KeyValuePair <Expression, LinqExpr> > inputs = new List <KeyValuePair <Expression, LinqExpr> >(); List <KeyValuePair <Expression, LinqExpr> > outputs = new List <KeyValuePair <Expression, LinqExpr> >(); // Lambda code generator. CodeGen code = new CodeGen(); // Create parameters for the basic simulation info (N, t, Iterations). ParamExpr SampleCount = code.Decl <int>(Scope.Parameter, "SampleCount"); ParamExpr t = code.Decl(Scope.Parameter, Simulation.t); // Create buffer parameters for each input... foreach (Expression i in Input) { inputs.Add(new KeyValuePair <Expression, LinqExpr>(i, code.Decl <double[]>(Scope.Parameter, i.ToString()))); } // ... and output. foreach (Expression i in Output) { outputs.Add(new KeyValuePair <Expression, LinqExpr>(i, code.Decl <double[]>(Scope.Parameter, i.ToString()))); } // Create globals to store previous values of inputs. foreach (Expression i in Input.Distinct()) { AddGlobal(i.Evaluate(t_t0)); } // Define lambda body. // int Zero = 0 LinqExpr Zero = LinqExpr.Constant(0); // double h = T / Oversample LinqExpr h = LinqExpr.Constant(TimeStep / (double)Oversample); // Load the globals to local variables and add them to the map. foreach (KeyValuePair <Expression, GlobalExpr <double> > i in globals) { code.Add(LinqExpr.Assign(code.Decl(i.Key), i.Value)); } foreach (KeyValuePair <Expression, LinqExpr> i in inputs) { code.Add(LinqExpr.Assign(code.Decl(i.Key), code[i.Key.Evaluate(t_t0)])); } // Create arrays for linear systems. int M = Solution.Solutions.OfType <NewtonIteration>().Max(i => i.Equations.Count(), 0); int N = Solution.Solutions.OfType <NewtonIteration>().Max(i => i.UnknownDeltas.Count(), 0) + 1; LinqExpr JxF = code.DeclInit <double[][]>("JxF", LinqExpr.NewArrayBounds(typeof(double[]), LinqExpr.Constant(M))); for (int j = 0; j < M; ++j) { code.Add(LinqExpr.Assign(LinqExpr.ArrayAccess(JxF, LinqExpr.Constant(j)), LinqExpr.NewArrayBounds(typeof(double), LinqExpr.Constant(N)))); } // for (int n = 0; n < SampleCount; ++n) ParamExpr n = code.Decl <int>("n"); code.For( () => code.Add(LinqExpr.Assign(n, Zero)), LinqExpr.LessThan(n, SampleCount), () => code.Add(LinqExpr.PreIncrementAssign(n)), () => { // Prepare input samples for oversampling interpolation. Dictionary <Expression, LinqExpr> dVi = new Dictionary <Expression, LinqExpr>(); foreach (Expression i in Input.Distinct()) { LinqExpr Va = code[i]; // Sum all inputs with this key. IEnumerable <LinqExpr> Vbs = inputs.Where(j => j.Key.Equals(i)).Select(j => j.Value); LinqExpr Vb = LinqExpr.ArrayAccess(Vbs.First(), n); foreach (LinqExpr j in Vbs.Skip(1)) { Vb = LinqExpr.Add(Vb, LinqExpr.ArrayAccess(j, n)); } // dVi = (Vb - Va) / Oversample code.Add(LinqExpr.Assign( Decl <double>(code, dVi, i, "d" + i.ToString().Replace("[t]", "")), LinqExpr.Multiply(LinqExpr.Subtract(Vb, Va), LinqExpr.Constant(1.0 / (double)Oversample)))); } // Prepare output sample accumulators for low pass filtering. Dictionary <Expression, LinqExpr> Vo = new Dictionary <Expression, LinqExpr>(); foreach (Expression i in Output.Distinct()) { code.Add(LinqExpr.Assign( Decl <double>(code, Vo, i, i.ToString().Replace("[t]", "")), LinqExpr.Constant(0.0))); } // int ov = Oversample; // do { -- ov; } while(ov > 0) ParamExpr ov = code.Decl <int>("ov"); code.Add(LinqExpr.Assign(ov, LinqExpr.Constant(Oversample))); code.DoWhile(() => { // t += h code.Add(LinqExpr.AddAssign(t, h)); // Interpolate the input samples. foreach (Expression i in Input.Distinct()) { code.Add(LinqExpr.AddAssign(code[i], dVi[i])); } // Compile all of the SolutionSets in the solution. foreach (SolutionSet ss in Solution.Solutions) { if (ss is LinearSolutions) { // Linear solutions are easy. LinearSolutions S = (LinearSolutions)ss; foreach (Arrow i in S.Solutions) { code.DeclInit(i.Left, i.Right); } } else if (ss is NewtonIteration) { NewtonIteration S = (NewtonIteration)ss; // Start with the initial guesses from the solution. foreach (Arrow i in S.Guesses) { code.DeclInit(i.Left, i.Right); } // int it = iterations LinqExpr it = code.ReDeclInit <int>("it", Iterations); // do { ... --it } while(it > 0) code.DoWhile((Break) => { // Solve the un-solved system. Solve(code, JxF, S.Equations, S.UnknownDeltas); // Compile the pre-solved solutions. if (S.KnownDeltas != null) { foreach (Arrow i in S.KnownDeltas) { code.DeclInit(i.Left, i.Right); } } // bool done = true LinqExpr done = code.ReDeclInit("done", true); foreach (Expression i in S.Unknowns) { LinqExpr v = code[i]; LinqExpr dv = code[NewtonIteration.Delta(i)]; // done &= (|dv| < |v|*epsilon) code.Add(LinqExpr.AndAssign(done, LinqExpr.LessThan(LinqExpr.Multiply(Abs(dv), LinqExpr.Constant(1e4)), LinqExpr.Add(Abs(v), LinqExpr.Constant(1e-6))))); // v += dv code.Add(LinqExpr.AddAssign(v, dv)); } // if (done) break code.Add(LinqExpr.IfThen(done, Break)); // --it; code.Add(LinqExpr.PreDecrementAssign(it)); }, LinqExpr.GreaterThan(it, Zero)); //// bool failed = false //LinqExpr failed = Decl(code, code, "failed", LinqExpr.Constant(false)); //for (int i = 0; i < eqs.Length; ++i) // // failed |= |JxFi| > epsilon // code.Add(LinqExpr.OrAssign(failed, LinqExpr.GreaterThan(Abs(eqs[i].ToExpression().Compile(map)), LinqExpr.Constant(1e-3)))); //code.Add(LinqExpr.IfThen(failed, ThrowSimulationDiverged(n))); } } // Update the previous timestep variables. foreach (SolutionSet S in Solution.Solutions) { foreach (Expression i in S.Unknowns.Where(i => globals.Keys.Contains(i.Evaluate(t_t0)))) { code.Add(LinqExpr.Assign(code[i.Evaluate(t_t0)], code[i])); } } // Vo += i foreach (Expression i in Output.Distinct()) { LinqExpr Voi = LinqExpr.Constant(0.0); try { Voi = code.Compile(i); } catch (Exception Ex) { Log.WriteLine(MessageType.Warning, Ex.Message); } code.Add(LinqExpr.AddAssign(Vo[i], Voi)); } // Vi_t0 = Vi foreach (Expression i in Input.Distinct()) { code.Add(LinqExpr.Assign(code[i.Evaluate(t_t0)], code[i])); } // --ov; code.Add(LinqExpr.PreDecrementAssign(ov)); }, LinqExpr.GreaterThan(ov, Zero)); // Output[i][n] = Vo / Oversample foreach (KeyValuePair <Expression, LinqExpr> i in outputs) { code.Add(LinqExpr.Assign(LinqExpr.ArrayAccess(i.Value, n), LinqExpr.Multiply(Vo[i.Key], LinqExpr.Constant(1.0 / (double)Oversample)))); } // Every 256 samples, check for divergence. if (Vo.Any()) { code.Add(LinqExpr.IfThen(LinqExpr.Equal(LinqExpr.And(n, LinqExpr.Constant(0xFF)), Zero), LinqExpr.Block(Vo.Select(i => LinqExpr.IfThenElse(IsNotReal(i.Value), ThrowSimulationDiverged(n), LinqExpr.Assign(i.Value, RoundDenormToZero(i.Value))))))); } }); // Copy the global state variables back to the globals. foreach (KeyValuePair <Expression, GlobalExpr <double> > i in globals) { code.Add(LinqExpr.Assign(i.Value, code[i.Key])); } LinqExprs.LambdaExpression lambda = code.Build(); Delegate ret = lambda.Compile(); return(ret); }
// Use homotopy method with newton's method to find a solution for F(x) = 0. private static List<Arrow> NSolve(List<Expression> F, List<Arrow> x0, double Epsilon, int MaxIterations) { int M = F.Count; int N = x0.Count; // Compute JxF, the Jacobian of F. List<Dictionary<Expression, Expression>> JxF = Jacobian(F, x0.Select(i => i.Left)).ToList(); // Define a function to evaluate JxH(x), where H = F(x) - s*F(x0). CodeGen code = new CodeGen(); ParamExpr _JxH = code.Decl<double[,]>(Scope.Parameter, "JxH"); ParamExpr _x0 = code.Decl<double[]>(Scope.Parameter, "x0"); ParamExpr _s = code.Decl<double>(Scope.Parameter, "s"); // Load x_j from the input array and add them to the map. for (int j = 0; j < N; ++j) code.DeclInit(x0[j].Left, LinqExpr.ArrayAccess(_x0, LinqExpr.Constant(j))); LinqExpr error = code.Decl<double>("error"); // Compile the expressions to assign JxH for (int i = 0; i < M; ++i) { LinqExpr _i = LinqExpr.Constant(i); for (int j = 0; j < N; ++j) code.Add(LinqExpr.Assign( LinqExpr.ArrayAccess(_JxH, _i, LinqExpr.Constant(j)), code.Compile(JxF[i][x0[j].Left]))); // e = F(x) - s*F(x0) LinqExpr e = code.DeclInit<double>("e", LinqExpr.Subtract(code.Compile(F[i]), LinqExpr.Multiply(LinqExpr.Constant((double)F[i].Evaluate(x0)), _s))); code.Add(LinqExpr.Assign(LinqExpr.ArrayAccess(_JxH, _i, LinqExpr.Constant(N)), e)); // error += e * e code.Add(LinqExpr.AddAssign(error, LinqExpr.Multiply(e, e))); } // return error code.Return(error); Func<double[,], double[], double, double> JxH = code.Build<Func<double[,], double[], double, double>>().Compile(); double[] x = new double[N]; // Remember where we last succeeded/failed. double s0 = 0.0; double s1 = 1.0; do { try { // H(F, s) = F - s*F0 NewtonsMethod(M, N, JxH, s0, x, Epsilon, MaxIterations); // Success at this s! s1 = s0; for (int i = 0; i < N; ++i) x0[i] = Arrow.New(x0[i].Left, x[i]); // Go near the goal. s0 = Lerp(s0, 0.0, 0.9); } catch (FailedToConvergeException) { // Go near the last success. s0 = Lerp(s0, s1, 0.9); for (int i = 0; i < N; ++i) x[i] = (double)x0[i].Right; } } while (s0 > 0.0 && s1 >= s0 + 1e-6); // Make sure the last solution is at F itself. if (s0 != 0.0) { NewtonsMethod(M, N, JxH, 0.0, x, Epsilon, MaxIterations); for (int i = 0; i < N; ++i) x0[i] = Arrow.New(x0[i].Left, x[i]); } return x0; }
// Define a function for running the simulation of a population system S with timestep // dt. The number of timesteps and the data buffer are parameters of the defined function. static Func<int, double[, ], int> DefineSimulate(double dt, PopulationSystem S) { CodeGen code = new CodeGen(); // Define a parameter for the current population x, and define mappings to the // expressions defined above. LinqExpr N = code.Decl<int>(Scope.Parameter, "N"); LinqExpr Data = code.Decl<double[,]>(Scope.Parameter, "Data"); // Loop over the sample range requested. Note that this loop is a 'runtime' loop, // while the rest of the loops nested in the body of this loop are 'compile time' loops. LinqExpr n = code.DeclInit<int>("n", 1); code.For( () => { }, LinqExpr.LessThan(n, N), () => code.Add(LinqExpr.PostIncrementAssign(n)), () => { // Define expressions representing the population of each species. List<Expression> x = new List<Expression>(); for (int i = 0; i < S.N; ++i) { // Define a variable xi. Expression xi = "x" + i.ToString(); x.Add(xi); // xi = Data[n, i]. code.DeclInit(xi, LinqExpr.ArrayAccess(Data, LinqExpr.Subtract(n, LinqExpr.Constant(1)), LinqExpr.Constant(i))); } for (int i = 0; i < S.N; ++i) { // This list is the elements of the sum representing the i'th // row of f, i.e. r_i + (A*x)_i. Expression dx_dt = 1; for (int j = 0; j < S.N; ++j) dx_dt -= S.A[i, j] * x[j]; // Define dx_i/dt = x_i * f_i(x), as per the Lotka-Volterra equations. dx_dt *= x[i] * S.r[i]; // Euler's method for x(t) is: x(t) = x(t - h) + h * x'(t - h). Expression integral = x[i] + dt * dx_dt; // Data[n, i] = Data[n - 1, i] + dt * dx_dt; code.Add(LinqExpr.Assign( LinqExpr.ArrayAccess(Data, n, LinqExpr.Constant(i)), code.Compile(integral))); } }); code.Return(N); // Compile the generated code. LinqExprs.Expression<Func<int, double[,], int>> expr = code.Build<Func<int, double[,], int>>(); return expr.Compile(); }