#region License Information
/* HeuristicLab
* Copyright (C) 2002-2008 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeuristicLab is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HeuristicLab. If not, see .
*/
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using HeuristicLab.Core;
using System.Diagnostics;
using HeuristicLab.Constraints;
using HeuristicLab.DataAnalysis;
using System.Xml;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.IO;
using HeuristicLab.Operators.Programmable;
using HeuristicLab.Data;
using System.Collections;
namespace HeuristicLab.Functions {
public sealed class ProgrammableFunction : ProgrammableOperator, IFunction {
private MethodInfo applyMethod;
public ProgrammableFunction() {
// clear the variableinfo that was added by the default constructor of ProgrammableOperator
RemoveVariableInfo("Result");
Code = "return 0.0;";
SetDescription("A function that can be programmed for arbitrary needs.");
applyMethod = null;
}
public override void Compile() {
CodeNamespace ns = new CodeNamespace("HeuristicLab.Functions.CustomFunctions");
CodeTypeDeclaration typeDecl = new CodeTypeDeclaration("Function");
typeDecl.IsClass = true;
typeDecl.TypeAttributes = TypeAttributes.Public;
CodeMemberMethod method = new CodeMemberMethod();
method.Name = "Apply";
method.ReturnType = new CodeTypeReference(typeof(double));
method.Attributes = MemberAttributes.Public | MemberAttributes.Static;
method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(Dataset), "dataset"));
method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "index"));
foreach(IVariableInfo info in VariableInfos)
method.Parameters.Add(new CodeParameterDeclarationExpression(info.DataType, info.FormalName));
method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(double[]), "args"));
string code = Code;
method.Statements.Add(new CodeSnippetStatement(code));
typeDecl.Members.Add(method);
ns.Types.Add(typeDecl);
ns.Imports.Add(new CodeNamespaceImport("System"));
ns.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
ns.Imports.Add(new CodeNamespaceImport("System.Text"));
ns.Imports.Add(new CodeNamespaceImport("HeuristicLab.Core"));
ns.Imports.Add(new CodeNamespaceImport("HeuristicLab.Functions"));
foreach(IVariableInfo variableInfo in VariableInfos)
ns.Imports.Add(new CodeNamespaceImport(variableInfo.DataType.Namespace));
CodeCompileUnit unit = new CodeCompileUnit();
unit.Namespaces.Add(ns);
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.GenerateInMemory = true;
parameters.IncludeDebugInformation = false;
Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach(Assembly loadedAssembly in loadedAssemblies)
parameters.ReferencedAssemblies.Add(loadedAssembly.Location);
CodeDomProvider provider = new CSharpCodeProvider();
CompilerResults results = provider.CompileAssemblyFromDom(parameters, unit);
applyMethod = null;
if(results.Errors.HasErrors) {
StringWriter writer = new StringWriter();
CodeGeneratorOptions options = new CodeGeneratorOptions();
options.BlankLinesBetweenMembers = false;
options.ElseOnClosing = true;
options.IndentString = " ";
provider.GenerateCodeFromCompileUnit(unit, writer, options);
writer.Flush();
string[] source = writer.ToString().Split(new string[] { "\r\n" }, StringSplitOptions.None);
StringBuilder builder = new StringBuilder();
for(int i = 0; i < source.Length; i++)
builder.AppendLine((i + 1).ToString("###") + " " + source[i]);
builder.AppendLine();
builder.AppendLine();
builder.AppendLine();
foreach(CompilerError error in results.Errors) {
builder.Append("Line " + error.Line.ToString());
builder.Append(", Column " + error.Column.ToString());
builder.AppendLine(": " + error.ErrorText);
}
throw new Exception("Compile Errors:\n\n" + builder.ToString());
} else {
Assembly assembly = results.CompiledAssembly;
Type[] types = assembly.GetTypes();
applyMethod = types[0].GetMethod("Apply");
}
}
#region IFunction Members
public void Accept(IFunctionVisitor visitor) {
visitor.Visit(this);
}
public IFunctionTree GetTreeNode() {
return new BakedFunctionTree(this);
}
public double Apply(Dataset dataset, int sampleIndex, double[] args) {
// collect parameters
ArrayList parameters = new ArrayList(args.Length);
parameters.Add(dataset);
parameters.Add(sampleIndex);
int j = 0;
List backupValues = new List();
// all local variables are available in the custom function
// values of local variables need to be adjusted based on the arguments we recieve from the evaluator
// because each instance of programmable-function can have different values for the local variables
foreach(IVariableInfo info in VariableInfos) {
if(info.Local) {
IVariable localVariable = GetVariable(info.FormalName);
double curValue = GetDoubleValue(localVariable);
backupValues.Add(curValue);
SetFromDoubleValue(localVariable, args[j++]);
parameters.Add(localVariable.Value);
}
}
// copy the evaluation results of the sub-branches into a separate array (last argument of user-defined function)
double[] evaluationResults = new double[args.Length - j];
Array.Copy(args, j, evaluationResults, 0, evaluationResults.Length);
parameters.Add(evaluationResults);
// lazy activation of the user-programmed code
if(applyMethod == null) {
Compile();
}
double result = (double)applyMethod.Invoke(null, parameters.ToArray());
// restore backed up values of variables (just a savety measure. changes of the function-library are unwanted)
j = 0;
foreach(IVariableInfo info in VariableInfos) {
if(info.Local) {
SetFromDoubleValue(GetVariable(info.FormalName), backupValues[j]);
}
}
return result;
}
private double GetDoubleValue(IVariable localVariable) {
IItem value = localVariable.Value;
if(value is ConstrainedDoubleData) {
return ((ConstrainedDoubleData)value).Data;
} else if(value is ConstrainedIntData) {
return (double)((ConstrainedIntData)value).Data;
} else if(value is DoubleData) {
return ((DoubleData)value).Data;
} else if(value is IntData) {
return (double)((IntData)value).Data;
} else throw new NotSupportedException("Datatype of variable " + localVariable.Name + " is not supported as local variable for programmable-functions.");
}
private void SetFromDoubleValue(IVariable variable, double x) {
IItem value = variable.Value;
if(value is ConstrainedDoubleData) {
((ConstrainedDoubleData)value).Data = x;
} else if(value is ConstrainedIntData) {
((ConstrainedIntData)value).Data = (int)x;
} else if(value is DoubleData) {
((DoubleData)value).Data = x;
} else if(value is IntData) {
((IntData)value).Data = (int)x;
} else throw new NotSupportedException("Datatype of variable " + variable.Name + " is not supported as local variable for programmable-functions.");
}
#endregion
#region disabled operator functionality
// operator-tree style evaluation is not supported for functions.
public override IOperation Apply(IScope scope) {
throw new NotSupportedException();
}
private static readonly List emptySubOperatorList = new List();
public override IList SubOperators {
get { return emptySubOperatorList; }
}
public override void AddSubOperator(IOperator subOperator) {
throw new NotSupportedException();
}
public override bool TryAddSubOperator(IOperator subOperator) {
throw new NotSupportedException();
}
public override bool TryAddSubOperator(IOperator subOperator, int index) {
throw new NotSupportedException();
}
public override bool TryAddSubOperator(IOperator subOperator, int index, out ICollection violatedConstraints) {
throw new NotSupportedException();
}
public override bool TryAddSubOperator(IOperator subOperator, out ICollection violatedConstraints) {
throw new NotSupportedException();
}
public override void AddSubOperator(IOperator subOperator, int index) {
throw new NotSupportedException();
}
public override void RemoveSubOperator(int index) {
throw new NotSupportedException();
}
public override bool TryRemoveSubOperator(int index) {
throw new NotSupportedException();
}
public override bool TryRemoveSubOperator(int index, out ICollection violatedConstraints) {
throw new NotSupportedException();
}
#endregion
}
}