Montag, 8. September 2014

C# Code kompilieren

Im heutigen Post möchte ich zeigen, wie man mit C# C# Code kompilieren kann und somit zur Laufzeit quasi neue Programme erzeugen und ausführen kann.
Dies geht relativ einfach, unter anderem bietet .Net zum Beispiel die Klasse CodeDomProvider an, mit welcher direkt Code kompiliert werden kann. Mehr dazu kann auf MSDN nachgelesen werden, von dort habe ich auch das Beispiel entnommen.
Das folgende Programm kompiliert den in einer Textbox eingegebenen C# Code, erzeugt daraus eine .exe Datei und führt diese dann aus. Der Code sollte eigentlich selbsterklärend sein, ich liste ihn zuerst auf, und erkläre anschließend das Wichtigste. Es gibt 2 Textboxes, in der einen wird der Quellcode eingegeben, in der anderen das Kompilierergebnis (Erfolg oder die aufgetretenen Fehler) ausgegeben. Außerdem gibt es 2 Buttons, der eine kompiliert den Code, der andere führt das entstandene Programm aus:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.IO;
using System.CodeDom.Compiler;

namespace CodeCompiler
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public void CompileExecutable(String[] sourceName)
        {
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");

            CompilerParameters cp = new CompilerParameters();

            cp.GenerateExecutable = true;
            cp.OutputAssembly = "Compiled.exe";
            cp.GenerateInMemory = false;
            cp.TreatWarningsAsErrors = false;

            CompilerResults cr = provider.CompileAssemblyFromSource(cp, sourceName);

            if (cr.Errors.Count > 0)
            {
                textBox2.Text = "Errors: " + Environment.NewLine;
                foreach (CompilerError ce in cr.Errors)
                {
                    textBox2.Text += ce.ToString() + Environment.NewLine;
                }
            }
            else
            {
                textBox2.Text = "Success";
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string[] SourceCode = new string[1];
            SourceCode[0] = textBox1.Text;
            CompileExecutable(SourceCode);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start("Compiled.exe");
        }
    }
}

In der ersten Zeile der Funktion CompileExecutable() wird ein neuer Code Provider für C# erstellt. Danach werden 4 Kompilieroptionen festgelegt: Mittels GenerateExecutable = true teilen wir dem Programm mit, dass wir eine ausführbare Datei erstellen möchten und nicht, zum Beispiel, eine Klassenbibliothek. Über die nächsten Zeilen definieren wir, dass wir die Datei tatsächlich erzeugen möchten und legen den Ausgabepfad fest, außerdem die Behandlung von Warnungen.
Mittels CompileAssemblyFromSource() kompilieren wir schließlich den Code. Hierbei müssen wir die eben erzeugten Kompilierargumente übergeben und ein Array von Strings, welches die Quellcodes enthält - es können also mehrere Programme auf einmal kompiliert werden.
Anschließend wird die Anzahl der Fehler abgefragt und, falls diese größer 0 ist, werden die Fehler ausgegeben.
Mittels System.Diagnostics.Process.Start() wird dann einfach das erzeugte Programm gestartet.

Mittwoch, 3. September 2014

Gurobi für Windows 64-bit: Ein Ausnahmefehler des Typs "System.BadImageFormatException" ist in Gurobi56.NET.dll aufgetreten.

Beim Versuch, den Gurobi Optimierer in der 64 Bit Version mit C# .Net zu benutzen, kam es bei mir zu einem Fehler. Beim Ausführen des Programmcodes kam der folgende Fehler: "Ein Ausnahmefehler des Typs "System.BadImageFormatException" ist in Gurobi56.NET.dll aufgetreten.". Wie hier zu lesen ist, liegt das wohl daran, dass eine DLL für ASP .Net für 32 Bit kompiliert ist und so auch die anderen DLLs zwingt, in 32 Bit zu laufen. Das heißt wohl, die 64 Bit Version funktioniert unter .Net erstmal nicht. Also die 32 Bit Version installieren, um den Fehler zu umgehen. Falls die 64 Bit Version anfängt zu laufen, würde ich mich über einen Kommentar freuen.

Dienstag, 2. September 2014

(Lineare) Optimierung mit C# und Gurobi

Den heutigen Post möchte ich dem Optimierungsprogramm Gurobi widmen, mit welchem Optimierungsprobleme gelöst werden können, speziell lineare und quadratische Programme.
Gurobi ist an sich kostenpflichtig, Angehörige einer akademischen Einrichtung (wie Studenten einer Universität) können sich allerdings eine kostenlose Lizenz besorgen, für alle anderen gibt es Trial Lizenzen. Für viele verschiedenen Programmiersprachen, wie auch die .Net Sprachen, werden Schnittstellen angeboten.
Zuerst muss Gurobi heruntergeladen werden, welches hier gemacht werden kann. Achtung: Für .Net scheint wohl die 64 Bit Version nicht zu funktionieren, die 32 Bit Version läuft aber auf allen Systemen - siehe nächster Post.
Danach ist eine Gurobi Lizenz zu erwerben. Hierfür muss man auf der Gurobi Website unter Downloads - Licenses die entsprechende Lizenz auswählen und auf "Request License" klicken. Auf der sich öffnenden Seite befindet ein Befehl in der Form "grbgetkey e7300a11-...", welcher mittels Start - Ausführen ausgeführt werden muss. Daraufhin wird der Gurobi Server kontaktiert, die Lizenz erworben und auf dem Computer gespeichert. Nun ist der Solver einsatzbereit.
Ich möchte nun ein Beispiel zur Benutzung dieses geben und werde hierfür das gleiche Beispiel wie im Gurobi Quickstart Guide verwenden.
Ziel ist die Optimierung des folgenden Modells:

Maximiere x + y + 2z
s.t. x + 2y + 3z <= 4
x + y >= 1
x, y ∈ {0, 1}

Um Gurobi in .Net benutzen zu können, müssen wir zuerst einen Verweis auf die Bibliothek Gurobi56.NET.dll zu unserem Projekt hinzufügen. Bei mir befindet sich diese Datei im Verzeichnis C:\gurobi563\win32\bin.
Der C# Code für eine Konsolenanwendung, welcher obiges Beispiel modelliert, lautet dann:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Gurobi;

namespace GurobiConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                GRBEnv env = new GRBEnv("mip1.log");
                GRBModel model = new GRBModel(env);

                // Create variables

                GRBVar x = model.AddVar(0.0, 1.0, 0.0, GRB.BINARY, "x");
                GRBVar y = model.AddVar(0.0, 1.0, 0.0, GRB.BINARY, "y");
                GRBVar z = model.AddVar(0.0, 1.0, 0.0, GRB.BINARY, "z");

                // Integrate new variables

                model.Update();

                // Set objective: maximize x + y + 2 z

                model.SetObjective(x + y + 2 * z, GRB.MAXIMIZE);

                // Add constraint: x + 2 y + 3 z <= 4

                model.AddConstr(x + 2 * y + 3 * z <= 4.0, "c0");

                // Add constraint: x + y >= 1

                model.AddConstr(x + y >= 1.0, "c1");

                // Optimize model

                model.Optimize();

                Console.WriteLine(x.Get(GRB.StringAttr.VarName)
                                   + " " + x.Get(GRB.DoubleAttr.X));
                Console.WriteLine(y.Get(GRB.StringAttr.VarName)
                                   + " " + y.Get(GRB.DoubleAttr.X));
                Console.WriteLine(z.Get(GRB.StringAttr.VarName)
                                   + " " + z.Get(GRB.DoubleAttr.X));

                Console.WriteLine("Obj: " + model.Get(GRB.DoubleAttr.ObjVal));

                // Dispose of model and env

                model.Dispose();
                env.Dispose();

                string Dummy = Console.ReadLine();

            }
            catch (GRBException ex)
            {
                Console.WriteLine("Error code: " + ex.ErrorCode + ". " + ex.Message);
            }
        }
    }
}

Gehen wir diesen Schritt für Schritt durch. Anfangs erstellen wir eine Gurobi Umgebung und ein Modell, als Parameter geben wir die Log Datei, in welche Meldungen des Programms geschrieben werden. Dann legen wir die 3 benötigten Variablen x, y und z an. Die ersten beiden Parameter sind Unter- und Obergrenze für diese, der dritte Wert gibt den Koeffizient in der Zielfunktion an. Dieser ist hier 0, da wir diese später definieren. GRB.BINARY gibt an, dass die Variable binär ist. Andere mögliche Werte sind GRB.INTEGER (ganzzahlig) und GRB.CONTINOUS (reell). Die darauf folgenden Befehle sollten selbsterklärend sein, mittels SetObjective() setzen wir die Zielfunktion, mittels AddConstr() definieren wir Nebenbedingungen. Die ermittelte Lösung wird schließlich in der Konsole ausgegeben.
Hat das LP keine Lösung oder ist unbeschränkt, wird ein Fehler beim Abfragen der Variablenwerte geworfen, nämlich "Error at GRBVar.Get".