Click here to Skip to main content
15,891,184 members
Articles / Programming Languages / C#

Efficient Matrix Programming in C#

Rate me:
Please Sign up or sign in to vote.
4.89/5 (45 votes)
11 Dec 20024 min read 453.1K   10.9K   152  
Fast matrix expressions evaluation, based on dynamic code generation and partial evaluation
using System;
using System.Reflection.Emit;
using System.Reflection;
using System.Threading;

namespace MetaExpr
{	
	// Binary Operator classes for the BinOpExpr
	public abstract class BinaryOp
	{
		public abstract float Apply(float a, float b);
		public abstract void Compile(ILGenerator g);
	}
	
	public class Plus : BinaryOp
	{
		public override float Apply(float a, float b)
		{
			return a+b;
		}

		public override void Compile(ILGenerator g)
		{
			g.Emit(OpCodes.Add);
		}						
	}

	public class Minus : BinaryOp
	{
		public override float Apply(float a, float b)
		{
			return a-b;
		}

		public override void Compile(ILGenerator g)
		{
			g.Emit(OpCodes.Sub);
		}
	}

	public class Times : BinaryOp
	{
		public override float Apply(float a, float b)
		{
			return a*b;
		}

		public override void Compile(ILGenerator g)
		{
			g.Emit(OpCodes.Mul);
		}
	}

	public class Div : BinaryOp
	{
		public override float Apply(float a, float b)
		{
			return a/b;
		}

		public override void Compile(ILGenerator g)
		{
			g.Emit(OpCodes.Div);
		}
	}
	

	// A literal expression specified as a floating point
	public class LiteralExpr : Expr
	{
		public LiteralExpr(float vv)
		{
			thevalue = vv;
		}

		// Compilation First Pass: none
		// Code Generation: push a float literal (partial evaluation)
		public override void Compile(ILGenerator g, CompilerContext cc)
		{
			if(cc.IsFirstPass()) return ;
			g.Emit(OpCodes.Ldc_R4, thevalue);
		}

		public override float Eval(int i, int j)
		{			
			return thevalue;
		}
		
		public override OpSize Size
		{
			get { return OpSize.Scalar; }
		}

		float thevalue;
	}

	public class UnOpExpr : Expr
	{
		public UnOpExpr(Expr ee)
		{
			e = ee;
		}
		
		public override float Eval(int i, int j)
		{
			return e.Eval(j,i);	
		}
		
		public override OpSize Size
		{
			get { return e.Size; }
		}
		
		public override void Compile(ILGenerator g, CompilerContext cc)
		{
			e.Compile(g, cc);
		}
		
		protected Expr e;
	}

	public abstract class BinaryOpExpr : Expr
	{
		public BinaryOpExpr(Expr e1, Expr e2)
		{
			expr1 = e1;
			expr2 = e2;			
		}
		
		protected Expr expr1, expr2;		
	}

	// A element by element binary operation
	public class BinaryMemOpExpr : BinaryOpExpr
	{
		public BinaryMemOpExpr(Expr e1, Expr e2, BinaryOp op) : base(e1,e2)
		{
			oper = op;
			OpSize o1 = expr1.Size;
			OpSize o2 = expr2.Size;
				
			if(o1 != o2 && (o1 != OpSize.Scalar && o2 != OpSize.Scalar))
				throw new SizeMismatchException("Binary Memberwise Mismatch");
			size = o1 == OpSize.Scalar ? o2: o1;
		}

		// Compilation First Pass: check sizes
		// Code Generation: the operator code
		public override void Compile(ILGenerator g, CompilerContext cc)
		{
			expr1.Compile(g,cc);
			expr2.Compile(g,cc);
			if(cc.IsFirstPass()) 
				return;			
			oper.Compile(g);
		}

		public override float Eval(int i, int j)
		{
			return oper.Apply(expr1.Eval(i, j), expr2.Eval(i, j));
		}

		
		public override OpSize Size
		{
			get { return size; }
		}
		
		BinaryOp oper;
		OpSize   size;
	}

	public class PowMemExpr : BinaryOpExpr
	{
		public PowMemExpr (Expr e1, Expr e2) : base(e1,e2)
		{
			
		}
		
		public override void Compile(ILGenerator g, CompilerContext cc)
		{
			if(cc.IsFirstPass())		
			{
				expr1.Compile(g, cc);
				expr2.Compile(g, cc);
				if(!(expr2.Size == OpSize.Scalar))
					throw new SizeMismatchException("Pow Operator requires a scalar second operand");
				return;				
			}
			expr1.Compile(g,cc);
			g.Emit(OpCodes.Conv_R8);
			expr2.Compile(g,cc);
			g.Emit(OpCodes.Conv_R8);
			g.EmitCall(OpCodes.Call, typeof(Math).GetMethod("Pow"), null);
			g.Emit(OpCodes.Conv_R4);							
		}			
		
		public override float Eval(int i, int j)
		{
			return (float)Math.Pow(expr1.Eval(i,j), expr2.Eval(i,j));
		}
		
		public override OpSize Size
		{
			get { return expr1.Size; }
		}
	}	

	public class FxExprOp : UnOpExpr
	{			
		public enum FxType { Cos, Sin, Ln, Log };
		
		public FxExprOp(Expr ee, FxType fx) : base(ee)
		{
			effx = fx;			
		}
		
		public override void Compile(ILGenerator g, CompilerContext cc)
		{
			if(cc.IsFirstPass())		
			{
				e.Compile(g, cc);
				return;				
			}
			MethodInfo mi = null;
			bool doublemeth = true;
			switch(effx)
			{
				case FxType.Cos: mi = typeof(Math).GetMethod("Cos"); break;
				case FxType.Sin: mi = typeof(Math).GetMethod("Sin"); break;
				case FxType.Ln: mi = typeof(Math).GetMethod("Log");  break;
				case FxType.Log: mi = typeof(Math).GetMethod("Log10");  break;
				default:
					break;					
			}
			if(mi == null) return;
			
			e.Compile(g,cc);
			if(doublemeth) 			
				g.Emit(OpCodes.Conv_R8);			
			g.EmitCall(OpCodes.Call, mi, null);
			if(doublemeth) 			
				g.Emit(OpCodes.Conv_R4);							
		}			
		
		public override float Eval(int i, int j)
		{
			float f = e.Eval(i,j);
			switch(effx)
			{
			case FxType.Cos: return (float)Math.Cos(f);
			case FxType.Sin: return (float)Math.Sin(f);
			case FxType.Ln: return (float)Math.Log(f);
			case FxType.Log: return (float)Math.Log10(f);
			default:
				return f;
			}			
		}
		
		public override OpSize Size
		{
			get { return e.Size; }
		}
		
		FxType effx;
	}	
	
	public class TransposeExpr : UnOpExpr
	{			
		public TransposeExpr(Expr e) : base(e)
		{
			
		}
		
		public override void Compile(ILGenerator g, CompilerContext cc)
		{
			if(cc.IsFirstPass())		
			{
				e.Compile(g, cc);
				return;				
			}
			
			// swap the indexes at the compiler level
			int i1 = cc.GetIndexVariable(0);
			int i2 = cc.GetIndexVariable(1);
			cc.SetIndexVariable(1, i1);
			cc.SetIndexVariable(0, i2);
			e.Compile(g,cc);
			cc.SetIndexVariable(0, i1);
			cc.SetIndexVariable(1, i2);
		}
		
		public override float Eval(int i, int j)
		{
			return e.Eval(j,i);	
		}
		
		public override OpSize Size
		{
			get {
				OpSize o = e.Size;
				return new OpSize(o.cols, o.rows);	
			}
		}
		
	}
	
	class MatrixMulOp : BinaryOpExpr
	{
		public MatrixMulOp(Expr e1, Expr e2) : base(e1,e2)
		{
			
		}
		
		// Compilation First Pass: check sizes
		// Code Generation: the operator code
		public override void Compile(ILGenerator g, CompilerContext cc)
		{
			if(cc.IsFirstPass()) {
				expr1.Compile(g,cc);
				expr2.Compile(g,cc);
				if(expr1.Size.cols != expr2.Size.rows)
					throw new SizeMismatchException("Matrix Multiplication Error");
				// allocate a new temporaneous variable
				myIndex = cc.AllocIndexVariable();
				return;
			}
			
			Label topLabel = g.DefineLabel();			
			int i1 = cc.GetIndexVariable(0);
			int i2 = cc.GetIndexVariable(1);
			int me = cc.GetIndexVariable(myIndex);
			// generate an inner loop with the variable k, we can use the stack
			/*
				double r = 0;	// the value is on the stack!
				k = 0;
				do {
					r += e1[i,k]*e2[k,j]
					k++;
				} while (k < e1.cols)
			*/
			g.Emit(OpCodes.Ldc_I4_0);	// k = 0
			g.Emit(OpCodes.Stloc, me);	

			g.Emit(OpCodes.Ldc_R4,0);
			g.MarkLabel(topLabel);								

			cc.SetIndexVariable(1, me);
			expr1.Compile(g,cc);
			cc.SetIndexVariable(0, me);
			cc.SetIndexVariable(1, i2);
			expr2.Compile(g,cc);
			cc.SetIndexVariable(0, i1);
			cc.SetIndexVariable(1, i2);
			g.Emit(OpCodes.Mul);
			g.Emit(OpCodes.Add);
			
			// increment k
			g.Emit(OpCodes.Ldloc, me);			// k =>
			g.Emit(OpCodes.Ldc_I4_1);			// 1
			g.Emit(OpCodes.Add);				// +
			g.Emit(OpCodes.Dup);
			g.Emit(OpCodes.Stloc, me);			// k <=
			
			// here from first jump
			g.Emit(OpCodes.Ldc_I4, expr1.Size.cols);	// k < cols
			g.Emit(OpCodes.Blt, topLabel);			
			
			// on the stack we have a value ...
		}

		public override float Eval(int i, int j)
		{
			int q = expr1.Size.cols;
			float r = 0;
			for(int k = 0; k < q; q++)
				r += expr1.Eval(i,k)*expr2.Eval(k,j);
			return r;
		}

		
		public override OpSize Size
		{
			get { return new OpSize(expr1.Size.rows, expr2.Size.cols); }
		}
				
		int myIndex;
	}


}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) Scuola Superiore S.Anna
Italy Italy
Assistant Professor in Applied Mechanics working in Virtual Reality, Robotics and having fun with Programming

Comments and Discussions