Index: /branches/1721-RandomForestPersistence/HeuristicLab.Algorithms.DataAnalysis/3.4/RandomForest/RandomForestClassification.cs
===================================================================
--- /branches/1721-RandomForestPersistence/HeuristicLab.Algorithms.DataAnalysis/3.4/RandomForest/RandomForestClassification.cs	(revision 10320)
+++ /branches/1721-RandomForestPersistence/HeuristicLab.Algorithms.DataAnalysis/3.4/RandomForest/RandomForestClassification.cs	(revision 10321)
@@ -131,45 +131,6 @@
     public static IClassificationSolution CreateRandomForestClassificationSolution(IClassificationProblemData problemData, int nTrees, double r, double m, int seed,
       out double rmsError, out double relClassificationError, out double outOfBagRmsError, out double outOfBagRelClassificationError) {
-      if (r <= 0 || r > 1) throw new ArgumentException("The R parameter in the random forest regression must be between 0 and 1.");
-      if (m <= 0 || m > 1) throw new ArgumentException("The M parameter in the random forest regression must be between 0 and 1.");
-
-      alglib.math.rndobject = new System.Random(seed);
-
-      Dataset dataset = problemData.Dataset;
-      string targetVariable = problemData.TargetVariable;
-      IEnumerable<string> allowedInputVariables = problemData.AllowedInputVariables;
-      IEnumerable<int> rows = problemData.TrainingIndices;
-      double[,] inputMatrix = AlglibUtil.PrepareInputMatrix(dataset, allowedInputVariables.Concat(new string[] { targetVariable }), rows);
-      if (inputMatrix.Cast<double>().Any(x => double.IsNaN(x) || double.IsInfinity(x)))
-        throw new NotSupportedException("Random forest classification does not support NaN or infinity values in the input dataset.");
-
-      int info = 0;
-      alglib.decisionforest dForest = new alglib.decisionforest();
-      alglib.dfreport rep = new alglib.dfreport(); ;
-      int nRows = inputMatrix.GetLength(0);
-      int nColumns = inputMatrix.GetLength(1);
-      int sampleSize = Math.Max((int)Math.Round(r * nRows), 1);
-      int nFeatures = Math.Max((int)Math.Round(m * (nColumns - 1)), 1);
-
-
-      double[] classValues = problemData.ClassValues.ToArray();
-      int nClasses = problemData.Classes;
-      // map original class values to values [0..nClasses-1]
-      Dictionary<double, double> classIndices = new Dictionary<double, double>();
-      for (int i = 0; i < nClasses; i++) {
-        classIndices[classValues[i]] = i;
-      }
-      for (int row = 0; row < nRows; row++) {
-        inputMatrix[row, nColumns - 1] = classIndices[inputMatrix[row, nColumns - 1]];
-      }
-      // execute random forest algorithm      
-      alglib.dforest.dfbuildinternal(inputMatrix, nRows, nColumns - 1, nClasses, nTrees, sampleSize, nFeatures, alglib.dforest.dfusestrongsplits + alglib.dforest.dfuseevs, ref info, dForest.innerobj, rep.innerobj);
-      if (info != 1) throw new ArgumentException("Error in calculation of random forest classification solution");
-
-      rmsError = rep.rmserror;
-      outOfBagRmsError = rep.oobrmserror;
-      relClassificationError = rep.relclserror;
-      outOfBagRelClassificationError = rep.oobrelclserror;
-      return new RandomForestClassificationSolution((IClassificationProblemData)problemData.Clone(), new RandomForestModel(dForest, targetVariable, allowedInputVariables, classValues));
+      var model = RandomForestModel.CreateClassificationModel(problemData, nTrees, r, m, seed, out rmsError, out relClassificationError, out outOfBagRmsError, out outOfBagRelClassificationError);
+      return new RandomForestClassificationSolution((IClassificationProblemData)problemData.Clone(), model);
     }
     #endregion
Index: /branches/1721-RandomForestPersistence/HeuristicLab.Algorithms.DataAnalysis/3.4/RandomForest/RandomForestModel.cs
===================================================================
--- /branches/1721-RandomForestPersistence/HeuristicLab.Algorithms.DataAnalysis/3.4/RandomForest/RandomForestModel.cs	(revision 10320)
+++ /branches/1721-RandomForestPersistence/HeuristicLab.Algorithms.DataAnalysis/3.4/RandomForest/RandomForestModel.cs	(revision 10321)
@@ -35,51 +35,88 @@
   [Item("RandomForestModel", "Represents a random forest for regression and classification.")]
   public sealed class RandomForestModel : NamedItem, IRandomForestModel {
-
+    // not persisted
     private alglib.decisionforest randomForest;
-    public alglib.decisionforest RandomForest {
-      get { return randomForest; }
-      set {
-        if (value != randomForest) {
-          if (value == null) throw new ArgumentNullException();
-          randomForest = value;
-          OnChanged(EventArgs.Empty);
-        }
-      }
-    }
-
+    private alglib.decisionforest RandomForest {
+      get {
+        // recalculate lazily
+        if (randomForest == null || randomForest.innerobj.trees == null) RecalculateModel();
+        return randomForest;
+      }
+    }
+
+    // instead of storing the data of the model itself
+    // we instead only store data necessary to recalculate the same model lazily on demand
+    [Storable]
+    private int seed;
+    [Storable]
+    private Dataset originalTrainingData;
+    [Storable]
+    private int[] trainingRows;
+    [Storable]
+    private double[] classValues;
+    [Storable]
+    private string[] allowedInputVariables;
     [Storable]
     private string targetVariable;
     [Storable]
-    private string[] allowedInputVariables;
-    [Storable]
-    private double[] classValues;
+    private int nTrees;
+    [Storable]
+    private double r;
+    [Storable]
+    private double m;
+
+
     [StorableConstructor]
     private RandomForestModel(bool deserializing)
       : base(deserializing) {
-      if (deserializing)
-        randomForest = new alglib.decisionforest();
+      // for backwards compatibility (loading old solutions)
+      randomForest = new alglib.decisionforest();
     }
     private RandomForestModel(RandomForestModel original, Cloner cloner)
       : base(original, cloner) {
-      randomForest = new alglib.decisionforest();
-      randomForest.innerobj.bufsize = original.randomForest.innerobj.bufsize;
-      randomForest.innerobj.nclasses = original.randomForest.innerobj.nclasses;
-      randomForest.innerobj.ntrees = original.randomForest.innerobj.ntrees;
-      randomForest.innerobj.nvars = original.randomForest.innerobj.nvars;
-      randomForest.innerobj.trees = (double[])original.randomForest.innerobj.trees.Clone();
-      targetVariable = original.targetVariable;
-      allowedInputVariables = (string[])original.allowedInputVariables.Clone();
-      if (original.classValues != null)
-        this.classValues = (double[])original.classValues.Clone();
-    }
-    public RandomForestModel(alglib.decisionforest randomForest, string targetVariable, IEnumerable<string> allowedInputVariables, double[] classValues = null)
+      // clone the model if necessary
+      if (original.randomForest != null) {
+        randomForest = new alglib.decisionforest();
+        randomForest.innerobj.bufsize = original.randomForest.innerobj.bufsize;
+        randomForest.innerobj.nclasses = original.randomForest.innerobj.nclasses;
+        randomForest.innerobj.ntrees = original.randomForest.innerobj.ntrees;
+        randomForest.innerobj.nvars = original.randomForest.innerobj.nvars;
+        // we assume that the trees array (double[]) is immutable in alglib
+        randomForest.innerobj.trees = original.randomForest.innerobj.trees;
+      }
+      // clone data which is necessary to rebuild the model
+      this.seed = original.seed;
+      // Dataset is immutable so we do not need to clone
+      this.originalTrainingData = original.originalTrainingData;
+      this.targetVariable = original.targetVariable;
+      // trainingRows, classvalues and allowedInputVariables are immutable
+      this.trainingRows = original.trainingRows;
+      this.allowedInputVariables = original.allowedInputVariables;
+      this.classValues = original.classValues;  // null for regression problems
+
+      this.nTrees = original.nTrees;
+      this.r = original.r;
+      this.m = original.m;
+    }
+
+    // random forest models can only be created through the static factory methods CreateRegressionModel and CreateClassificationModel
+    private RandomForestModel(alglib.decisionforest randomForest,
+      int seed, Dataset originalTrainingData, IEnumerable<int> trainingRows, IEnumerable<string> allowedInputVariables, string targetVariable,
+      int nTrees, double r, double m, double[] classValues = null)
       : base() {
       this.name = ItemName;
       this.description = ItemDescription;
+      // the model itself
       this.randomForest = randomForest;
+      // data which is necessary for recalculation of the model
+      this.seed = seed;
+      this.originalTrainingData = originalTrainingData;
+      this.trainingRows = (int[])trainingRows.ToArray().Clone();
       this.targetVariable = targetVariable;
-      this.allowedInputVariables = allowedInputVariables.ToArray();
-      if (classValues != null)
-        this.classValues = (double[])classValues.Clone();
+      this.allowedInputVariables = (string[])allowedInputVariables.ToArray().Clone();
+      this.classValues = classValues; // null for regression problems
+      this.nTrees = nTrees;
+      this.r = r;
+      this.m = m;
     }
 
@@ -88,6 +125,22 @@
     }
 
+    private void RecalculateModel() {
+      double rmsError, oobRmsError, relClassError, oobRelClassError;
+      if (classValues == null) {
+        var model = CreateRegressionModel(originalTrainingData, trainingRows, allowedInputVariables, targetVariable,
+                                              nTrees, r, m, seed, out rmsError, out oobRmsError,
+                                              out relClassError, out oobRelClassError);
+        randomForest = model.randomForest;
+      } else {
+        var model = CreateClassificationModel(originalTrainingData, trainingRows, allowedInputVariables, targetVariable,
+                                              classValues, nTrees, r, m, seed, out rmsError, out oobRmsError,
+                                              out relClassError, out oobRelClassError);
+        randomForest = model.randomForest;
+      }
+    }
+
     public IEnumerable<double> GetEstimatedValues(Dataset dataset, IEnumerable<int> rows) {
       double[,] inputData = AlglibUtil.PrepareInputMatrix(dataset, allowedInputVariables, rows);
+      AssertInputMatrix(inputData);
 
       int n = inputData.GetLength(0);
@@ -100,5 +153,5 @@
           x[column] = inputData[row, column];
         }
-        alglib.dfprocess(randomForest, x, ref y);
+        alglib.dfprocess(RandomForest, x, ref y);
         yield return y[0];
       }
@@ -107,9 +160,10 @@
     public IEnumerable<double> GetEstimatedClassValues(Dataset dataset, IEnumerable<int> rows) {
       double[,] inputData = AlglibUtil.PrepareInputMatrix(dataset, allowedInputVariables, rows);
+      AssertInputMatrix(inputData);
 
       int n = inputData.GetLength(0);
       int columns = inputData.GetLength(1);
       double[] x = new double[columns];
-      double[] y = new double[randomForest.innerobj.nclasses];
+      double[] y = new double[RandomForest.innerobj.nclasses];
 
       for (int row = 0; row < n; row++) {
@@ -144,55 +198,139 @@
     }
 
-    #region events
-    public event EventHandler Changed;
-    private void OnChanged(EventArgs e) {
-      var handlers = Changed;
-      if (handlers != null)
-        handlers(this, e);
-    }
-    #endregion
-
-    #region persistence
-    [Storable]
+    public static RandomForestModel CreateRegressionModel(IRegressionProblemData problemData, int nTrees, double r, double m, int seed,
+      out double rmsError, out double avgRelError, out double outOfBagAvgRelError, out double outOfBagRmsError) {
+      return CreateRegressionModel(problemData.Dataset, problemData.TrainingIndices,
+                                     problemData.AllowedInputVariables, problemData.TargetVariable,
+                                     nTrees, r, m, seed,
+                                     out rmsError, out outOfBagRmsError, out avgRelError, out outOfBagAvgRelError);
+    }
+
+    private static RandomForestModel CreateRegressionModel(
+      // prob param
+        Dataset dataset, IEnumerable<int> trainingRows, IEnumerable<string> allowedInputVariables, string targetVariable,
+      // alg param
+        int nTrees, double r, double m, int seed,
+      // results
+        out double rmsError, out double avgRelError, out double outOfBagAvgRelError, out double outOfBagRmsError) {
+
+      var variables = allowedInputVariables.Concat(new string[] { targetVariable });
+      double[,] inputMatrix = AlglibUtil.PrepareInputMatrix(dataset, variables, trainingRows);
+
+      alglib.dfreport rep;
+      var dForest = CreateRandomForestModel(seed, inputMatrix, nTrees, r, m, 1, out rep);
+
+      rmsError = rep.rmserror;
+      avgRelError = rep.avgrelerror;
+      outOfBagAvgRelError = rep.oobavgrelerror;
+      outOfBagRmsError = rep.oobrmserror;
+
+      return new RandomForestModel(dForest,
+        seed, dataset, trainingRows, allowedInputVariables, targetVariable,
+        nTrees, r, m);
+    }
+
+    public static RandomForestModel CreateClassificationModel(IClassificationProblemData problemData, int nTrees, double r, double m, int seed,
+      out double rmsError, out double outOfBagRmsError, out double relClassificationError, out double outOfBagRelClassificationError) {
+      return CreateClassificationModel(problemData.Dataset, problemData.TrainingIndices,
+                                       problemData.AllowedInputVariables, problemData.TargetVariable,
+                                       problemData.ClassValues.ToArray(),
+                                       nTrees, r, m, seed,
+                                       out rmsError, out outOfBagRmsError, out relClassificationError,
+                                       out outOfBagRelClassificationError);
+    }
+
+    private static RandomForestModel CreateClassificationModel(
+      // prob param
+        Dataset dataset, IEnumerable<int> trainingRows, IEnumerable<string> allowedInputVariables, string targetVariable, double[] classValues,
+      // alg param
+        int nTrees, double r, double m, int seed,
+      // results
+        out double rmsError, out double outOfBagRmsError, out double relClassificationError, out double outOfBagRelClassificationError) {
+
+      var variables = allowedInputVariables.Concat(new string[] { targetVariable });
+      double[,] inputMatrix = AlglibUtil.PrepareInputMatrix(dataset, variables, trainingRows);
+
+      int nClasses = classValues.Length;
+
+      // map original class values to values [0..nClasses-1]
+      var classIndices = new Dictionary<double, double>();
+      for (int i = 0; i < nClasses; i++) {
+        classIndices[classValues[i]] = i;
+      }
+
+      int nRows = inputMatrix.GetLength(0);
+      int nColumns = inputMatrix.GetLength(1);
+      for (int row = 0; row < nRows; row++) {
+        inputMatrix[row, nColumns - 1] = classIndices[inputMatrix[row, nColumns - 1]];
+      }
+
+      alglib.dfreport rep;
+      var dForest = CreateRandomForestModel(seed, inputMatrix, nTrees, r, m, nClasses, out rep);
+
+      rmsError = rep.rmserror;
+      outOfBagRmsError = rep.oobrmserror;
+      relClassificationError = rep.relclserror;
+      outOfBagRelClassificationError = rep.oobrelclserror;
+
+      return new RandomForestModel(dForest,
+        seed, dataset, trainingRows, allowedInputVariables, targetVariable,
+        nTrees, r, m, classValues);
+    }
+
+    private static alglib.decisionforest CreateRandomForestModel(int seed, double[,] inputMatrix, int nTrees, double r, double m, int nClasses, out alglib.dfreport rep) {
+      AssertParameters(r, m);
+      AssertInputMatrix(inputMatrix);
+
+      int info = 0;
+      alglib.math.rndobject = new System.Random(seed);
+      alglib.decisionforest dForest = new alglib.decisionforest();
+      rep = new alglib.dfreport();
+      int nRows = inputMatrix.GetLength(0);
+      int nColumns = inputMatrix.GetLength(1);
+      int sampleSize = Math.Max((int)Math.Round(r * nRows), 1);
+      int nFeatures = Math.Max((int)Math.Round(m * (nColumns - 1)), 1);
+
+      alglib.dforest.dfbuildinternal(inputMatrix, nRows, nColumns - 1, nClasses, nTrees, sampleSize, nFeatures, alglib.dforest.dfusestrongsplits + alglib.dforest.dfuseevs, ref info, dForest.innerobj, rep.innerobj);
+      if (info != 1) throw new ArgumentException("Error in calculation of random forest model");
+      return dForest;
+    }
+
+    private static void AssertParameters(double r, double m) {
+      if (r <= 0 || r > 1) throw new ArgumentException("The R parameter for random forest modeling must be between 0 and 1.");
+      if (m <= 0 || m > 1) throw new ArgumentException("The M parameter for random forest modeling must be between 0 and 1.");
+    }
+
+    private static void AssertInputMatrix(double[,] inputMatrix) {
+      if (inputMatrix.Cast<double>().Any(x => double.IsNaN(x) || double.IsInfinity(x)))
+        throw new NotSupportedException("Random forest modeling does not support NaN or infinity values in the input dataset.");
+    }
+
+    #region persistence for backwards compatibility
+    [Storable(AllowOneWay = true)]
     private int RandomForestBufSize {
-      get {
-        return randomForest.innerobj.bufsize;
-      }
       set {
         randomForest.innerobj.bufsize = value;
       }
     }
-    [Storable]
+    [Storable(AllowOneWay = true)]
     private int RandomForestNClasses {
-      get {
-        return randomForest.innerobj.nclasses;
-      }
       set {
         randomForest.innerobj.nclasses = value;
       }
     }
-    [Storable]
+    [Storable(AllowOneWay = true)]
     private int RandomForestNTrees {
-      get {
-        return randomForest.innerobj.ntrees;
-      }
       set {
         randomForest.innerobj.ntrees = value;
       }
     }
-    [Storable]
+    [Storable(AllowOneWay = true)]
     private int RandomForestNVars {
-      get {
-        return randomForest.innerobj.nvars;
-      }
       set {
         randomForest.innerobj.nvars = value;
       }
     }
-    [Storable]
+    [Storable(AllowOneWay = true)]
     private double[] RandomForestTrees {
-      get {
-        return randomForest.innerobj.trees;
-      }
       set {
         randomForest.innerobj.trees = value;
Index: /branches/1721-RandomForestPersistence/HeuristicLab.Algorithms.DataAnalysis/3.4/RandomForest/RandomForestRegression.cs
===================================================================
--- /branches/1721-RandomForestPersistence/HeuristicLab.Algorithms.DataAnalysis/3.4/RandomForest/RandomForestRegression.cs	(revision 10320)
+++ /branches/1721-RandomForestPersistence/HeuristicLab.Algorithms.DataAnalysis/3.4/RandomForest/RandomForestRegression.cs	(revision 10321)
@@ -130,34 +130,6 @@
     public static IRegressionSolution CreateRandomForestRegressionSolution(IRegressionProblemData problemData, int nTrees, double r, double m, int seed,
       out double rmsError, out double avgRelError, out double outOfBagRmsError, out double outOfBagAvgRelError) {
-      if (r <= 0 || r > 1) throw new ArgumentException("The R parameter in the random forest regression must be between 0 and 1.");
-      if (m <= 0 || m > 1) throw new ArgumentException("The M parameter in the random forest regression must be between 0 and 1.");
-
-      alglib.math.rndobject = new System.Random(seed);
-
-      Dataset dataset = problemData.Dataset;
-      string targetVariable = problemData.TargetVariable;
-      IEnumerable<string> allowedInputVariables = problemData.AllowedInputVariables;
-      IEnumerable<int> rows = problemData.TrainingIndices;
-      double[,] inputMatrix = AlglibUtil.PrepareInputMatrix(dataset, allowedInputVariables.Concat(new string[] { targetVariable }), rows);
-      if (inputMatrix.Cast<double>().Any(x => double.IsNaN(x) || double.IsInfinity(x)))
-        throw new NotSupportedException("Random forest regression does not support NaN or infinity values in the input dataset.");
-
-      int info = 0;
-      alglib.decisionforest dForest = new alglib.decisionforest();
-      alglib.dfreport rep = new alglib.dfreport(); ;
-      int nRows = inputMatrix.GetLength(0);
-      int nColumns = inputMatrix.GetLength(1);
-      int sampleSize = Math.Max((int)Math.Round(r * nRows), 1);
-      int nFeatures = Math.Max((int)Math.Round(m * (nColumns - 1)), 1);
-
-      alglib.dforest.dfbuildinternal(inputMatrix, nRows, nColumns - 1, 1, nTrees, sampleSize, nFeatures, alglib.dforest.dfusestrongsplits + alglib.dforest.dfuseevs, ref info, dForest.innerobj, rep.innerobj);
-      if (info != 1) throw new ArgumentException("Error in calculation of random forest regression solution");
-
-      rmsError = rep.rmserror;
-      avgRelError = rep.avgrelerror;
-      outOfBagAvgRelError = rep.oobavgrelerror;
-      outOfBagRmsError = rep.oobrmserror;
-
-      return new RandomForestRegressionSolution((IRegressionProblemData)problemData.Clone(), new RandomForestModel(dForest, targetVariable, allowedInputVariables));
+      var model = RandomForestModel.CreateRegressionModel(problemData, nTrees, r, m, seed, out rmsError, out avgRelError, out outOfBagRmsError, out outOfBagAvgRelError);
+      return new RandomForestRegressionSolution((IRegressionProblemData)problemData.Clone(), model);
     }
     #endregion
